diff --git a/submodules/AccountContext/Sources/PresentationCallManager.swift b/submodules/AccountContext/Sources/PresentationCallManager.swift index 83f88f17c8..451c92824d 100644 --- a/submodules/AccountContext/Sources/PresentationCallManager.swift +++ b/submodules/AccountContext/Sources/PresentationCallManager.swift @@ -402,6 +402,8 @@ public protocol PresentationGroupCall: AnyObject { var schedulePending: Bool { get } + var isStream: Bool { get } + var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> { get } var isSpeaking: Signal { get } diff --git a/submodules/ComponentFlow/Source/Base/Component.swift b/submodules/ComponentFlow/Source/Base/Component.swift index 72a660ced3..db68fab951 100644 --- a/submodules/ComponentFlow/Source/Base/Component.swift +++ b/submodules/ComponentFlow/Source/Base/Component.swift @@ -84,10 +84,13 @@ extension UIView { } } -public class ComponentState { +open class ComponentState { var _updated: ((Transition) -> Void)? var isUpdated: Bool = false + public init() { + } + public final func updated(transition: Transition = .immediate) { self.isUpdated = true self._updated?(transition) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 2fc4aafa3d..b93c2512a4 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -542,6 +542,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-6249322] = { return Api.InputStickerSetItem.parse_inputStickerSetItem($0) } dict[-1728664459] = { return Api.help.PromoData.parse_promoDataEmpty($0) } dict[-1942390465] = { return Api.help.PromoData.parse_promoData($0) } + dict[767505458] = { return Api.phone.GroupCallStreamRtmpUrl.parse_groupCallStreamRtmpUrl($0) } dict[1753266509] = { return Api.messages.PeerSettings.parse_peerSettings($0) } dict[-1613493288] = { return Api.NotifyPeer.parse_notifyPeer($0) } dict[-1261946036] = { return Api.NotifyPeer.parse_notifyUsers($0) } @@ -1336,6 +1337,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.help.PromoData: _1.serialize(buffer, boxed) + case let _1 as Api.phone.GroupCallStreamRtmpUrl: + _1.serialize(buffer, boxed) case let _1 as Api.messages.PeerSettings: _1.serialize(buffer, boxed) case let _1 as Api.NotifyPeer: diff --git a/submodules/TelegramApi/Sources/Api4.swift b/submodules/TelegramApi/Sources/Api4.swift index 6550b65bc2..e9d42a2dd8 100644 --- a/submodules/TelegramApi/Sources/Api4.swift +++ b/submodules/TelegramApi/Sources/Api4.swift @@ -1821,6 +1821,44 @@ public struct phone { } } + } + public enum GroupCallStreamRtmpUrl: TypeConstructorDescription { + case groupCallStreamRtmpUrl(url: String, key: String) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .groupCallStreamRtmpUrl(let url, let key): + if boxed { + buffer.appendInt32(767505458) + } + serializeString(url, buffer: buffer, boxed: false) + serializeString(key, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .groupCallStreamRtmpUrl(let url, let key): + return ("groupCallStreamRtmpUrl", [("url", url), ("key", key)]) + } + } + + public static func parse_groupCallStreamRtmpUrl(_ reader: BufferReader) -> GroupCallStreamRtmpUrl? { + var _1: String? + _1 = parseString(reader) + var _2: String? + _2 = parseString(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.phone.GroupCallStreamRtmpUrl.groupCallStreamRtmpUrl(url: _1!, key: _2!) + } + else { + return nil + } + } + } public enum GroupCall: TypeConstructorDescription { case groupCall(call: Api.GroupCall, participants: [Api.GroupCallParticipant], participantsNextOffset: String, chats: [Api.Chat], users: [Api.User]) @@ -8687,6 +8725,21 @@ public extension Api { return result }) } + + public static func getGroupCallStreamRtmpUrl(peer: Api.InputPeer, revoke: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-558650433) + peer.serialize(buffer, true) + revoke.serialize(buffer, true) + return (FunctionDescription(name: "phone.getGroupCallStreamRtmpUrl", parameters: [("peer", peer), ("revoke", revoke)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCallStreamRtmpUrl? in + let reader = BufferReader(buffer) + var result: Api.phone.GroupCallStreamRtmpUrl? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.GroupCallStreamRtmpUrl + } + return result + }) + } } } } diff --git a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift index 3df2f22d91..41a9bb797e 100644 --- a/submodules/TelegramBaseController/Sources/TelegramBaseController.swift +++ b/submodules/TelegramBaseController/Sources/TelegramBaseController.swift @@ -420,7 +420,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder { strongSelf.joinGroupCall( peerId: groupCallPanelData.peerId, invite: nil, - activeCall: EngineGroupCallDescription(id: groupCallPanelData.info.id, accessHash: groupCallPanelData.info.accessHash, title: groupCallPanelData.info.title, scheduleTimestamp: groupCallPanelData.info.scheduleTimestamp, subscribedToScheduled: groupCallPanelData.info.subscribedToScheduled) + activeCall: EngineGroupCallDescription(id: groupCallPanelData.info.id, accessHash: groupCallPanelData.info.accessHash, title: groupCallPanelData.info.title, scheduleTimestamp: groupCallPanelData.info.scheduleTimestamp, subscribedToScheduled: groupCallPanelData.info.subscribedToScheduled, isStream: groupCallPanelData.info.isStream) ) }) self.navigationBar?.additionalContentNode.addSubnode(groupCallAccessoryPanel) diff --git a/submodules/TelegramCallsUI/BUILD b/submodules/TelegramCallsUI/BUILD index 0865e3101d..b5877ec8e6 100644 --- a/submodules/TelegramCallsUI/BUILD +++ b/submodules/TelegramCallsUI/BUILD @@ -95,6 +95,7 @@ swift_library( "//submodules/Markdown:Markdown", "//submodules/ChatTitleActivityNode:ChatTitleActivityNode", "//third-party/libyuv:LibYuvBinding", + "//submodules/ComponentFlow:ComponentFlow", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift new file mode 100644 index 0000000000..a8c007a790 --- /dev/null +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamComponent.swift @@ -0,0 +1,253 @@ +import Foundation +import UIKit +import ComponentFlow +import Display +import AccountContext +import SwiftSignalKit + +private final class NavigationBarComponent: CombinedComponent { + let leftItem: AnyComponent? + let rightItem: AnyComponent? + let centerItem: AnyComponent? + + init( + leftItem: AnyComponent?, + rightItem: AnyComponent?, + centerItem: AnyComponent? + ) { + self.leftItem = leftItem + self.rightItem = rightItem + self.centerItem = centerItem + } + + static func ==(lhs: NavigationBarComponent, rhs: NavigationBarComponent) -> Bool { + return true + } + + static var body: Body { + let leftItem = Child(environment: Empty.self) + let rightItem = Child(environment: Empty.self) + let centerItem = Child(environment: Empty.self) + + return { context in + var availableWidth = context.availableSize.width + let sideInset: CGFloat = 16.0 + + let leftItem = context.component.leftItem.flatMap { leftItemComponent in + return leftItem.update( + component: leftItemComponent, + availableSize: CGSize(width: availableWidth, height: context.availableSize.height), + transition: context.transition + ) + } + if let leftItem = leftItem { + availableWidth -= leftItem.size.width + } + + let rightItem = context.component.rightItem.flatMap { rightItemComponent in + return rightItem.update( + component: rightItemComponent, + availableSize: CGSize(width: availableWidth, height: context.availableSize.height), + transition: context.transition + ) + } + if let rightItem = rightItem { + availableWidth -= rightItem.size.width + } + + let centerItem = context.component.centerItem.flatMap { centerItemComponent in + return centerItem.update( + component: centerItemComponent, + availableSize: CGSize(width: availableWidth, height: context.availableSize.height), + transition: context.transition + ) + } + if let centerItem = centerItem { + availableWidth -= centerItem.size.width + } + + var centerLeftInset = sideInset + if let leftItem = leftItem { + context.add(leftItem + .position(CGPoint(x: sideInset + leftItem.size.width / 2.0, y: context.availableSize.height / 2.0)) + ) + centerLeftInset += leftItem.size.width + 4.0 + } + + var centerRightInset = sideInset + if let rightItem = rightItem { + context.add(rightItem + .position(CGPoint(x: context.availableSize.width - sideInset - rightItem.size.width / 2.0, y: context.availableSize.height / 2.0)) + ) + centerRightInset += rightItem.size.width + 4.0 + } + + let maxCenterInset = max(centerLeftInset, centerRightInset) + if let centerItem = centerItem { + context.add(centerItem + .position(CGPoint(x: maxCenterInset + (context.availableSize.width - maxCenterInset - maxCenterInset) / 2.0, y: context.availableSize.height / 2.0)) + ) + } + + return context.availableSize + } + } +} + +public final class MediaStreamComponent: CombinedComponent { + public typealias EnvironmentType = ViewControllerComponentContainer.Environment + + public let call: PresentationGroupCallImpl + + public init(call: PresentationGroupCallImpl) { + self.call = call + } + + public static func ==(lhs: MediaStreamComponent, rhs: MediaStreamComponent) -> Bool { + if lhs.call !== rhs.call { + return false + } + + return true + } + + public final class State: ComponentState { + private let call: PresentationGroupCallImpl + + private(set) var hasVideo: Bool = false + private var stateDisposable: Disposable? + + init(call: PresentationGroupCallImpl) { + self.call = call + + super.init() + + self.stateDisposable = (call.state + |> map { state -> Bool in + switch state.networkState { + case .connected: + return true + default: + return false + } + } + |> filter { $0 } + |> take(1)).start(next: { [weak self] _ in + guard let strongSelf = self else { + return + } + strongSelf.hasVideo = true + strongSelf.updated(transition: .immediate) + }) + + let _ = call.accountContext.engine.calls.getGroupCallStreamCredentials(peerId: call.peerId, revokePreviousCredentials: false).start() + } + + deinit { + self.stateDisposable?.dispose() + } + } + + public func makeState() -> State { + return State(call: self.call) + } + + public static var body: Body { + let background = Child(Rectangle.self) + let video = Child(MediaStreamVideoComponent.self) + let navigationBar = Child(NavigationBarComponent.self) + + return { context in + let environment = context.environment[ViewControllerComponentContainer.Environment.self].value + + let background = background.update( + component: Rectangle(color: .black), + availableSize: context.availableSize, + transition: context.transition + ) + + let video = Condition(context.state.hasVideo) { + return video.update( + component: MediaStreamVideoComponent( + call: context.component.call + ), + availableSize: CGSize(width: context.availableSize.width, height: floor(context.availableSize.width * 9.0 / 16.0)), + transition: context.transition + ) + } + + let call = context.component.call + let navigationBar = navigationBar.update( + component: NavigationBarComponent( + leftItem: AnyComponent(Button( + content: AnyComponent(Text(text: "Leave", font: Font.regular(17.0), color: .white)), + insets: UIEdgeInsets(), + action: { [weak call] in + let _ = call?.leave(terminateIfPossible: false) + }) + ), + rightItem: nil, + centerItem: AnyComponent(Text(text: "Live Stream", font: Font.semibold(17.0), color: .white)) + ), + availableSize: CGSize(width: context.availableSize.width, height: 44.0), + transition: context.transition + ) + + context.add(background + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) + ) + + if let video = video { + context.add(video + .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))) + } + + context.add(navigationBar + .position(CGPoint(x: context.availableSize.width / 2.0, y: environment.statusBarHeight + navigationBar.size.height / 2.0)) + ) + + return context.availableSize + } + } +} + +public final class MediaStreamComponentController: ViewControllerComponentContainer, VoiceChatController { + public let call: PresentationGroupCall + public private(set) var currentOverlayController: VoiceChatOverlayController? = nil + public var parentNavigationController: NavigationController? + + public var onViewDidAppear: (() -> Void)? + public var onViewDidDisappear: (() -> Void)? + + public init(call: PresentationGroupCall) { + self.call = call + + super.init(MediaStreamComponent(call: call as! PresentationGroupCallImpl)) + + self.statusBar.statusBarStyle = .White + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + DispatchQueue.main.async { + self.onViewDidAppear?() + } + } + + override public func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + DispatchQueue.main.async { + self.onViewDidDisappear?() + } + } + + public func dismiss(closing: Bool, manual: Bool) { + self.dismiss(animated: true, completion: nil) + } +} diff --git a/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift b/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift new file mode 100644 index 0000000000..a3cecd7d0a --- /dev/null +++ b/submodules/TelegramCallsUI/Sources/Components/MediaStreamVideoComponent.swift @@ -0,0 +1,51 @@ +import Foundation +import UIKit +import ComponentFlow +import AccountContext + +final class MediaStreamVideoComponent: Component { + let call: PresentationGroupCallImpl + + init(call: PresentationGroupCallImpl) { + self.call = call + } + + public static func ==(lhs: MediaStreamVideoComponent, rhs: MediaStreamVideoComponent) -> Bool { + if lhs.call !== rhs.call { + return false + } + + return true + } + + public final class View: UIView { + private let videoRenderingContext = VideoRenderingContext() + private var videoView: VideoRenderingView? + + func update(component: MediaStreamVideoComponent, availableSize: CGSize, transition: Transition) -> CGSize { + if self.videoView == nil { + if let input = component.call.video(endpointId: "unified") { + if let videoView = self.videoRenderingContext.makeView(input: input, blur: false) { + self.videoView = videoView + self.addSubview(videoView) + } + } + } + + if let videoView = self.videoView { + videoView.updateIsEnabled(true) + videoView.frame = CGRect(origin: CGPoint(), size: availableSize) + } + + return availableSize + } + } + + public func makeView() -> View { + return View() + } + + public func update(view: View, availableSize: CGSize, environment: Environment, transition: Transition) -> CGSize { + return view.update(component: self, availableSize: availableSize, transition: transition) + } +} diff --git a/submodules/TelegramCallsUI/Sources/Components/ViewControllerComponent.swift b/submodules/TelegramCallsUI/Sources/Components/ViewControllerComponent.swift new file mode 100644 index 0000000000..fe4d4e34a5 --- /dev/null +++ b/submodules/TelegramCallsUI/Sources/Components/ViewControllerComponent.swift @@ -0,0 +1,121 @@ +import Foundation +import UIKit +import ComponentFlow +import Display +import SwiftSignalKit + +public extension Transition.Animation.Curve { + init(_ curve: ContainedViewLayoutTransitionCurve) { + switch curve { + case .linear: + self = .easeInOut + case .easeInOut: + self = .easeInOut + case .custom: + self = .spring + case .customSpring: + self = .spring + case .spring: + self = .spring + } + } +} + +public extension Transition { + init(_ transition: ContainedViewLayoutTransition) { + switch transition { + case .immediate: + self.init(animation: .none) + case let .animated(duration, curve): + self.init(animation: .curve(duration: duration, curve: Transition.Animation.Curve(curve))) + } + } +} + +open class ViewControllerComponentContainer: ViewController { + public final class Environment: Equatable { + public let statusBarHeight: CGFloat + public let safeInsets: UIEdgeInsets + + public init( + statusBarHeight: CGFloat, + safeInsets: UIEdgeInsets + ) { + self.statusBarHeight = statusBarHeight + self.safeInsets = safeInsets + } + + public static func ==(lhs: Environment, rhs: Environment) -> Bool { + if lhs.statusBarHeight != rhs.statusBarHeight { + return false + } + if lhs.safeInsets != rhs.safeInsets { + return false + } + + return true + } + } + + private final class Node: ViewControllerTracingNode { + private weak var controller: ViewControllerComponentContainer? + + private let component: AnyComponent + private let hostView: ComponentHostView + + init(controller: ViewControllerComponentContainer, component: AnyComponent) { + self.controller = controller + + self.component = component + self.hostView = ComponentHostView() + + super.init() + + self.view.addSubview(self.hostView) + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + let environment = ViewControllerComponentContainer.Environment( + statusBarHeight: layout.statusBarHeight ?? 0.0, + safeInsets: layout.intrinsicInsets + ) + let _ = self.hostView.update( + transition: Transition(transition), + component: self.component, + environment: { + environment + }, + containerSize: layout.size + ) + transition.updateFrame(view: self.hostView, frame: CGRect(origin: CGPoint(), size: layout.size)) + } + } + + private var node: Node { + return self.displayNode as! Node + } + + private let component: AnyComponent + + public init(_ component: C) where C.EnvironmentType == ViewControllerComponentContainer.Environment { + self.component = AnyComponent(component) + + super.init(navigationBarPresentationData: nil) + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override open func loadDisplayNode() { + self.displayNode = Node(controller: self, component: self.component) + + self.displayNodeDidLoad() + } + + override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.node.containerLayoutUpdated(layout, transition: transition) + } +} diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 523f7f1151..ebddea535c 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -107,7 +107,8 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext { sortAscending: true, defaultParticipantsAreMuted: nil, isVideoEnabled: false, - unmutedVideoLimit: 0 + unmutedVideoLimit: 0, + isStream: call.isStream ), topParticipants: [], participantCount: 0, @@ -160,7 +161,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), + 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), topParticipants: topParticipants, participantCount: state.totalCount, activeSpeakers: activeSpeakers, @@ -629,6 +630,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { private var screencastAudioDataDisposable: Disposable? private var screencastStateDisposable: Disposable? + public var isStream = false + init( accountContext: AccountContext, audioSession: ManagedAudioSession, @@ -656,6 +659,10 @@ 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) @@ -837,7 +844,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { }) if let initialCall = initialCall, let temporaryParticipantsContext = (self.accountContext.cachedGroupCallContexts as? AccountGroupCallContextCacheImpl)?.impl.syncWith({ impl in - impl.get(account: accountContext.account, engine: accountContext.engine, peerId: peerId, isChannel: isChannel, call: EngineGroupCallDescription(id: initialCall.id, accessHash: initialCall.accessHash, title: initialCall.title, scheduleTimestamp: initialCall.scheduleTimestamp, subscribedToScheduled: initialCall.subscribedToScheduled)) + impl.get(account: accountContext.account, engine: accountContext.engine, peerId: peerId, isChannel: isChannel, call: EngineGroupCallDescription(id: initialCall.id, accessHash: initialCall.accessHash, title: initialCall.title, scheduleTimestamp: initialCall.scheduleTimestamp, subscribedToScheduled: initialCall.subscribedToScheduled, isStream: initialCall.isStream)) }) { self.switchToTemporaryParticipantsContext(sourceContext: temporaryParticipantsContext.context.participantsContext, oldMyPeerId: self.joinAsPeerId) } else { @@ -1312,7 +1319,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { strongSelf.stateValue.subscribedToScheduled = state.subscribedToScheduled strongSelf.stateValue.scheduleTimestamp = strongSelf.isScheduledStarted ? nil : state.scheduleTimestamp if state.scheduleTimestamp == nil && !strongSelf.isScheduledStarted { - strongSelf.updateSessionState(internalState: .active(GroupCallInfo(id: callInfo.id, accessHash: callInfo.accessHash, participantCount: state.totalCount, streamDcId: callInfo.streamDcId, title: state.title, scheduleTimestamp: nil, subscribedToScheduled: false, recordingStartTimestamp: nil, sortAscending: true, defaultParticipantsAreMuted: callInfo.defaultParticipantsAreMuted ?? state.defaultParticipantsAreMuted, isVideoEnabled: callInfo.isVideoEnabled, unmutedVideoLimit: callInfo.unmutedVideoLimit)), audioSessionControl: strongSelf.audioSessionControl) + strongSelf.updateSessionState(internalState: .active(GroupCallInfo(id: callInfo.id, accessHash: callInfo.accessHash, participantCount: state.totalCount, streamDcId: callInfo.streamDcId, title: state.title, scheduleTimestamp: nil, subscribedToScheduled: false, recordingStartTimestamp: nil, sortAscending: true, defaultParticipantsAreMuted: callInfo.defaultParticipantsAreMuted ?? state.defaultParticipantsAreMuted, isVideoEnabled: callInfo.isVideoEnabled, unmutedVideoLimit: callInfo.unmutedVideoLimit, isStream: callInfo.isStream)), audioSessionControl: strongSelf.audioSessionControl) } else { strongSelf.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo( id: callInfo.id, @@ -1326,7 +1333,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { sortAscending: state.sortAscending, defaultParticipantsAreMuted: state.defaultParticipantsAreMuted, isVideoEnabled: state.isVideoEnabled, - unmutedVideoLimit: state.unmutedVideoLimit + unmutedVideoLimit: state.unmutedVideoLimit, + isStream: callInfo.isStream )))) strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState( @@ -2067,7 +2075,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { sortAscending: state.sortAscending, defaultParticipantsAreMuted: state.defaultParticipantsAreMuted, isVideoEnabled: state.isVideoEnabled, - unmutedVideoLimit: state.unmutedVideoLimit + unmutedVideoLimit: state.unmutedVideoLimit, + isStream: callInfo.isStream )))) strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState( @@ -2980,7 +2989,8 @@ 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) + 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 { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index ab826cd71b..c139de4f37 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -240,7 +240,15 @@ struct VoiceChatPeerEntry: Identifiable { } } -public final class VoiceChatController: ViewController { +public protocol VoiceChatController: ViewController { + var call: PresentationGroupCall { get } + var currentOverlayController: VoiceChatOverlayController? { get } + var parentNavigationController: NavigationController? { get set } + + func dismiss(closing: Bool, manual: Bool) +} + +public final class VoiceChatControllerImpl: ViewController, VoiceChatController { enum DisplayMode { case modal(isExpanded: Bool, isFilled: Bool) case fullscreen(controlsHidden: Bool) @@ -748,7 +756,7 @@ public final class VoiceChatController: ViewController { private var configuration: VoiceChatConfiguration? - private weak var controller: VoiceChatController? + private weak var controller: VoiceChatControllerImpl? private let sharedContext: SharedAccountContext private let context: AccountContext private let call: PresentationGroupCall @@ -952,7 +960,7 @@ public final class VoiceChatController: ViewController { private var statsDisposable: Disposable? - init(controller: VoiceChatController, sharedContext: SharedAccountContext, call: PresentationGroupCall) { + init(controller: VoiceChatControllerImpl, sharedContext: SharedAccountContext, call: PresentationGroupCall) { self.controller = controller self.sharedContext = sharedContext self.context = call.accountContext diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatJoinScreen.swift b/submodules/TelegramCallsUI/Sources/VoiceChatJoinScreen.swift index e2c79778a9..11640dbeb8 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatJoinScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatJoinScreen.swift @@ -144,7 +144,7 @@ public final class VoiceChatJoinScreen: ViewController { defaultJoinAsPeerId = cachedData.callJoinPeerId } - let activeCall = CachedChannelData.ActiveCall(id: call.info.id, accessHash: call.info.accessHash, title: call.info.title, scheduleTimestamp: call.info.scheduleTimestamp, subscribedToScheduled: call.info.subscribedToScheduled) + let activeCall = CachedChannelData.ActiveCall(id: call.info.id, accessHash: call.info.accessHash, title: call.info.title, scheduleTimestamp: call.info.scheduleTimestamp, subscribedToScheduled: call.info.subscribedToScheduled, isStream: call.info.isStream) if availablePeers.count > 0 && defaultJoinAsPeerId == nil { strongSelf.dismiss() strongSelf.join(activeCall) diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index e181c39161..571c61e8bd 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -3174,9 +3174,9 @@ func replayFinalState( if let info = GroupCallInfo(call) { transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in if let current = current as? CachedChannelData { - return current.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: info.scheduleTimestamp, subscribedToScheduled: info.subscribedToScheduled)) + return current.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: info.scheduleTimestamp, subscribedToScheduled: info.subscribedToScheduled, isStream: info.isStream)) } else if let current = current as? CachedGroupData { - return current.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: info.scheduleTimestamp, subscribedToScheduled: info.subscribedToScheduled)) + return current.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: info.scheduleTimestamp, subscribedToScheduled: info.subscribedToScheduled, isStream: info.isStream)) } else { return current } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift index 3422a04b51..72bb4bc5e5 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedChannelData.swift @@ -161,19 +161,22 @@ public final class CachedChannelData: CachedPeerData { public var title: String? public var scheduleTimestamp: Int32? public var subscribedToScheduled: Bool + public var isStream: Bool public init( id: Int64, accessHash: Int64, title: String?, scheduleTimestamp: Int32?, - subscribedToScheduled: Bool + subscribedToScheduled: Bool, + isStream: Bool ) { self.id = id self.accessHash = accessHash self.title = title self.scheduleTimestamp = scheduleTimestamp self.subscribedToScheduled = subscribedToScheduled + self.isStream = isStream } public init(decoder: PostboxDecoder) { @@ -182,6 +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) } public func encode(_ encoder: PostboxEncoder) { @@ -198,6 +202,7 @@ public final class CachedChannelData: CachedPeerData { encoder.encodeNil(forKey: "scheduleTimestamp") } encoder.encodeBool(self.subscribedToScheduled, forKey: "subscribed") + encoder.encodeBool(self.isStream, forKey: "isStream") } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index 8901dc3beb..005adfa30a 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -16,6 +16,7 @@ public struct GroupCallInfo: Equatable { public var defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted? public var isVideoEnabled: Bool public var unmutedVideoLimit: Int + public var isStream: Bool public init( id: Int64, @@ -29,7 +30,8 @@ public struct GroupCallInfo: Equatable { sortAscending: Bool, defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, isVideoEnabled: Bool, - unmutedVideoLimit: Int + unmutedVideoLimit: Int, + isStream: Bool ) { self.id = id self.accessHash = accessHash @@ -43,6 +45,7 @@ public struct GroupCallInfo: Equatable { self.defaultParticipantsAreMuted = defaultParticipantsAreMuted self.isVideoEnabled = isVideoEnabled self.unmutedVideoLimit = unmutedVideoLimit + self.isStream = isStream } } @@ -67,7 +70,8 @@ extension GroupCallInfo { sortAscending: (flags & (1 << 6)) != 0, defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: (flags & (1 << 1)) != 0, canChange: (flags & (1 << 2)) != 0), isVideoEnabled: (flags & (1 << 9)) != 0, - unmutedVideoLimit: Int(unmutedVideoLimit) + unmutedVideoLimit: Int(unmutedVideoLimit), + isStream: (flags & (1 << 12)) != 0 ) case .groupCallDiscarded: return nil @@ -111,9 +115,9 @@ func _internal_getCurrentGroupCall(account: Account, callId: Int64, accessHash: if let peerId = peerId { transaction.updatePeerCachedData(peerIds: [peerId], update: { _, current in if let cachedData = current as? CachedChannelData { - return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall.init(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: info.scheduleTimestamp, subscribedToScheduled: cachedData.activeCall?.subscribedToScheduled ?? false)) + return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall.init(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: info.scheduleTimestamp, subscribedToScheduled: cachedData.activeCall?.subscribedToScheduled ?? false, isStream: info.isStream)) } else if let cachedData = current as? CachedGroupData { - return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: info.scheduleTimestamp, subscribedToScheduled: cachedData.activeCall?.subscribedToScheduled ?? false)) + return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: info.scheduleTimestamp, subscribedToScheduled: cachedData.activeCall?.subscribedToScheduled ?? false, isStream: info.isStream)) } else { return current } @@ -191,9 +195,9 @@ func _internal_createGroupCall(account: Account, peerId: PeerId, title: String?, return account.postbox.transaction { transaction -> GroupCallInfo in transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in if let cachedData = cachedData as? CachedChannelData { - return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: callInfo.id, accessHash: callInfo.accessHash, title: callInfo.title, scheduleTimestamp: callInfo.scheduleTimestamp, subscribedToScheduled: callInfo.subscribedToScheduled)) + return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: callInfo.id, accessHash: callInfo.accessHash, title: callInfo.title, scheduleTimestamp: callInfo.scheduleTimestamp, subscribedToScheduled: callInfo.subscribedToScheduled, isStream: callInfo.isStream)) } else if let cachedData = cachedData as? CachedGroupData { - return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: callInfo.id, accessHash: callInfo.accessHash, title: callInfo.title, scheduleTimestamp: callInfo.scheduleTimestamp, subscribedToScheduled: callInfo.subscribedToScheduled)) + return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: callInfo.id, accessHash: callInfo.accessHash, title: callInfo.title, scheduleTimestamp: callInfo.scheduleTimestamp, subscribedToScheduled: callInfo.subscribedToScheduled, isStream: callInfo.isStream)) } else { return cachedData } @@ -236,9 +240,9 @@ func _internal_startScheduledGroupCall(account: Account, peerId: PeerId, callId: return account.postbox.transaction { transaction -> GroupCallInfo in transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in if let cachedData = cachedData as? CachedChannelData { - return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: callInfo.id, accessHash: callInfo.accessHash, title: callInfo.title, scheduleTimestamp: nil, subscribedToScheduled: false)) + return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: callInfo.id, accessHash: callInfo.accessHash, title: callInfo.title, scheduleTimestamp: nil, subscribedToScheduled: false, isStream: callInfo.isStream)) } else if let cachedData = cachedData as? CachedGroupData { - return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: callInfo.id, accessHash: callInfo.accessHash, title: callInfo.title, scheduleTimestamp: nil, subscribedToScheduled: false)) + return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: callInfo.id, accessHash: callInfo.accessHash, title: callInfo.title, scheduleTimestamp: nil, subscribedToScheduled: false, isStream: callInfo.isStream)) } else { return cachedData } @@ -280,9 +284,9 @@ func _internal_toggleScheduledGroupCallSubscription(account: Account, peerId: Pe return account.postbox.transaction { transaction in transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in if let cachedData = cachedData as? CachedChannelData { - return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: callInfo.id, accessHash: callInfo.accessHash, title: callInfo.title, scheduleTimestamp: callInfo.scheduleTimestamp, subscribedToScheduled: callInfo.subscribedToScheduled)) + return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: callInfo.id, accessHash: callInfo.accessHash, title: callInfo.title, scheduleTimestamp: callInfo.scheduleTimestamp, subscribedToScheduled: callInfo.subscribedToScheduled, isStream: callInfo.isStream)) } else if let cachedData = cachedData as? CachedGroupData { - return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: callInfo.id, accessHash: callInfo.accessHash, title: callInfo.title, scheduleTimestamp: callInfo.scheduleTimestamp, subscribedToScheduled: callInfo.subscribedToScheduled)) + return cachedData.withUpdatedActiveCall(CachedChannelData.ActiveCall(id: callInfo.id, accessHash: callInfo.accessHash, title: callInfo.title, scheduleTimestamp: callInfo.scheduleTimestamp, subscribedToScheduled: callInfo.subscribedToScheduled, isStream: callInfo.isStream)) } else { return cachedData } @@ -607,9 +611,9 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId, joinAs: PeerId?, return account.postbox.transaction { transaction -> JoinGroupCallResult in transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in if let cachedData = cachedData as? CachedChannelData { - return cachedData.withUpdatedCallJoinPeerId(joinAs).withUpdatedActiveCall(CachedChannelData.ActiveCall(id: parsedCall.id, accessHash: parsedCall.accessHash, title: parsedCall.title, scheduleTimestamp: nil, subscribedToScheduled: false)) + return cachedData.withUpdatedCallJoinPeerId(joinAs).withUpdatedActiveCall(CachedChannelData.ActiveCall(id: parsedCall.id, accessHash: parsedCall.accessHash, title: parsedCall.title, scheduleTimestamp: nil, subscribedToScheduled: false, isStream: parsedCall.isStream)) } else if let cachedData = cachedData as? CachedGroupData { - return cachedData.withUpdatedCallJoinPeerId(joinAs).withUpdatedActiveCall(CachedChannelData.ActiveCall(id: parsedCall.id, accessHash: parsedCall.accessHash, title: parsedCall.title, scheduleTimestamp: nil, subscribedToScheduled: false)) + return cachedData.withUpdatedCallJoinPeerId(joinAs).withUpdatedActiveCall(CachedChannelData.ActiveCall(id: parsedCall.id, accessHash: parsedCall.accessHash, title: parsedCall.title, scheduleTimestamp: nil, subscribedToScheduled: false, isStream: parsedCall.isStream)) } else { return cachedData } @@ -2606,3 +2610,35 @@ private extension GroupCallParticipantsContext.Participant.VideoDescription { } } } + +public struct GroupCallStreamCredentials { + public var url: String + public var streamKey: String +} + +public enum GetGroupCallStreamCredentialsError { + case generic +} + +func _internal_getGroupCallStreamCredentials(account: Account, peerId: PeerId, revokePreviousCredentials: Bool) -> Signal { + return account.postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(peerId).flatMap(apiInputPeer) + } + |> castError(GetGroupCallStreamCredentialsError.self) + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer = inputPeer else { + return .fail(.generic) + } + + return account.network.request(Api.functions.phone.getGroupCallStreamRtmpUrl(peer: inputPeer, revoke: revokePreviousCredentials ? .boolTrue : .boolFalse)) + |> mapError { _ -> GetGroupCallStreamCredentialsError in + return .generic + } + |> map { result -> GroupCallStreamCredentials in + switch result { + case let .groupCallStreamRtmpUrl(url, key): + return GroupCallStreamCredentials(url: url, streamKey: key) + } + } + } +} diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift index 56c6a52dce..59e40078a3 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift @@ -143,16 +143,16 @@ public extension TelegramEngine { return EngineCallStreamState.Channel(id: channel, scale: scale, latestTimestamp: lastTimestampMs) } }) - /*if state.channels.isEmpty { - return .fail(MTRpcError(errorCode: 500, errorDescription: "Generated")) |> delay(10.0, queue: .mainQueue()) - }*/ return .single(state) } } - //|> restartIfError |> `catch` { _ -> Signal in return .single(nil) } } + + public func getGroupCallStreamCredentials(peerId: EnginePeer.Id, revokePreviousCredentials: Bool) -> Signal { + return _internal_getGroupCallStreamCredentials(account: self.account, peerId: peerId, revokePreviousCredentials: revokePreviousCredentials) + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineGroupCallDescription.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineGroupCallDescription.swift index a4685f9193..b2468bccb4 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineGroupCallDescription.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/EngineGroupCallDescription.swift @@ -6,19 +6,22 @@ public final class EngineGroupCallDescription { public let title: String? public let scheduleTimestamp: Int32? public let subscribedToScheduled: Bool + public let isStream: Bool public init( id: Int64, accessHash: Int64, title: String?, scheduleTimestamp: Int32?, - subscribedToScheduled: Bool + subscribedToScheduled: Bool, + isStream: Bool ) { self.id = id self.accessHash = accessHash self.title = title self.scheduleTimestamp = scheduleTimestamp self.subscribedToScheduled = subscribedToScheduled + self.isStream = isStream } } @@ -29,7 +32,8 @@ public extension EngineGroupCallDescription { accessHash: activeCall.accessHash, title: activeCall.title, scheduleTimestamp: activeCall.scheduleTimestamp, - subscribedToScheduled: activeCall.subscribedToScheduled + subscribedToScheduled: activeCall.subscribedToScheduled, + isStream: activeCall.isStream ) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 508e71d359..d845b546c5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -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) + 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) } } @@ -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) + 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) } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 54e29ef7b6..7093dd4c18 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -648,7 +648,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } case .groupPhoneCall, .inviteToGroupPhoneCall: if let activeCall = strongSelf.presentationInterfaceState.activeGroupCallInfo?.activeCall { - strongSelf.joinGroupCall(peerId: message.id.peerId, invite: nil, activeCall: EngineGroupCallDescription(id: activeCall.id, accessHash: activeCall.accessHash, title: activeCall.title, scheduleTimestamp: activeCall.scheduleTimestamp, subscribedToScheduled: activeCall.subscribedToScheduled)) + strongSelf.joinGroupCall(peerId: message.id.peerId, invite: nil, activeCall: EngineGroupCallDescription(id: activeCall.id, accessHash: activeCall.accessHash, title: activeCall.title, scheduleTimestamp: activeCall.scheduleTimestamp, subscribedToScheduled: activeCall.subscribedToScheduled, isStream: activeCall.isStream)) } else { var canManageGroupCalls = false if let channel = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramChannel { @@ -688,7 +688,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - strongSelf.joinGroupCall(peerId: message.id.peerId, invite: nil, activeCall: EngineGroupCallDescription(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: info.scheduleTimestamp, subscribedToScheduled: info.subscribedToScheduled)) + strongSelf.joinGroupCall(peerId: message.id.peerId, invite: nil, activeCall: EngineGroupCallDescription(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: info.scheduleTimestamp, subscribedToScheduled: info.subscribedToScheduled, isStream: info.isStream)) }, error: { [weak self] error in dismissStatus?() diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index adc4439026..2c9b9d040f 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -4151,7 +4151,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } strongSelf.context.joinGroupCall(peerId: peerId, invite: nil, requestJoinAsPeerId: { result in result(joinAsPeerId) - }, activeCall: EngineGroupCallDescription(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: nil, subscribedToScheduled: false)) + }, activeCall: EngineGroupCallDescription(id: info.id, accessHash: info.accessHash, title: info.title, scheduleTimestamp: nil, subscribedToScheduled: false, isStream: info.isStream)) }, error: { [weak self] error in guard let strongSelf = self else { return diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 89baabe529..fca422eabb 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -650,28 +650,49 @@ public final class SharedAccountContextImpl: SharedAccountContext { |> deliverOnMainQueue).start(next: { [weak self] call in if let strongSelf = self { if call !== strongSelf.groupCallController?.call { - strongSelf.groupCallController?.dismiss(closing: true) + strongSelf.groupCallController?.dismiss(closing: true, manual: false) strongSelf.groupCallController = nil strongSelf.hasOngoingCall.set(false) if let call = call, let navigationController = mainWindow.viewController as? NavigationController { mainWindow.hostView.containerView.endEditing(true) - strongSelf.hasGroupCallOnScreenPromise.set(true) - let groupCallController = VoiceChatController(sharedContext: strongSelf, accountContext: call.accountContext, call: call) - groupCallController.onViewDidAppear = { [weak self] in - if let strongSelf = self { - strongSelf.hasGroupCallOnScreenPromise.set(true) + + if call.isStream { + strongSelf.hasGroupCallOnScreenPromise.set(true) + let groupCallController = MediaStreamComponentController(call: call) + groupCallController.onViewDidAppear = { [weak self] in + if let strongSelf = self { + strongSelf.hasGroupCallOnScreenPromise.set(true) + } } - } - groupCallController.onViewDidDisappear = { [weak self] in - if let strongSelf = self { - strongSelf.hasGroupCallOnScreenPromise.set(false) + groupCallController.onViewDidDisappear = { [weak self] in + if let strongSelf = self { + strongSelf.hasGroupCallOnScreenPromise.set(false) + } } + groupCallController.navigationPresentation = .flatModal + groupCallController.parentNavigationController = navigationController + strongSelf.groupCallController = groupCallController + navigationController.pushViewController(groupCallController) + } else { + strongSelf.hasGroupCallOnScreenPromise.set(true) + let groupCallController = VoiceChatControllerImpl(sharedContext: strongSelf, accountContext: call.accountContext, call: call) + groupCallController.onViewDidAppear = { [weak self] in + if let strongSelf = self { + strongSelf.hasGroupCallOnScreenPromise.set(true) + } + } + groupCallController.onViewDidDisappear = { [weak self] in + if let strongSelf = self { + strongSelf.hasGroupCallOnScreenPromise.set(false) + } + } + groupCallController.navigationPresentation = .flatModal + groupCallController.parentNavigationController = navigationController + strongSelf.groupCallController = groupCallController + navigationController.pushViewController(groupCallController) } - groupCallController.navigationPresentation = .flatModal - groupCallController.parentNavigationController = navigationController - strongSelf.groupCallController = groupCallController - navigationController.pushViewController(groupCallController) + strongSelf.hasOngoingCall.set(true) } else { strongSelf.hasOngoingCall.set(false) diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index 7214d1df90..a5ae22266f 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit 7214d1df903dfcfb58a78858414d5724cfae5578 +Subproject commit a5ae22266f4113c60dd21bc405371b98af0359cd