mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 11:23:48 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
f0fd82f3c2
@ -1515,7 +1515,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
interaction.dismissInput()
|
||||
}, present: { c, a in
|
||||
interaction.present(c, a)
|
||||
}, transitionNode: { [weak self] messageId, media in
|
||||
}, transitionNode: { messageId, media in
|
||||
var transitionNode: (ASDisplayNode, CGRect, () -> (UIView?, UIView?))?
|
||||
if let strongSelf = self {
|
||||
strongSelf.listNode.forEachItemNode { itemNode in
|
||||
@ -1527,7 +1527,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
}
|
||||
}
|
||||
return transitionNode
|
||||
}, addToTransitionSurface: { [weak self] view in
|
||||
}, addToTransitionSurface: { view in
|
||||
self?.addToTransitionSurface(view: view)
|
||||
}, openUrl: { url in
|
||||
interaction.openUrl(url)
|
||||
@ -1626,7 +1626,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
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
|
||||
interaction.peerContextAction?(message, node, rect, gesture)
|
||||
}, toggleExpandLocalResults: { [weak self] in
|
||||
}, toggleExpandLocalResults: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -1635,7 +1635,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
state.expandLocalSearch = !state.expandLocalSearch
|
||||
return state
|
||||
}
|
||||
}, toggleExpandGlobalResults: { [weak self] in
|
||||
}, toggleExpandGlobalResults: {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -1959,6 +1959,9 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
self.recentDisposable.dispose()
|
||||
self.updatedRecentPeersDisposable.dispose()
|
||||
self.deletedMessagesDisposable?.dispose()
|
||||
if self.key == .downloads {
|
||||
print("downloads")
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
|
18
submodules/Components/ActivityIndicatorComponent/BUILD
Normal file
18
submodules/Components/ActivityIndicatorComponent/BUILD
Normal 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",
|
||||
],
|
||||
)
|
@ -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)
|
||||
}
|
||||
}
|
@ -97,6 +97,7 @@ swift_library(
|
||||
"//third-party/libyuv:LibYuvBinding",
|
||||
"//submodules/ComponentFlow:ComponentFlow",
|
||||
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
|
||||
"//submodules/Components/ActivityIndicatorComponent:ActivityIndicatorComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -489,8 +489,12 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
private(set) var displayUI: Bool = true
|
||||
var dismissOffset: CGFloat = 0.0
|
||||
|
||||
var storedIsLandscape: Bool?
|
||||
|
||||
let isPictureInPictureSupported: Bool
|
||||
|
||||
private var scheduledDismissUITimer: SwiftSignalKit.Timer?
|
||||
|
||||
init(call: PresentationGroupCallImpl) {
|
||||
self.call = call
|
||||
|
||||
@ -551,6 +555,26 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
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) {
|
||||
self.dismissOffset = value
|
||||
if interactive {
|
||||
@ -575,7 +599,8 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
|
||||
return { context in
|
||||
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
||||
if !environment.isVisible {
|
||||
if environment.isVisible {
|
||||
} else {
|
||||
context.state.dismissOffset = 0.0
|
||||
}
|
||||
|
||||
@ -588,10 +613,10 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
let call = context.component.call
|
||||
let controller = environment.controller
|
||||
|
||||
let video = Condition(context.state.hasVideo) {
|
||||
return video.update(
|
||||
let video = video.update(
|
||||
component: MediaStreamVideoComponent(
|
||||
call: context.component.call,
|
||||
hasVideo: context.state.hasVideo,
|
||||
activatePictureInPicture: activatePictureInPicture,
|
||||
bringBackControllerForPictureInPictureDeactivation: { [weak call] completed in
|
||||
guard let call = call else {
|
||||
@ -607,10 +632,9 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
availableSize: context.availableSize,
|
||||
transition: context.transition
|
||||
)
|
||||
}
|
||||
|
||||
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(
|
||||
content: AnyComponent(BundleIconComponent(
|
||||
name: "Media Gallery/PictureInPictureButton",
|
||||
@ -663,7 +687,7 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
topInset: environment.statusBarHeight,
|
||||
sideInset: environment.safeInsets.left,
|
||||
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
|
||||
let _ = call?.leave(terminateIfPossible: false)
|
||||
})
|
||||
@ -676,6 +700,14 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
)
|
||||
|
||||
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>?
|
||||
if let originInfo = context.state.originInfo {
|
||||
@ -754,11 +786,9 @@ public final class MediaStreamComponent: CombinedComponent {
|
||||
})
|
||||
)
|
||||
|
||||
if let video = video {
|
||||
context.add(video
|
||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0 + context.state.dismissOffset))
|
||||
)
|
||||
}
|
||||
|
||||
context.add(navigationBar
|
||||
.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 {
|
||||
private let context: AccountContext
|
||||
public let call: PresentationGroupCall
|
||||
public private(set) var currentOverlayController: VoiceChatOverlayController? = nil
|
||||
public var parentNavigationController: NavigationController?
|
||||
@ -785,13 +816,19 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
|
||||
|
||||
private var initialOrientation: UIInterfaceOrientation?
|
||||
|
||||
private let inviteLinksPromise = Promise<GroupCallInviteLinks?>(nil)
|
||||
|
||||
public init(call: PresentationGroupCall) {
|
||||
self.context = call.accountContext
|
||||
self.call = call
|
||||
|
||||
super.init(context: call.accountContext, component: MediaStreamComponent(call: call as! PresentationGroupCallImpl))
|
||||
|
||||
self.statusBar.statusBarStyle = .White
|
||||
self.view.disablesInteractiveModalDismiss = true
|
||||
|
||||
self.inviteLinksPromise.set(.single(nil)
|
||||
|> then(call.inviteLinks))
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
@ -811,6 +848,10 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
|
||||
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.animateAlpha(from: 0.0, to: 1.0, duration: 0.25, completion: { [weak self] _ in
|
||||
guard let strongSelf = self else {
|
||||
@ -858,6 +899,41 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
|
||||
}
|
||||
|
||||
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
|
||||
var string = string
|
||||
if string.contains("[") && string.contains("]") {
|
||||
@ -869,34 +945,29 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
|
||||
}
|
||||
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
|
||||
if let strongSelf = self {
|
||||
var maybeInviteLinks: GroupCallInviteLinks? = nil
|
||||
var inviteLinks = inviteLinks
|
||||
|
||||
if let peer = peer as? TelegramChannel, let addressName = peer.addressName {
|
||||
maybeInviteLinks = GroupCallInviteLinks(listenerLink: "https://t.me/\(addressName)", speakerLink: nil)
|
||||
if let peer = peer as? TelegramChannel, case .group = peer.info, !peer.flags.contains(.isGigagroup), !(peer.addressName ?? "").isEmpty, let defaultParticipantMuteState = callState.defaultParticipantMuteState {
|
||||
let isMuted = defaultParticipantMuteState == .muted
|
||||
|
||||
if !isMuted {
|
||||
inviteLinks = GroupCallInviteLinks(listenerLink: inviteLinks.listenerLink, speakerLink: nil)
|
||||
}
|
||||
}
|
||||
|
||||
guard let inviteLinks = maybeInviteLinks else {
|
||||
return
|
||||
}
|
||||
|
||||
let presentationData = strongSelf.call.accountContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
var segmentedValues: [ShareControllerSegmentedValue]?
|
||||
if let speakerLink = inviteLinks.speakerLink {
|
||||
segmentedValues = [ShareControllerSegmentedValue(title: presentationData.strings.VoiceChat_InviteLink_Speaker, subject: .url(speakerLink), actionTitle: presentationData.strings.VoiceChat_InviteLink_CopySpeakerLink, formatSendTitle: { count in
|
||||
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)
|
||||
segmentedValues = nil
|
||||
let shareController = ShareController(context: strongSelf.context, subject: .url(inviteLinks.listenerLink), segmentedValues: segmentedValues, forceTheme: defaultDarkPresentationTheme, forcedActionTitle: presentationData.strings.VoiceChat_CopyInviteLink)
|
||||
shareController.completed = { [weak self] peerIds in
|
||||
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] = []
|
||||
for peerId in peerIds {
|
||||
if let peer = transaction.getPeer(peerId) {
|
||||
@ -904,19 +975,19 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
|
||||
}
|
||||
}
|
||||
return peers
|
||||
} |> deliverOnMainQueue).start(next: { peers in
|
||||
} |> deliverOnMainQueue).start(next: { [weak self] peers in
|
||||
if let strongSelf = self {
|
||||
let presentationData = strongSelf.call.accountContext.sharedContext.currentPresentationData.with { $0 }
|
||||
let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
let text: String
|
||||
var isSavedMessages = false
|
||||
if peers.count == 1, let peer = peers.first {
|
||||
isSavedMessages = peer.id == strongSelf.call.accountContext.account.peerId
|
||||
let peerName = peer.id == strongSelf.call.accountContext.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
isSavedMessages = peer.id == strongSelf.context.account.peerId
|
||||
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
|
||||
} 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 secondPeerName = secondPeer.id == strongSelf.call.accountContext.account.peerId ? presentationData.strings.DialogList_SavedMessages : EnginePeer(secondPeer).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.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
|
||||
} else if let peer = peers.first {
|
||||
let peerName = EnginePeer(peer).displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
|
||||
@ -932,7 +1003,7 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
|
||||
}
|
||||
shareController.actionCompleted = {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,19 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import ComponentFlow
|
||||
import ActivityIndicatorComponent
|
||||
import AccountContext
|
||||
import AVKit
|
||||
|
||||
final class MediaStreamVideoComponent: Component {
|
||||
let call: PresentationGroupCallImpl
|
||||
let hasVideo: Bool
|
||||
let activatePictureInPicture: ActionSlot<Action<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.hasVideo = hasVideo
|
||||
self.activatePictureInPicture = activatePictureInPicture
|
||||
self.bringBackControllerForPictureInPictureDeactivation = bringBackControllerForPictureInPictureDeactivation
|
||||
}
|
||||
@ -19,6 +22,9 @@ final class MediaStreamVideoComponent: Component {
|
||||
if lhs.call !== rhs.call {
|
||||
return false
|
||||
}
|
||||
if lhs.hasVideo != rhs.hasVideo {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -33,19 +39,24 @@ final class MediaStreamVideoComponent: Component {
|
||||
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 var videoView: VideoRenderingView?
|
||||
private let blurTintView: UIView
|
||||
private var videoBlurView: VideoRenderingView?
|
||||
private var activityIndicatorView: ComponentHostView<Empty>?
|
||||
|
||||
private var pictureInPictureController: AVPictureInPictureController?
|
||||
|
||||
private var component: MediaStreamVideoComponent?
|
||||
private var hadVideo: Bool = false
|
||||
|
||||
override init(frame: CGRect) {
|
||||
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)
|
||||
|
||||
@ -59,8 +70,19 @@ final class MediaStreamVideoComponent: Component {
|
||||
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 {
|
||||
if self.videoView == nil {
|
||||
if component.hasVideo, self.videoView == nil {
|
||||
if let input = component.call.video(endpointId: "unified") {
|
||||
if let videoBlurView = self.videoRenderingContext.makeView(input: input, blur: true) {
|
||||
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 {
|
||||
let pictureInPictureController = AVPictureInPictureController(contentSource: AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: sampleBufferVideoView.sampleBufferLayer, playbackDelegate: self))
|
||||
self.pictureInPictureController = pictureInPictureController
|
||||
|
||||
pictureInPictureController.delegate = self
|
||||
pictureInPictureController.canStartPictureInPictureAutomaticallyFromInline = true
|
||||
pictureInPictureController.requiresLinearPlayback = true
|
||||
pictureInPictureController.delegate = self
|
||||
|
||||
self.pictureInPictureController = pictureInPictureController
|
||||
}
|
||||
|
||||
videoView.setOnOrientationUpdated { [weak state] _, _ in
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
component.activatePictureInPicture.connect { [weak self] completion in
|
||||
|
@ -78,12 +78,12 @@ open class ViewControllerComponentContainer: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private final class Node: ViewControllerTracingNode {
|
||||
final class Node: ViewControllerTracingNode {
|
||||
private var presentationData: PresentationData
|
||||
private weak var controller: ViewControllerComponentContainer?
|
||||
|
||||
private let component: AnyComponent<ViewControllerComponentContainer.Environment>
|
||||
private let hostView: ComponentHostView<ViewControllerComponentContainer.Environment>
|
||||
let hostView: ComponentHostView<ViewControllerComponentContainer.Environment>
|
||||
|
||||
private var currentIsVisible: Bool = false
|
||||
private var currentLayout: ContainerViewLayout?
|
||||
@ -137,7 +137,7 @@ open class ViewControllerComponentContainer: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private var node: Node {
|
||||
var node: Node {
|
||||
return self.displayNode as! Node
|
||||
}
|
||||
|
||||
|
@ -379,6 +379,8 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
||||
let membersText: String
|
||||
if summaryState.participantCount == 0 {
|
||||
membersText = strongSelf.strings.VoiceChat_Panel_TapToJoin
|
||||
} else if let info = summaryState.info, info.isStream {
|
||||
membersText = strongSelf.strings.LiveStream_ViewerCount(Int32(summaryState.participantCount))
|
||||
} else {
|
||||
membersText = strongSelf.strings.VoiceChat_Panel_Members(Int32(summaryState.participantCount))
|
||||
}
|
||||
|
@ -687,7 +687,8 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
peerId: peerId,
|
||||
isChannel: isChannel,
|
||||
invite: nil,
|
||||
joinAsPeerId: nil
|
||||
joinAsPeerId: nil,
|
||||
isStream: false
|
||||
)
|
||||
strongSelf.updateCurrentGroupCall(call)
|
||||
strongSelf.currentGroupCallPromise.set(.single(call))
|
||||
@ -849,7 +850,8 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
peerId: peerId,
|
||||
isChannel: isChannel,
|
||||
invite: invite,
|
||||
joinAsPeerId: joinAsPeerId
|
||||
joinAsPeerId: joinAsPeerId,
|
||||
isStream: initialCall.isStream ?? false
|
||||
)
|
||||
strongSelf.updateCurrentGroupCall(call)
|
||||
strongSelf.currentGroupCallPromise.set(.single(call))
|
||||
|
@ -86,13 +86,14 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
|
||||
var disposable: Disposable?
|
||||
public var participantsContext: GroupCallParticipantsContext?
|
||||
|
||||
private let panelDataPromise = Promise<GroupCallPanelData>()
|
||||
public var panelData: Signal<GroupCallPanelData, NoError> {
|
||||
private let panelDataPromise = Promise<GroupCallPanelData?>()
|
||||
public var panelData: Signal<GroupCallPanelData?, NoError> {
|
||||
return self.panelDataPromise.get()
|
||||
}
|
||||
|
||||
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,
|
||||
isChannel: isChannel,
|
||||
info: GroupCallInfo(
|
||||
@ -114,7 +115,7 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
|
||||
participantCount: 0,
|
||||
activeSpeakers: Set(),
|
||||
groupCall: nil
|
||||
)))
|
||||
)))*/
|
||||
|
||||
let state = engine.calls.getGroupCallParticipants(callId: call.id, accessHash: call.accessHash, offset: "", ssrcs: [], limit: 100, sortAscending: nil)
|
||||
|> map(Optional.init)
|
||||
@ -161,7 +162,7 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
|
||||
return GroupCallPanelData(
|
||||
peerId: peerId,
|
||||
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,
|
||||
participantCount: state.totalCount,
|
||||
activeSpeakers: activeSpeakers,
|
||||
@ -229,7 +230,14 @@ public final class AccountGroupCallContextCacheImpl: AccountGroupCallContextCach
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@ -630,7 +638,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
private var screencastAudioDataDisposable: Disposable?
|
||||
private var screencastStateDisposable: Disposable?
|
||||
|
||||
public var isStream = false
|
||||
public let isStream: Bool
|
||||
|
||||
init(
|
||||
accountContext: AccountContext,
|
||||
@ -642,7 +650,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
peerId: PeerId,
|
||||
isChannel: Bool,
|
||||
invite: String?,
|
||||
joinAsPeerId: PeerId?
|
||||
joinAsPeerId: PeerId?,
|
||||
isStream: Bool
|
||||
) {
|
||||
self.account = accountContext.account
|
||||
self.accountContext = accountContext
|
||||
@ -659,10 +668,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
self.schedulePending = initialCall == 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.statePromise = ValuePromise(self.stateValue)
|
||||
|
||||
@ -671,6 +676,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
self.isVideoEnabled = true
|
||||
self.hasVideo = false
|
||||
self.hasScreencast = false
|
||||
self.isStream = isStream
|
||||
|
||||
var didReceiveAudioOutputs = false
|
||||
|
||||
@ -1228,6 +1234,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
totalCount: 0,
|
||||
isVideoEnabled: callInfo.isVideoEnabled,
|
||||
unmutedVideoLimit: callInfo.unmutedVideoLimit,
|
||||
isStream: callInfo.isStream,
|
||||
version: 0
|
||||
),
|
||||
previousServiceState: nil
|
||||
@ -1677,7 +1684,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
strongSelf.processMyAudioLevel(level: mappedLevel, hasVoice: myLevelHasVoice)
|
||||
strongSelf.isSpeakingPromise.set(orignalMyLevelHasVoice)
|
||||
|
||||
if !missingSsrcs.isEmpty {
|
||||
if !missingSsrcs.isEmpty && !strongSelf.isStream {
|
||||
strongSelf.participantsContext?.ensureHaveParticipants(ssrcs: missingSsrcs)
|
||||
}
|
||||
}))
|
||||
@ -2228,6 +2235,14 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
|
||||
private func beginTone(tone: PresentationCallTone) {
|
||||
if self.isStream {
|
||||
switch tone {
|
||||
case .groupJoined, .groupLeft:
|
||||
return
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
var completed: (() -> Void)?
|
||||
let toneRenderer = PresentationCallToneRenderer(tone: tone, completed: {
|
||||
completed?()
|
||||
@ -2995,7 +3010,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
|
||||
if let value = value {
|
||||
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)
|
||||
} else {
|
||||
|
@ -161,7 +161,7 @@ public final class CachedChannelData: CachedPeerData {
|
||||
public var title: String?
|
||||
public var scheduleTimestamp: Int32?
|
||||
public var subscribedToScheduled: Bool
|
||||
public var isStream: Bool
|
||||
public var isStream: Bool?
|
||||
|
||||
public init(
|
||||
id: Int64,
|
||||
@ -169,7 +169,7 @@ public final class CachedChannelData: CachedPeerData {
|
||||
title: String?,
|
||||
scheduleTimestamp: Int32?,
|
||||
subscribedToScheduled: Bool,
|
||||
isStream: Bool
|
||||
isStream: Bool?
|
||||
) {
|
||||
self.id = id
|
||||
self.accessHash = accessHash
|
||||
@ -185,7 +185,7 @@ public final class CachedChannelData: CachedPeerData {
|
||||
self.title = decoder.decodeOptionalStringForKey("title")
|
||||
self.scheduleTimestamp = decoder.decodeOptionalInt32ForKey("scheduleTimestamp")
|
||||
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) {
|
||||
@ -202,7 +202,11 @@ public final class CachedChannelData: CachedPeerData {
|
||||
encoder.encodeNil(forKey: "scheduleTimestamp")
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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> {
|
||||
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)
|
||||
|> mapError { _ -> GetGroupCallParticipantsError in
|
||||
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 {
|
||||
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(
|
||||
@ -369,7 +369,7 @@ func _internal_getGroupCallParticipants(account: Account, callId: Int64, accessH
|
||||
let version: Int32
|
||||
let nextParticipantsFetchOffset: String?
|
||||
|
||||
let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, isVideoEnabled, unmutedVideoLimit) = sortAscendingAndScheduleTimestamp
|
||||
let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, isVideoEnabled, unmutedVideoLimit, isStream) = sortAscendingAndScheduleTimestamp
|
||||
|
||||
switch result {
|
||||
case let .groupParticipants(count, participants, nextOffset, chats, users, apiVersion):
|
||||
@ -423,6 +423,7 @@ func _internal_getGroupCallParticipants(account: Account, callId: Int64, accessH
|
||||
totalCount: totalCount,
|
||||
isVideoEnabled: isVideoEnabled,
|
||||
unmutedVideoLimit: unmutedVideoLimit,
|
||||
isStream: isStream,
|
||||
version: version
|
||||
)
|
||||
}
|
||||
@ -1047,6 +1048,7 @@ public final class GroupCallParticipantsContext {
|
||||
public var totalCount: Int
|
||||
public var isVideoEnabled: Bool
|
||||
public var unmutedVideoLimit: Int
|
||||
public var isStream: Bool
|
||||
public var version: Int32
|
||||
|
||||
public mutating func mergeActivity(from other: State, myPeerId: PeerId?, previousMyPeerId: PeerId?, mergeActivityTimestamps: Bool) {
|
||||
@ -1081,6 +1083,7 @@ public final class GroupCallParticipantsContext {
|
||||
totalCount: Int,
|
||||
isVideoEnabled: Bool,
|
||||
unmutedVideoLimit: Int,
|
||||
isStream: Bool,
|
||||
version: Int32
|
||||
) {
|
||||
self.participants = participants
|
||||
@ -1096,6 +1099,7 @@ public final class GroupCallParticipantsContext {
|
||||
self.totalCount = totalCount
|
||||
self.isVideoEnabled = isVideoEnabled
|
||||
self.unmutedVideoLimit = unmutedVideoLimit
|
||||
self.isStream = isStream
|
||||
self.version = version
|
||||
}
|
||||
}
|
||||
@ -1398,6 +1402,7 @@ public final class GroupCallParticipantsContext {
|
||||
totalCount: strongSelf.stateValue.state.totalCount,
|
||||
isVideoEnabled: strongSelf.stateValue.state.isVideoEnabled,
|
||||
unmutedVideoLimit: strongSelf.stateValue.state.unmutedVideoLimit,
|
||||
isStream: strongSelf.stateValue.state.isStream,
|
||||
version: strongSelf.stateValue.state.version
|
||||
),
|
||||
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 {
|
||||
let value = self.serviceState.nextActivityRank
|
||||
self.serviceState.nextActivityRank += 1
|
||||
@ -1537,6 +1550,7 @@ public final class GroupCallParticipantsContext {
|
||||
totalCount: strongSelf.stateValue.state.totalCount,
|
||||
isVideoEnabled: strongSelf.stateValue.state.isVideoEnabled,
|
||||
unmutedVideoLimit: strongSelf.stateValue.state.unmutedVideoLimit,
|
||||
isStream: strongSelf.stateValue.state.isStream,
|
||||
version: strongSelf.stateValue.state.version
|
||||
),
|
||||
overlayState: strongSelf.stateValue.overlayState
|
||||
@ -1758,6 +1772,7 @@ public final class GroupCallParticipantsContext {
|
||||
let scheduleTimestamp = strongSelf.stateValue.state.scheduleTimestamp
|
||||
let subscribedToScheduled = strongSelf.stateValue.state.subscribedToScheduled
|
||||
let isVideoEnabled = strongSelf.stateValue.state.isVideoEnabled
|
||||
let isStream = strongSelf.stateValue.state.isStream
|
||||
let unmutedVideoLimit = strongSelf.stateValue.state.unmutedVideoLimit
|
||||
|
||||
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,
|
||||
isVideoEnabled: isVideoEnabled,
|
||||
unmutedVideoLimit: unmutedVideoLimit,
|
||||
isStream: isStream,
|
||||
version: update.version
|
||||
),
|
||||
overlayState: updatedOverlayState
|
||||
@ -2398,7 +2414,7 @@ private func mergeAndSortParticipants(current currentParticipants: [GroupCallPar
|
||||
}
|
||||
|
||||
public final class AudioBroadcastDataSource {
|
||||
fileprivate let download: Download
|
||||
let download: Download
|
||||
|
||||
fileprivate init(download: Download) {
|
||||
self.download = download
|
||||
|
@ -132,8 +132,8 @@ public extension TelegramEngine {
|
||||
|> take(1)
|
||||
}
|
||||
|
||||
public func requestStreamState(callId: Int64, accessHash: Int64) -> Signal<EngineCallStreamState?, NoError> {
|
||||
return self.account.network.request(Api.functions.phone.getGroupCallStreamChannels(call: .inputGroupCall(id: callId, accessHash: accessHash)))
|
||||
public func requestStreamState(dataSource: AudioBroadcastDataSource, callId: Int64, accessHash: Int64) -> Signal<EngineCallStreamState?, NoError> {
|
||||
return dataSource.download.request(Api.functions.phone.getGroupCallStreamChannels(call: .inputGroupCall(id: callId, accessHash: accessHash)))
|
||||
|> mapToSignal { result -> Signal<EngineCallStreamState?, MTRpcError> in
|
||||
switch result {
|
||||
case let .groupCallStreamChannels(channels):
|
||||
|
@ -6,7 +6,7 @@ public final class EngineGroupCallDescription {
|
||||
public let title: String?
|
||||
public let scheduleTimestamp: Int32?
|
||||
public let subscribedToScheduled: Bool
|
||||
public let isStream: Bool
|
||||
public let isStream: Bool?
|
||||
|
||||
public init(
|
||||
id: Int64,
|
||||
@ -14,7 +14,7 @@ public final class EngineGroupCallDescription {
|
||||
title: String?,
|
||||
scheduleTimestamp: Int32?,
|
||||
subscribedToScheduled: Bool,
|
||||
isStream: Bool
|
||||
isStream: Bool?
|
||||
) {
|
||||
self.id = id
|
||||
self.accessHash = accessHash
|
||||
|
@ -349,7 +349,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
|
||||
if let inputCall = chatFullCall {
|
||||
switch inputCall {
|
||||
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 {
|
||||
switch inputCall {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,28 @@ private final class NetworkBroadcastPartSource: BroadcastPartSource {
|
||||
|
||||
func requestTime(completion: @escaping (Int64) -> Void) -> Disposable {
|
||||
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 {
|
||||
completion(channel.latestTimestamp)
|
||||
} else {
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit a424c9919c425454abbf31bc41f8496756e6d8bf
|
||||
Subproject commit d389503ac99d5d6fa16870cc280e83151cba8ccc
|
Loading…
x
Reference in New Issue
Block a user