Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Ilya Laktyushin 2022-02-27 04:17:21 +04:00
commit f0fd82f3c2
17 changed files with 341 additions and 98 deletions

View File

@ -1515,7 +1515,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
interaction.dismissInput() interaction.dismissInput()
}, present: { c, a in }, present: { c, a in
interaction.present(c, a) interaction.present(c, a)
}, transitionNode: { [weak self] messageId, media in }, transitionNode: { messageId, media in
var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))? var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
if let strongSelf = self { if let strongSelf = self {
strongSelf.listNode.forEachItemNode { itemNode in strongSelf.listNode.forEachItemNode { itemNode in
@ -1527,7 +1527,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
} }
} }
return transitionNode return transitionNode
}, addToTransitionSurface: { [weak self] view in }, addToTransitionSurface: { view in
self?.addToTransitionSurface(view: view) self?.addToTransitionSurface(view: view)
}, openUrl: { url in }, openUrl: { url in
interaction.openUrl(url) interaction.openUrl(url)
@ -1626,7 +1626,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
let firstTime = previousEntries == nil let firstTime = previousEntries == nil
var transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: newEntries, displayingResults: entriesAndFlags?.0 != nil, isEmpty: !isSearching && (entriesAndFlags?.0.isEmpty ?? false), isLoading: isSearching, animated: animated, context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: peersFilter, key: strongSelf.key, tagMask: tagMask, interaction: chatListInteraction, listInteraction: listInteraction, peerContextAction: { message, node, rect, gesture in var transition = chatListSearchContainerPreparedTransition(from: previousEntries ?? [], to: newEntries, displayingResults: entriesAndFlags?.0 != nil, isEmpty: !isSearching && (entriesAndFlags?.0.isEmpty ?? false), isLoading: isSearching, animated: animated, context: context, presentationData: strongSelf.presentationData, enableHeaders: true, filter: peersFilter, key: strongSelf.key, tagMask: tagMask, interaction: chatListInteraction, listInteraction: listInteraction, peerContextAction: { message, node, rect, gesture in
interaction.peerContextAction?(message, node, rect, gesture) interaction.peerContextAction?(message, node, rect, gesture)
}, toggleExpandLocalResults: { [weak self] in }, toggleExpandLocalResults: {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -1635,7 +1635,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
state.expandLocalSearch = !state.expandLocalSearch state.expandLocalSearch = !state.expandLocalSearch
return state return state
} }
}, toggleExpandGlobalResults: { [weak self] in }, toggleExpandGlobalResults: {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
@ -1959,6 +1959,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
self.recentDisposable.dispose() self.recentDisposable.dispose()
self.updatedRecentPeersDisposable.dispose() self.updatedRecentPeersDisposable.dispose()
self.deletedMessagesDisposable?.dispose() self.deletedMessagesDisposable?.dispose()
if self.key == .downloads {
print("downloads")
}
} }
override func didLoad() { override func didLoad() {

View File

@ -0,0 +1,18 @@
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
swift_library(
name = "ActivityIndicatorComponent",
module_name = "ActivityIndicatorComponent",
srcs = glob([
"Sources/**/*.swift",
]),
copts = [
"-warnings-as-errors",
],
deps = [
"//submodules/ComponentFlow:ComponentFlow",
],
visibility = [
"//visibility:public",
],
)

View File

@ -0,0 +1,39 @@
import Foundation
import UIKit
import ComponentFlow
public final class ActivityIndicatorComponent: Component {
public init(
) {
}
public static func ==(lhs: ActivityIndicatorComponent, rhs: ActivityIndicatorComponent) -> Bool {
return true
}
public final class View: UIActivityIndicatorView {
public init() {
super.init(style: .whiteLarge)
}
required public init(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func update(component: ActivityIndicatorComponent, availableSize: CGSize, transition: Transition) -> CGSize {
if !self.isAnimating {
self.startAnimating()
}
return CGSize(width: 22.0, height: 22.0)
}
}
public func makeView() -> View {
return View()
}
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition)
}
}

View File

@ -97,6 +97,7 @@ swift_library(
"//third-party/libyuv:LibYuvBinding", "//third-party/libyuv:LibYuvBinding",
"//submodules/ComponentFlow:ComponentFlow", "//submodules/ComponentFlow:ComponentFlow",
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent", "//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
"//submodules/Components/ActivityIndicatorComponent:ActivityIndicatorComponent",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -489,8 +489,12 @@ public final class MediaStreamComponent: CombinedComponent {
private(set) var displayUI: Bool = true private(set) var displayUI: Bool = true
var dismissOffset: CGFloat = 0.0 var dismissOffset: CGFloat = 0.0
var storedIsLandscape: Bool?
let isPictureInPictureSupported: Bool let isPictureInPictureSupported: Bool
private var scheduledDismissUITimer: SwiftSignalKit.Timer?
init(call: PresentationGroupCallImpl) { init(call: PresentationGroupCallImpl) {
self.call = call self.call = call
@ -551,6 +555,26 @@ public final class MediaStreamComponent: CombinedComponent {
self.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .easeInOut))) self.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .easeInOut)))
} }
func cancelScheduledDismissUI() {
self.scheduledDismissUITimer?.invalidate()
self.scheduledDismissUITimer = nil
}
func scheduleDismissUI() {
if self.scheduledDismissUITimer == nil {
self.scheduledDismissUITimer = SwiftSignalKit.Timer(timeout: 3.0, repeat: false, completion: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.scheduledDismissUITimer = nil
if strongSelf.displayUI {
strongSelf.toggleDisplayUI()
}
}, queue: .mainQueue())
self.scheduledDismissUITimer?.start()
}
}
func updateDismissOffset(value: CGFloat, interactive: Bool) { func updateDismissOffset(value: CGFloat, interactive: Bool) {
self.dismissOffset = value self.dismissOffset = value
if interactive { if interactive {
@ -575,7 +599,8 @@ public final class MediaStreamComponent: CombinedComponent {
return { context in return { context in
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
if !environment.isVisible { if environment.isVisible {
} else {
context.state.dismissOffset = 0.0 context.state.dismissOffset = 0.0
} }
@ -588,10 +613,10 @@ public final class MediaStreamComponent: CombinedComponent {
let call = context.component.call let call = context.component.call
let controller = environment.controller let controller = environment.controller
let video = Condition(context.state.hasVideo) { let video = video.update(
return video.update(
component: MediaStreamVideoComponent( component: MediaStreamVideoComponent(
call: context.component.call, call: context.component.call,
hasVideo: context.state.hasVideo,
activatePictureInPicture: activatePictureInPicture, activatePictureInPicture: activatePictureInPicture,
bringBackControllerForPictureInPictureDeactivation: { [weak call] completed in bringBackControllerForPictureInPictureDeactivation: { [weak call] completed in
guard let call = call else { guard let call = call else {
@ -607,10 +632,9 @@ public final class MediaStreamComponent: CombinedComponent {
availableSize: context.availableSize, availableSize: context.availableSize,
transition: context.transition transition: context.transition
) )
}
var navigationRightItems: [AnyComponentWithIdentity<Empty>] = [] var navigationRightItems: [AnyComponentWithIdentity<Empty>] = []
if context.state.isPictureInPictureSupported, let _ = video { if context.state.isPictureInPictureSupported, context.state.hasVideo {
navigationRightItems.append(AnyComponentWithIdentity(id: "pip", component: AnyComponent(Button( navigationRightItems.append(AnyComponentWithIdentity(id: "pip", component: AnyComponent(Button(
content: AnyComponent(BundleIconComponent( content: AnyComponent(BundleIconComponent(
name: "Media Gallery/PictureInPictureButton", name: "Media Gallery/PictureInPictureButton",
@ -663,7 +687,7 @@ public final class MediaStreamComponent: CombinedComponent {
topInset: environment.statusBarHeight, topInset: environment.statusBarHeight,
sideInset: environment.safeInsets.left, sideInset: environment.safeInsets.left,
leftItem: AnyComponent(Button( leftItem: AnyComponent(Button(
content: AnyComponent(NavigationBackButtonComponent(text: environment.strings.Common_Back, color: .white)), content: AnyComponent(NavigationBackButtonComponent(text: environment.strings.Common_Close, color: .white)),
action: { [weak call] in action: { [weak call] in
let _ = call?.leave(terminateIfPossible: false) let _ = call?.leave(terminateIfPossible: false)
}) })
@ -676,6 +700,14 @@ public final class MediaStreamComponent: CombinedComponent {
) )
let isLandscape = context.availableSize.width > context.availableSize.height let isLandscape = context.availableSize.width > context.availableSize.height
if context.state.storedIsLandscape != isLandscape {
context.state.storedIsLandscape = isLandscape
if isLandscape {
context.state.scheduleDismissUI()
} else {
context.state.cancelScheduledDismissUI()
}
}
var infoItem: AnyComponent<Empty>? var infoItem: AnyComponent<Empty>?
if let originInfo = context.state.originInfo { if let originInfo = context.state.originInfo {
@ -754,11 +786,9 @@ public final class MediaStreamComponent: CombinedComponent {
}) })
) )
if let video = video {
context.add(video context.add(video
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0 + context.state.dismissOffset)) .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0 + context.state.dismissOffset))
) )
}
context.add(navigationBar context.add(navigationBar
.position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height / 2.0)) .position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height / 2.0))
@ -776,6 +806,7 @@ public final class MediaStreamComponent: CombinedComponent {
} }
public final class MediaStreamComponentController: ViewControllerComponentContainer, VoiceChatController { public final class MediaStreamComponentController: ViewControllerComponentContainer, VoiceChatController {
private let context: AccountContext
public let call: PresentationGroupCall public let call: PresentationGroupCall
public private(set) var currentOverlayController: VoiceChatOverlayController? = nil public private(set) var currentOverlayController: VoiceChatOverlayController? = nil
public var parentNavigationController: NavigationController? public var parentNavigationController: NavigationController?
@ -785,13 +816,19 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
private var initialOrientation: UIInterfaceOrientation? private var initialOrientation: UIInterfaceOrientation?
private let inviteLinksPromise = Promise<GroupCallInviteLinks?>(nil)
public init(call: PresentationGroupCall) { public init(call: PresentationGroupCall) {
self.context = call.accountContext
self.call = call self.call = call
super.init(context: call.accountContext, component: MediaStreamComponent(call: call as! PresentationGroupCallImpl)) super.init(context: call.accountContext, component: MediaStreamComponent(call: call as! PresentationGroupCallImpl))
self.statusBar.statusBarStyle = .White self.statusBar.statusBarStyle = .White
self.view.disablesInteractiveModalDismiss = true self.view.disablesInteractiveModalDismiss = true
self.inviteLinksPromise.set(.single(nil)
|> then(call.inviteLinks))
} }
required public init(coder aDecoder: NSCoder) { required public init(coder aDecoder: NSCoder) {
@ -811,6 +848,10 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
self.onViewDidAppear?() self.onViewDidAppear?()
} }
if let view = self.node.hostView.findTaggedView(tag: MediaStreamVideoComponent.View.Tag()) as? MediaStreamVideoComponent.View {
view.expandFromPictureInPicture()
}
self.view.layer.allowsGroupOpacity = true self.view.layer.allowsGroupOpacity = true
self.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { [weak self] _ in self.view.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { [weak self] _ in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -858,6 +899,41 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
} }
func presentShare() { func presentShare() {
let _ = (self.inviteLinksPromise.get()
|> take(1)
|> deliverOnMainQueue).start(next: { [weak self] inviteLinks in
guard let strongSelf = self else {
return
}
let callPeerId = strongSelf.call.peerId
let _ = (strongSelf.call.accountContext.account.postbox.transaction { transaction -> GroupCallInviteLinks? in
if let inviteLinks = inviteLinks {
return inviteLinks
} else if let peer = transaction.getPeer(callPeerId), let addressName = peer.addressName, !addressName.isEmpty {
return GroupCallInviteLinks(listenerLink: "https://t.me/\(addressName)?voicechat", speakerLink: nil)
} else if let cachedData = transaction.getPeerCachedData(peerId: callPeerId) {
if let cachedData = cachedData as? CachedChannelData, let link = cachedData.exportedInvitation?.link {
return GroupCallInviteLinks(listenerLink: link, speakerLink: nil)
} else if let cachedData = cachedData as? CachedGroupData, let link = cachedData.exportedInvitation?.link {
return GroupCallInviteLinks(listenerLink: link, speakerLink: nil)
}
}
return nil
}
|> deliverOnMainQueue).start(next: { links in
guard let strongSelf = self else {
return
}
if let links = links {
strongSelf.presentShare(links: links)
}
})
})
}
func presentShare(links inviteLinks: GroupCallInviteLinks) {
let formatSendTitle: (String) -> String = { string in let formatSendTitle: (String) -> String = { string in
var string = string var string = string
if string.contains("[") && string.contains("]") { if string.contains("[") && string.contains("]") {
@ -869,34 +945,29 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
} }
return string return string
} }
let _ = formatSendTitle
let _ = (combineLatest(self.call.accountContext.account.postbox.loadedPeerWithId(self.call.peerId), self.call.state |> take(1)) let _ = (combineLatest(queue: .mainQueue(), self.context.account.postbox.loadedPeerWithId(self.call.peerId), self.call.state |> take(1))
|> deliverOnMainQueue).start(next: { [weak self] peer, callState in |> deliverOnMainQueue).start(next: { [weak self] peer, callState in
if let strongSelf = self { if let strongSelf = self {
var maybeInviteLinks: GroupCallInviteLinks? = nil var inviteLinks = inviteLinks
if let peer = peer as? TelegramChannel, let addressName = peer.addressName { if let peer = peer as? TelegramChannel, case .group = peer.info, !peer.flags.contains(.isGigagroup), !(peer.addressName ?? "").isEmpty, let defaultParticipantMuteState = callState.defaultParticipantMuteState {
maybeInviteLinks = GroupCallInviteLinks(listenerLink: "https://t.me/\(addressName)", speakerLink: nil) let isMuted = defaultParticipantMuteState == .muted
if !isMuted {
inviteLinks = GroupCallInviteLinks(listenerLink: inviteLinks.listenerLink, speakerLink: nil)
}
} }
guard let inviteLinks = maybeInviteLinks else { let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
return
}
let presentationData = strongSelf.call.accountContext.sharedContext.currentPresentationData.with { $0 }
var segmentedValues: [ShareControllerSegmentedValue]? var segmentedValues: [ShareControllerSegmentedValue]?
if let speakerLink = inviteLinks.speakerLink { segmentedValues = nil
segmentedValues = [ShareControllerSegmentedValue(title: presentationData.strings.VoiceChat_InviteLink_Speaker, subject: .url(speakerLink), actionTitle: presentationData.strings.VoiceChat_InviteLink_CopySpeakerLink, formatSendTitle: { count in let shareController = ShareController(context: strongSelf.context, subject: .url(inviteLinks.listenerLink), segmentedValues: segmentedValues, forceTheme: defaultDarkPresentationTheme, forcedActionTitle: presentationData.strings.VoiceChat_CopyInviteLink)
return formatSendTitle(presentationData.strings.VoiceChat_InviteLink_InviteSpeakers(Int32(count)))
}), ShareControllerSegmentedValue(title: presentationData.strings.VoiceChat_InviteLink_Listener, subject: .url(inviteLinks.listenerLink), actionTitle: presentationData.strings.VoiceChat_InviteLink_CopyListenerLink, formatSendTitle: { count in
return formatSendTitle(presentationData.strings.VoiceChat_InviteLink_InviteListeners(Int32(count)))
})]
}
let shareController = ShareController(context: strongSelf.call.accountContext, subject: .url(inviteLinks.listenerLink), segmentedValues: segmentedValues, forceTheme: defaultDarkColorPresentationTheme, forcedActionTitle: presentationData.strings.VoiceChat_CopyInviteLink)
shareController.completed = { [weak self] peerIds in shareController.completed = { [weak self] peerIds in
if let strongSelf = self { if let strongSelf = self {
let _ = (strongSelf.call.accountContext.account.postbox.transaction { transaction -> [Peer] in let _ = (strongSelf.context.account.postbox.transaction { transaction -> [Peer] in
var peers: [Peer] = [] var peers: [Peer] = []
for peerId in peerIds { for peerId in peerIds {
if let peer = transaction.getPeer(peerId) { if let peer = transaction.getPeer(peerId) {
@ -904,19 +975,19 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
} }
} }
return peers return peers
} |> deliverOnMainQueue).start(next: { peers in } |> deliverOnMainQueue).start(next: { [weak self] peers in
if let strongSelf = self { if let strongSelf = self {
let presentationData = strongSelf.call.accountContext.sharedContext.currentPresentationData.with { $0 } let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
let text: String let text: String
var isSavedMessages = false var isSavedMessages = false
if peers.count == 1, let peer = peers.first { if peers.count == 1, let peer = peers.first {
isSavedMessages = peer.id == strongSelf.call.accountContext.account.peerId isSavedMessages = peer.id == strongSelf.context.account.peerId
let peerName = peer.id == strongSelf.call.accountContext.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) let peerName = peer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
text = presentationData.strings.VoiceChat_ForwardTooltip_Chat(peerName).string text = presentationData.strings.VoiceChat_ForwardTooltip_Chat(peerName).string
} else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last { } else if peers.count == 2, let firstPeer = peers.first, let secondPeer = peers.last {
let firstPeerName = firstPeer.id == strongSelf.call.accountContext.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(firstPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) let firstPeerName = firstPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(firstPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
let secondPeerName = secondPeer.id == strongSelf.call.accountContext.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(secondPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) let secondPeerName = secondPeer.id == strongSelf.context.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(secondPeer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
text = presentationData.strings.VoiceChat_ForwardTooltip_TwoChats(firstPeerName, secondPeerName).string text = presentationData.strings.VoiceChat_ForwardTooltip_TwoChats(firstPeerName, secondPeerName).string
} else if let peer = peers.first { } else if let peer = peers.first {
let peerName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) let peerName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
@ -932,7 +1003,7 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
} }
shareController.actionCompleted = { shareController.actionCompleted = {
if let strongSelf = self { if let strongSelf = self {
let presentationData = strongSelf.call.accountContext.sharedContext.currentPresentationData.with { $0 } let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.VoiceChat_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.VoiceChat_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
} }
} }

View File

@ -1,16 +1,19 @@
import Foundation import Foundation
import UIKit import UIKit
import ComponentFlow import ComponentFlow
import ActivityIndicatorComponent
import AccountContext import AccountContext
import AVKit import AVKit
final class MediaStreamVideoComponent: Component { final class MediaStreamVideoComponent: Component {
let call: PresentationGroupCallImpl let call: PresentationGroupCallImpl
let hasVideo: Bool
let activatePictureInPicture: ActionSlot<Action<Void>> let activatePictureInPicture: ActionSlot<Action<Void>>
let bringBackControllerForPictureInPictureDeactivation: (@escaping () -> Void) -> Void let bringBackControllerForPictureInPictureDeactivation: (@escaping () -> Void) -> Void
init(call: PresentationGroupCallImpl, activatePictureInPicture: ActionSlot<Action<Void>>, bringBackControllerForPictureInPictureDeactivation: @escaping (@escaping () -> Void) -> Void) { init(call: PresentationGroupCallImpl, hasVideo: Bool, activatePictureInPicture: ActionSlot<Action<Void>>, bringBackControllerForPictureInPictureDeactivation: @escaping (@escaping () -> Void) -> Void) {
self.call = call self.call = call
self.hasVideo = hasVideo
self.activatePictureInPicture = activatePictureInPicture self.activatePictureInPicture = activatePictureInPicture
self.bringBackControllerForPictureInPictureDeactivation = bringBackControllerForPictureInPictureDeactivation self.bringBackControllerForPictureInPictureDeactivation = bringBackControllerForPictureInPictureDeactivation
} }
@ -19,6 +22,9 @@ final class MediaStreamVideoComponent: Component {
if lhs.call !== rhs.call { if lhs.call !== rhs.call {
return false return false
} }
if lhs.hasVideo != rhs.hasVideo {
return false
}
return true return true
} }
@ -33,19 +39,24 @@ final class MediaStreamVideoComponent: Component {
return State() return State()
} }
public final class View: UIView, AVPictureInPictureControllerDelegate, AVPictureInPictureSampleBufferPlaybackDelegate { public final class View: UIView, AVPictureInPictureControllerDelegate, AVPictureInPictureSampleBufferPlaybackDelegate, ComponentTaggedView {
public final class Tag {
}
private let videoRenderingContext = VideoRenderingContext() private let videoRenderingContext = VideoRenderingContext()
private var videoView: VideoRenderingView? private var videoView: VideoRenderingView?
private let blurTintView: UIView private let blurTintView: UIView
private var videoBlurView: VideoRenderingView? private var videoBlurView: VideoRenderingView?
private var activityIndicatorView: ComponentHostView<Empty>?
private var pictureInPictureController: AVPictureInPictureController? private var pictureInPictureController: AVPictureInPictureController?
private var component: MediaStreamVideoComponent? private var component: MediaStreamVideoComponent?
private var hadVideo: Bool = false
override init(frame: CGRect) { override init(frame: CGRect) {
self.blurTintView = UIView() self.blurTintView = UIView()
self.blurTintView.backgroundColor = UIColor(white: 0.0, alpha: 0.5) self.blurTintView.backgroundColor = UIColor(white: 0.0, alpha: 0.55)
super.init(frame: frame) super.init(frame: frame)
@ -59,8 +70,19 @@ final class MediaStreamVideoComponent: Component {
fatalError("init(coder:) has not been implemented") fatalError("init(coder:) has not been implemented")
} }
public func matches(tag: Any) -> Bool {
if let _ = tag as? Tag {
return true
}
return false
}
func expandFromPictureInPicture() {
self.pictureInPictureController?.stopPictureInPicture()
}
func update(component: MediaStreamVideoComponent, availableSize: CGSize, state: State, transition: Transition) -> CGSize { func update(component: MediaStreamVideoComponent, availableSize: CGSize, state: State, transition: Transition) -> CGSize {
if self.videoView == nil { if component.hasVideo, self.videoView == nil {
if let input = component.call.video(endpointId: "unified") { if let input = component.call.video(endpointId: "unified") {
if let videoBlurView = self.videoRenderingContext.makeView(input: input, blur: true) { if let videoBlurView = self.videoRenderingContext.makeView(input: input, blur: true) {
self.videoBlurView = videoBlurView self.videoBlurView = videoBlurView
@ -73,17 +95,26 @@ final class MediaStreamVideoComponent: Component {
if #available(iOSApplicationExtension 15.0, iOS 15.0, *), AVPictureInPictureController.isPictureInPictureSupported(), let sampleBufferVideoView = videoView as? SampleBufferVideoRenderingView { if #available(iOSApplicationExtension 15.0, iOS 15.0, *), AVPictureInPictureController.isPictureInPictureSupported(), let sampleBufferVideoView = videoView as? SampleBufferVideoRenderingView {
let pictureInPictureController = AVPictureInPictureController(contentSource: AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: sampleBufferVideoView.sampleBufferLayer, playbackDelegate: self)) let pictureInPictureController = AVPictureInPictureController(contentSource: AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: sampleBufferVideoView.sampleBufferLayer, playbackDelegate: self))
self.pictureInPictureController = pictureInPictureController
pictureInPictureController.delegate = self
pictureInPictureController.canStartPictureInPictureAutomaticallyFromInline = true pictureInPictureController.canStartPictureInPictureAutomaticallyFromInline = true
pictureInPictureController.requiresLinearPlayback = true pictureInPictureController.requiresLinearPlayback = true
pictureInPictureController.delegate = self
self.pictureInPictureController = pictureInPictureController
} }
videoView.setOnOrientationUpdated { [weak state] _, _ in videoView.setOnOrientationUpdated { [weak state] _, _ in
state?.updated(transition: .immediate) state?.updated(transition: .immediate)
} }
videoView.setOnFirstFrameReceived { [weak state] _ in videoView.setOnFirstFrameReceived { [weak self, weak state] _ in
guard let strongSelf = self else {
return
}
strongSelf.hadVideo = true
strongSelf.activityIndicatorView?.removeFromSuperview()
strongSelf.activityIndicatorView = nil
state?.updated(transition: .immediate) state?.updated(transition: .immediate)
} }
} }
@ -108,6 +139,27 @@ final class MediaStreamVideoComponent: Component {
} }
} }
if !self.hadVideo {
var activityIndicatorTransition = transition
let activityIndicatorView: ComponentHostView<Empty>
if let current = self.activityIndicatorView {
activityIndicatorView = current
} else {
activityIndicatorTransition = transition.withAnimation(.none)
activityIndicatorView = ComponentHostView<Empty>()
self.activityIndicatorView = activityIndicatorView
self.addSubview(activityIndicatorView)
}
let activityIndicatorSize = activityIndicatorView.update(
transition: transition,
component: AnyComponent(ActivityIndicatorComponent()),
environment: {},
containerSize: CGSize(width: 100.0, height: 100.0)
)
activityIndicatorTransition.setFrame(view: activityIndicatorView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - activityIndicatorSize.width) / 2.0), y: floor((availableSize.height - activityIndicatorSize.height) / 2.0)), size: activityIndicatorSize), completion: nil)
}
self.component = component self.component = component
component.activatePictureInPicture.connect { [weak self] completion in component.activatePictureInPicture.connect { [weak self] completion in

View File

@ -78,12 +78,12 @@ open class ViewControllerComponentContainer: ViewController {
} }
} }
private final class Node: ViewControllerTracingNode { final class Node: ViewControllerTracingNode {
private var presentationData: PresentationData private var presentationData: PresentationData
private weak var controller: ViewControllerComponentContainer? private weak var controller: ViewControllerComponentContainer?
private let component: AnyComponent<ViewControllerComponentContainer.Environment> private let component: AnyComponent<ViewControllerComponentContainer.Environment>
private let hostView: ComponentHostView<ViewControllerComponentContainer.Environment> let hostView: ComponentHostView<ViewControllerComponentContainer.Environment>
private var currentIsVisible: Bool = false private var currentIsVisible: Bool = false
private var currentLayout: ContainerViewLayout? private var currentLayout: ContainerViewLayout?
@ -137,7 +137,7 @@ open class ViewControllerComponentContainer: ViewController {
} }
} }
private var node: Node { var node: Node {
return self.displayNode as! Node return self.displayNode as! Node
} }

View File

@ -379,6 +379,8 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
let membersText: String let membersText: String
if summaryState.participantCount == 0 { if summaryState.participantCount == 0 {
membersText = strongSelf.strings.VoiceChat_Panel_TapToJoin membersText = strongSelf.strings.VoiceChat_Panel_TapToJoin
} else if let info = summaryState.info, info.isStream {
membersText = strongSelf.strings.LiveStream_ViewerCount(Int32(summaryState.participantCount))
} else { } else {
membersText = strongSelf.strings.VoiceChat_Panel_Members(Int32(summaryState.participantCount)) membersText = strongSelf.strings.VoiceChat_Panel_Members(Int32(summaryState.participantCount))
} }

View File

@ -687,7 +687,8 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
peerId: peerId, peerId: peerId,
isChannel: isChannel, isChannel: isChannel,
invite: nil, invite: nil,
joinAsPeerId: nil joinAsPeerId: nil,
isStream: false
) )
strongSelf.updateCurrentGroupCall(call) strongSelf.updateCurrentGroupCall(call)
strongSelf.currentGroupCallPromise.set(.single(call)) strongSelf.currentGroupCallPromise.set(.single(call))
@ -849,7 +850,8 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
peerId: peerId, peerId: peerId,
isChannel: isChannel, isChannel: isChannel,
invite: invite, invite: invite,
joinAsPeerId: joinAsPeerId joinAsPeerId: joinAsPeerId,
isStream: initialCall.isStream ?? false
) )
strongSelf.updateCurrentGroupCall(call) strongSelf.updateCurrentGroupCall(call)
strongSelf.currentGroupCallPromise.set(.single(call)) strongSelf.currentGroupCallPromise.set(.single(call))

View File

@ -86,13 +86,14 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
var disposable: Disposable? var disposable: Disposable?
public var participantsContext: GroupCallParticipantsContext? public var participantsContext: GroupCallParticipantsContext?
private let panelDataPromise = Promise<GroupCallPanelData>() private let panelDataPromise = Promise<GroupCallPanelData?>()
public var panelData: Signal<GroupCallPanelData, NoError> { public var panelData: Signal<GroupCallPanelData?, NoError> {
return self.panelDataPromise.get() return self.panelDataPromise.get()
} }
public init(account: Account, engine: TelegramEngine, peerId: PeerId, isChannel: Bool, call: EngineGroupCallDescription) { public init(account: Account, engine: TelegramEngine, peerId: PeerId, isChannel: Bool, call: EngineGroupCallDescription) {
self.panelDataPromise.set(.single(GroupCallPanelData( self.panelDataPromise.set(.single(nil))
/*self.panelDataPromise.set(.single(GroupCallPanelData(
peerId: peerId, peerId: peerId,
isChannel: isChannel, isChannel: isChannel,
info: GroupCallInfo( info: GroupCallInfo(
@ -114,7 +115,7 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
participantCount: 0, participantCount: 0,
activeSpeakers: Set(), activeSpeakers: Set(),
groupCall: nil groupCall: nil
))) )))*/
let state = engine.calls.getGroupCallParticipants(callId: call.id, accessHash: call.accessHash, offset: "", ssrcs: [], limit: 100, sortAscending: nil) let state = engine.calls.getGroupCallParticipants(callId: call.id, accessHash: call.accessHash, offset: "", ssrcs: [], limit: 100, sortAscending: nil)
|> map(Optional.init) |> map(Optional.init)
@ -161,7 +162,7 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
return GroupCallPanelData( return GroupCallPanelData(
peerId: peerId, peerId: peerId,
isChannel: isChannel, isChannel: isChannel,
info: GroupCallInfo(id: call.id, accessHash: call.accessHash, participantCount: state.totalCount, streamDcId: nil, title: state.title, scheduleTimestamp: state.scheduleTimestamp, subscribedToScheduled: state.subscribedToScheduled, recordingStartTimestamp: nil, sortAscending: state.sortAscending, defaultParticipantsAreMuted: state.defaultParticipantsAreMuted, isVideoEnabled: state.isVideoEnabled, unmutedVideoLimit: state.unmutedVideoLimit, isStream: call.isStream), info: GroupCallInfo(id: call.id, accessHash: call.accessHash, participantCount: state.totalCount, streamDcId: nil, title: state.title, scheduleTimestamp: state.scheduleTimestamp, subscribedToScheduled: state.subscribedToScheduled, recordingStartTimestamp: nil, sortAscending: state.sortAscending, defaultParticipantsAreMuted: state.defaultParticipantsAreMuted, isVideoEnabled: state.isVideoEnabled, unmutedVideoLimit: state.unmutedVideoLimit, isStream: state.isStream),
topParticipants: topParticipants, topParticipants: topParticipants,
participantCount: state.totalCount, participantCount: state.totalCount,
activeSpeakers: activeSpeakers, activeSpeakers: activeSpeakers,
@ -229,7 +230,14 @@ public final class AccountGroupCallContextCacheImpl: AccountGroupCallContextCach
} }
public func leaveInBackground(engine: TelegramEngine, id: Int64, accessHash: Int64, source: UInt32) { public func leaveInBackground(engine: TelegramEngine, id: Int64, accessHash: Int64, source: UInt32) {
let disposable = engine.calls.leaveGroupCall(callId: id, accessHash: accessHash, source: source).start() let disposable = engine.calls.leaveGroupCall(callId: id, accessHash: accessHash, source: source).start(completed: { [weak self] in
guard let strongSelf = self else {
return
}
if let context = strongSelf.contexts[id] {
context.context.participantsContext?.removeLocalPeerId()
}
})
self.leaveDisposables.add(disposable) self.leaveDisposables.add(disposable)
} }
} }
@ -630,7 +638,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private var screencastAudioDataDisposable: Disposable? private var screencastAudioDataDisposable: Disposable?
private var screencastStateDisposable: Disposable? private var screencastStateDisposable: Disposable?
public var isStream = false public let isStream: Bool
init( init(
accountContext: AccountContext, accountContext: AccountContext,
@ -642,7 +650,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
peerId: PeerId, peerId: PeerId,
isChannel: Bool, isChannel: Bool,
invite: String?, invite: String?,
joinAsPeerId: PeerId? joinAsPeerId: PeerId?,
isStream: Bool
) { ) {
self.account = accountContext.account self.account = accountContext.account
self.accountContext = accountContext self.accountContext = accountContext
@ -659,10 +668,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.schedulePending = initialCall == nil self.schedulePending = initialCall == nil
self.isScheduled = initialCall == nil || initialCall?.scheduleTimestamp != nil self.isScheduled = initialCall == nil || initialCall?.scheduleTimestamp != nil
if let initialCall = initialCall {
self.isStream = initialCall.isStream
}
self.stateValue = PresentationGroupCallState.initialValue(myPeerId: self.joinAsPeerId, title: initialCall?.title, scheduleTimestamp: initialCall?.scheduleTimestamp, subscribedToScheduled: initialCall?.subscribedToScheduled ?? false) self.stateValue = PresentationGroupCallState.initialValue(myPeerId: self.joinAsPeerId, title: initialCall?.title, scheduleTimestamp: initialCall?.scheduleTimestamp, subscribedToScheduled: initialCall?.subscribedToScheduled ?? false)
self.statePromise = ValuePromise(self.stateValue) self.statePromise = ValuePromise(self.stateValue)
@ -671,6 +676,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.isVideoEnabled = true self.isVideoEnabled = true
self.hasVideo = false self.hasVideo = false
self.hasScreencast = false self.hasScreencast = false
self.isStream = isStream
var didReceiveAudioOutputs = false var didReceiveAudioOutputs = false
@ -1228,6 +1234,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
totalCount: 0, totalCount: 0,
isVideoEnabled: callInfo.isVideoEnabled, isVideoEnabled: callInfo.isVideoEnabled,
unmutedVideoLimit: callInfo.unmutedVideoLimit, unmutedVideoLimit: callInfo.unmutedVideoLimit,
isStream: callInfo.isStream,
version: 0 version: 0
), ),
previousServiceState: nil previousServiceState: nil
@ -1677,7 +1684,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
strongSelf.processMyAudioLevel(level: mappedLevel, hasVoice: myLevelHasVoice) strongSelf.processMyAudioLevel(level: mappedLevel, hasVoice: myLevelHasVoice)
strongSelf.isSpeakingPromise.set(orignalMyLevelHasVoice) strongSelf.isSpeakingPromise.set(orignalMyLevelHasVoice)
if !missingSsrcs.isEmpty { if !missingSsrcs.isEmpty && !strongSelf.isStream {
strongSelf.participantsContext?.ensureHaveParticipants(ssrcs: missingSsrcs) strongSelf.participantsContext?.ensureHaveParticipants(ssrcs: missingSsrcs)
} }
})) }))
@ -2228,6 +2235,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
} }
private func beginTone(tone: PresentationCallTone) { private func beginTone(tone: PresentationCallTone) {
if self.isStream {
switch tone {
case .groupJoined, .groupLeft:
return
default:
break
}
}
var completed: (() -> Void)? var completed: (() -> Void)?
let toneRenderer = PresentationCallToneRenderer(tone: tone, completed: { let toneRenderer = PresentationCallToneRenderer(tone: tone, completed: {
completed?() completed?()
@ -2995,7 +3010,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
if let value = value { if let value = value {
strongSelf.initialCall = EngineGroupCallDescription(id: value.id, accessHash: value.accessHash, title: value.title, scheduleTimestamp: nil, subscribedToScheduled: false, isStream: value.isStream) strongSelf.initialCall = EngineGroupCallDescription(id: value.id, accessHash: value.accessHash, title: value.title, scheduleTimestamp: nil, subscribedToScheduled: false, isStream: value.isStream)
strongSelf.isStream = value.isStream
strongSelf.updateSessionState(internalState: .active(value), audioSessionControl: strongSelf.audioSessionControl) strongSelf.updateSessionState(internalState: .active(value), audioSessionControl: strongSelf.audioSessionControl)
} else { } else {

View File

@ -161,7 +161,7 @@ public final class CachedChannelData: CachedPeerData {
public var title: String? public var title: String?
public var scheduleTimestamp: Int32? public var scheduleTimestamp: Int32?
public var subscribedToScheduled: Bool public var subscribedToScheduled: Bool
public var isStream: Bool public var isStream: Bool?
public init( public init(
id: Int64, id: Int64,
@ -169,7 +169,7 @@ public final class CachedChannelData: CachedPeerData {
title: String?, title: String?,
scheduleTimestamp: Int32?, scheduleTimestamp: Int32?,
subscribedToScheduled: Bool, subscribedToScheduled: Bool,
isStream: Bool isStream: Bool?
) { ) {
self.id = id self.id = id
self.accessHash = accessHash self.accessHash = accessHash
@ -185,7 +185,7 @@ public final class CachedChannelData: CachedPeerData {
self.title = decoder.decodeOptionalStringForKey("title") self.title = decoder.decodeOptionalStringForKey("title")
self.scheduleTimestamp = decoder.decodeOptionalInt32ForKey("scheduleTimestamp") self.scheduleTimestamp = decoder.decodeOptionalInt32ForKey("scheduleTimestamp")
self.subscribedToScheduled = decoder.decodeBoolForKey("subscribed", orElse: false) self.subscribedToScheduled = decoder.decodeBoolForKey("subscribed", orElse: false)
self.isStream = decoder.decodeBoolForKey("isStream", orElse: false) self.isStream = decoder.decodeOptionalBoolForKey("isStream_v2")
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
@ -202,7 +202,11 @@ public final class CachedChannelData: CachedPeerData {
encoder.encodeNil(forKey: "scheduleTimestamp") encoder.encodeNil(forKey: "scheduleTimestamp")
} }
encoder.encodeBool(self.subscribedToScheduled, forKey: "subscribed") encoder.encodeBool(self.subscribedToScheduled, forKey: "subscribed")
encoder.encodeBool(self.isStream, forKey: "isStream") if let isStream = self.isStream {
encoder.encodeBool(isStream, forKey: "isStream")
} else {
encoder.encodeNil(forKey: "isStream")
}
} }
} }

View File

@ -342,17 +342,17 @@ public enum GetGroupCallParticipantsError {
} }
func _internal_getGroupCallParticipants(account: Account, callId: Int64, accessHash: Int64, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> { func _internal_getGroupCallParticipants(account: Account, callId: Int64, accessHash: Int64, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
let sortAscendingValue: Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int), GetGroupCallParticipantsError> let sortAscendingValue: Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int, Bool), GetGroupCallParticipantsError>
sortAscendingValue = _internal_getCurrentGroupCall(account: account, callId: callId, accessHash: accessHash) sortAscendingValue = _internal_getCurrentGroupCall(account: account, callId: callId, accessHash: accessHash)
|> mapError { _ -> GetGroupCallParticipantsError in |> mapError { _ -> GetGroupCallParticipantsError in
return .generic return .generic
} }
|> mapToSignal { result -> Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int), GetGroupCallParticipantsError> in |> mapToSignal { result -> Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int, Bool), GetGroupCallParticipantsError> in
guard let result = result else { guard let result = result else {
return .fail(.generic) return .fail(.generic)
} }
return .single((sortAscending ?? result.info.sortAscending, result.info.scheduleTimestamp, result.info.subscribedToScheduled, result.info.defaultParticipantsAreMuted, result.info.isVideoEnabled, result.info.unmutedVideoLimit)) return .single((sortAscending ?? result.info.sortAscending, result.info.scheduleTimestamp, result.info.subscribedToScheduled, result.info.defaultParticipantsAreMuted, result.info.isVideoEnabled, result.info.unmutedVideoLimit, result.info.isStream))
} }
return combineLatest( return combineLatest(
@ -369,7 +369,7 @@ func _internal_getGroupCallParticipants(account: Account, callId: Int64, accessH
let version: Int32 let version: Int32
let nextParticipantsFetchOffset: String? let nextParticipantsFetchOffset: String?
let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, isVideoEnabled, unmutedVideoLimit) = sortAscendingAndScheduleTimestamp let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, isVideoEnabled, unmutedVideoLimit, isStream) = sortAscendingAndScheduleTimestamp
switch result { switch result {
case let .groupParticipants(count, participants, nextOffset, chats, users, apiVersion): case let .groupParticipants(count, participants, nextOffset, chats, users, apiVersion):
@ -423,6 +423,7 @@ func _internal_getGroupCallParticipants(account: Account, callId: Int64, accessH
totalCount: totalCount, totalCount: totalCount,
isVideoEnabled: isVideoEnabled, isVideoEnabled: isVideoEnabled,
unmutedVideoLimit: unmutedVideoLimit, unmutedVideoLimit: unmutedVideoLimit,
isStream: isStream,
version: version version: version
) )
} }
@ -1047,6 +1048,7 @@ public final class GroupCallParticipantsContext {
public var totalCount: Int public var totalCount: Int
public var isVideoEnabled: Bool public var isVideoEnabled: Bool
public var unmutedVideoLimit: Int public var unmutedVideoLimit: Int
public var isStream: Bool
public var version: Int32 public var version: Int32
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) {
@ -1081,6 +1083,7 @@ public final class GroupCallParticipantsContext {
totalCount: Int, totalCount: Int,
isVideoEnabled: Bool, isVideoEnabled: Bool,
unmutedVideoLimit: Int, unmutedVideoLimit: Int,
isStream: Bool,
version: Int32 version: Int32
) { ) {
self.participants = participants self.participants = participants
@ -1096,6 +1099,7 @@ public final class GroupCallParticipantsContext {
self.totalCount = totalCount self.totalCount = totalCount
self.isVideoEnabled = isVideoEnabled self.isVideoEnabled = isVideoEnabled
self.unmutedVideoLimit = unmutedVideoLimit self.unmutedVideoLimit = unmutedVideoLimit
self.isStream = isStream
self.version = version self.version = version
} }
} }
@ -1398,6 +1402,7 @@ public final class GroupCallParticipantsContext {
totalCount: strongSelf.stateValue.state.totalCount, totalCount: strongSelf.stateValue.state.totalCount,
isVideoEnabled: strongSelf.stateValue.state.isVideoEnabled, isVideoEnabled: strongSelf.stateValue.state.isVideoEnabled,
unmutedVideoLimit: strongSelf.stateValue.state.unmutedVideoLimit, unmutedVideoLimit: strongSelf.stateValue.state.unmutedVideoLimit,
isStream: strongSelf.stateValue.state.isStream,
version: strongSelf.stateValue.state.version version: strongSelf.stateValue.state.version
), ),
overlayState: strongSelf.stateValue.overlayState overlayState: strongSelf.stateValue.overlayState
@ -1471,6 +1476,14 @@ public final class GroupCallParticipantsContext {
} }
} }
public func removeLocalPeerId() {
var state = self.stateValue.state
state.participants.removeAll(where: { $0.peer.id == self.myPeerId })
self.stateValue.state = state
}
private func takeNextActivityRank() -> Int { private func takeNextActivityRank() -> Int {
let value = self.serviceState.nextActivityRank let value = self.serviceState.nextActivityRank
self.serviceState.nextActivityRank += 1 self.serviceState.nextActivityRank += 1
@ -1537,6 +1550,7 @@ public final class GroupCallParticipantsContext {
totalCount: strongSelf.stateValue.state.totalCount, totalCount: strongSelf.stateValue.state.totalCount,
isVideoEnabled: strongSelf.stateValue.state.isVideoEnabled, isVideoEnabled: strongSelf.stateValue.state.isVideoEnabled,
unmutedVideoLimit: strongSelf.stateValue.state.unmutedVideoLimit, unmutedVideoLimit: strongSelf.stateValue.state.unmutedVideoLimit,
isStream: strongSelf.stateValue.state.isStream,
version: strongSelf.stateValue.state.version version: strongSelf.stateValue.state.version
), ),
overlayState: strongSelf.stateValue.overlayState overlayState: strongSelf.stateValue.overlayState
@ -1758,6 +1772,7 @@ public final class GroupCallParticipantsContext {
let scheduleTimestamp = strongSelf.stateValue.state.scheduleTimestamp let scheduleTimestamp = strongSelf.stateValue.state.scheduleTimestamp
let subscribedToScheduled = strongSelf.stateValue.state.subscribedToScheduled let subscribedToScheduled = strongSelf.stateValue.state.subscribedToScheduled
let isVideoEnabled = strongSelf.stateValue.state.isVideoEnabled let isVideoEnabled = strongSelf.stateValue.state.isVideoEnabled
let isStream = strongSelf.stateValue.state.isStream
let unmutedVideoLimit = strongSelf.stateValue.state.unmutedVideoLimit let unmutedVideoLimit = strongSelf.stateValue.state.unmutedVideoLimit
updatedParticipants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: strongSelf.stateValue.state.sortAscending) }) updatedParticipants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: strongSelf.stateValue.state.sortAscending) })
@ -1777,6 +1792,7 @@ public final class GroupCallParticipantsContext {
totalCount: updatedTotalCount, totalCount: updatedTotalCount,
isVideoEnabled: isVideoEnabled, isVideoEnabled: isVideoEnabled,
unmutedVideoLimit: unmutedVideoLimit, unmutedVideoLimit: unmutedVideoLimit,
isStream: isStream,
version: update.version version: update.version
), ),
overlayState: updatedOverlayState overlayState: updatedOverlayState
@ -2398,7 +2414,7 @@ private func mergeAndSortParticipants(current currentParticipants: [GroupCallPar
} }
public final class AudioBroadcastDataSource { public final class AudioBroadcastDataSource {
fileprivate let download: Download let download: Download
fileprivate init(download: Download) { fileprivate init(download: Download) {
self.download = download self.download = download

View File

@ -132,8 +132,8 @@ public extension TelegramEngine {
|> take(1) |> take(1)
} }
public func requestStreamState(callId: Int64, accessHash: Int64) -> Signal<EngineCallStreamState?, NoError> { public func requestStreamState(dataSource: AudioBroadcastDataSource, callId: Int64, accessHash: Int64) -> Signal<EngineCallStreamState?, NoError> {
return self.account.network.request(Api.functions.phone.getGroupCallStreamChannels(call: .inputGroupCall(id: callId, accessHash: accessHash))) return dataSource.download.request(Api.functions.phone.getGroupCallStreamChannels(call: .inputGroupCall(id: callId, accessHash: accessHash)))
|> mapToSignal { result -> Signal<EngineCallStreamState?, MTRpcError> in |> mapToSignal { result -> Signal<EngineCallStreamState?, MTRpcError> in
switch result { switch result {
case let .groupCallStreamChannels(channels): case let .groupCallStreamChannels(channels):

View File

@ -6,7 +6,7 @@ public final class EngineGroupCallDescription {
public let title: String? public let title: String?
public let scheduleTimestamp: Int32? public let scheduleTimestamp: Int32?
public let subscribedToScheduled: Bool public let subscribedToScheduled: Bool
public let isStream: Bool public let isStream: Bool?
public init( public init(
id: Int64, id: Int64,
@ -14,7 +14,7 @@ public final class EngineGroupCallDescription {
title: String?, title: String?,
scheduleTimestamp: Int32?, scheduleTimestamp: Int32?,
subscribedToScheduled: Bool, subscribedToScheduled: Bool,
isStream: Bool isStream: Bool?
) { ) {
self.id = id self.id = id
self.accessHash = accessHash self.accessHash = accessHash

View File

@ -349,7 +349,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
if let inputCall = chatFullCall { if let inputCall = chatFullCall {
switch inputCall { switch inputCall {
case let .inputGroupCall(id, accessHash): case let .inputGroupCall(id, accessHash):
updatedActiveCall = CachedChannelData.ActiveCall(id: id, accessHash: accessHash, title: previous.activeCall?.title, scheduleTimestamp: previous.activeCall?.scheduleTimestamp, subscribedToScheduled: previous.activeCall?.subscribedToScheduled ?? false, isStream: previous.activeCall?.isStream ?? false) updatedActiveCall = CachedChannelData.ActiveCall(id: id, accessHash: accessHash, title: previous.activeCall?.title, scheduleTimestamp: previous.activeCall?.scheduleTimestamp, subscribedToScheduled: previous.activeCall?.subscribedToScheduled ?? false, isStream: previous.activeCall?.isStream)
} }
} }
@ -568,7 +568,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
if let inputCall = inputCall { if let inputCall = inputCall {
switch inputCall { switch inputCall {
case let .inputGroupCall(id, accessHash): case let .inputGroupCall(id, accessHash):
updatedActiveCall = CachedChannelData.ActiveCall(id: id, accessHash: accessHash, title: previous.activeCall?.title, scheduleTimestamp: previous.activeCall?.scheduleTimestamp, subscribedToScheduled: previous.activeCall?.subscribedToScheduled ?? false, isStream: previous.activeCall?.isStream ?? false) updatedActiveCall = CachedChannelData.ActiveCall(id: id, accessHash: accessHash, title: previous.activeCall?.title, scheduleTimestamp: previous.activeCall?.scheduleTimestamp, subscribedToScheduled: previous.activeCall?.subscribedToScheduled ?? false, isStream: previous.activeCall?.isStream)
} }
} }

View File

@ -55,7 +55,28 @@ private final class NetworkBroadcastPartSource: BroadcastPartSource {
func requestTime(completion: @escaping (Int64) -> Void) -> Disposable { func requestTime(completion: @escaping (Int64) -> Void) -> Disposable {
if self.isExternalStream { if self.isExternalStream {
return self.engine.calls.requestStreamState(callId: self.callId, accessHash: self.accessHash).start(next: { result in let dataSource: Signal<AudioBroadcastDataSource?, NoError>
if let dataSourceValue = self.dataSource {
dataSource = .single(dataSourceValue)
} else {
dataSource = self.engine.calls.getAudioBroadcastDataSource(callId: self.callId, accessHash: self.accessHash)
}
let engine = self.engine
let callId = self.callId
let accessHash = self.accessHash
return (dataSource
|> deliverOn(self.queue)
|> mapToSignal { [weak self] dataSource -> Signal<EngineCallStreamState?, NoError> in
if let dataSource = dataSource {
self?.dataSource = dataSource
return engine.calls.requestStreamState(dataSource: dataSource, callId: callId, accessHash: accessHash)
} else {
return .single(nil)
}
}
|> deliverOn(self.queue)).start(next: { result in
if let channel = result?.channels.first { if let channel = result?.channels.first {
completion(channel.latestTimestamp) completion(channel.latestTimestamp)
} else { } else {

@ -1 +1 @@
Subproject commit a424c9919c425454abbf31bc41f8496756e6d8bf Subproject commit d389503ac99d5d6fa16870cc280e83151cba8ccc