mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
1f93128173
@ -402,6 +402,8 @@ public protocol PresentationGroupCall: AnyObject {
|
|||||||
|
|
||||||
var schedulePending: Bool { get }
|
var schedulePending: Bool { get }
|
||||||
|
|
||||||
|
var isStream: Bool { get }
|
||||||
|
|
||||||
var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> { get }
|
var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> { get }
|
||||||
|
|
||||||
var isSpeaking: Signal<Bool, NoError> { get }
|
var isSpeaking: Signal<Bool, NoError> { get }
|
||||||
|
@ -614,7 +614,7 @@ private final class DayComponent: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, environment: Environment<DayEnvironment>, transition: Transition) -> CGSize {
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<DayEnvironment>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -725,7 +725,6 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController
|
|||||||
|
|
||||||
strongSelf.searchContentNode?.placeholderNode.setAccessoryComponent(component: AnyComponent(Button(
|
strongSelf.searchContentNode?.placeholderNode.setAccessoryComponent(component: AnyComponent(Button(
|
||||||
content: contentComponent,
|
content: contentComponent,
|
||||||
insets: UIEdgeInsets(),
|
|
||||||
action: {
|
action: {
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
|
@ -617,7 +617,7 @@ public extension CombinedComponent {
|
|||||||
return UIView()
|
return UIView()
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
func update(view: View, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize {
|
||||||
let context = view.getCombinedComponentContext(Self.self)
|
let context = view.getCombinedComponentContext(Self.self)
|
||||||
|
|
||||||
let storedBody: Body
|
let storedBody: Body
|
||||||
@ -823,4 +823,8 @@ public extension CombinedComponent {
|
|||||||
static func Guide() -> _ChildComponentGuide {
|
static func Guide() -> _ChildComponentGuide {
|
||||||
return _ChildComponentGuide()
|
return _ChildComponentGuide()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func StoredActionSlot<Arguments>(_ argumentsType: Arguments.Type) -> ActionSlot<Arguments> {
|
||||||
|
return ActionSlot<Arguments>()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,10 +84,13 @@ extension UIView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ComponentState {
|
open class ComponentState {
|
||||||
var _updated: ((Transition) -> Void)?
|
var _updated: ((Transition) -> Void)?
|
||||||
var isUpdated: Bool = false
|
var isUpdated: Bool = false
|
||||||
|
|
||||||
|
public init() {
|
||||||
|
}
|
||||||
|
|
||||||
public final func updated(transition: Transition = .immediate) {
|
public final func updated(transition: Transition = .immediate) {
|
||||||
self.isUpdated = true
|
self.isUpdated = true
|
||||||
self._updated?(transition)
|
self._updated?(transition)
|
||||||
@ -115,7 +118,7 @@ public protocol Component: _TypeErasedComponent, Equatable {
|
|||||||
|
|
||||||
func makeView() -> View
|
func makeView() -> View
|
||||||
func makeState() -> State
|
func makeState() -> State
|
||||||
func update(view: View, availableSize: CGSize, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize
|
func update(view: View, availableSize: CGSize, state: State, environment: Environment<EnvironmentType>, transition: Transition) -> CGSize
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension Component {
|
public extension Component {
|
||||||
@ -128,7 +131,9 @@ public extension Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func _update(view: UIView, availableSize: CGSize, environment: Any, transition: Transition) -> CGSize {
|
func _update(view: UIView, availableSize: CGSize, environment: Any, transition: Transition) -> CGSize {
|
||||||
return self.update(view: view as! Self.View, availableSize: availableSize, environment: environment as! Environment<EnvironmentType>, transition: transition)
|
let view = view as! Self.View
|
||||||
|
|
||||||
|
return self.update(view: view, availableSize: availableSize, state: view.context(component: self).state, environment: environment as! Environment<EnvironmentType>, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _isEqual(to other: _TypeErasedComponent) -> Bool {
|
func _isEqual(to other: _TypeErasedComponent) -> Bool {
|
||||||
|
@ -147,6 +147,12 @@ public struct Transition {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func withAnimation(_ animation: Animation) -> Transition {
|
||||||
|
var result = self
|
||||||
|
result.animation = animation
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
public static var immediate: Transition = Transition(animation: .none)
|
public static var immediate: Transition = Transition(animation: .none)
|
||||||
|
|
||||||
public static func easeInOut(duration: Double) -> Transition {
|
public static func easeInOut(duration: Double) -> Transition {
|
||||||
|
@ -1,68 +1,126 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
public final class Button: CombinedComponent, Equatable {
|
public final class Button: Component {
|
||||||
public let content: AnyComponent<Empty>
|
public let content: AnyComponent<Empty>
|
||||||
public let insets: UIEdgeInsets
|
public let minSize: CGSize?
|
||||||
public let action: () -> Void
|
public let action: () -> Void
|
||||||
|
|
||||||
public init(
|
convenience public init(
|
||||||
content: AnyComponent<Empty>,
|
content: AnyComponent<Empty>,
|
||||||
insets: UIEdgeInsets,
|
action: @escaping () -> Void
|
||||||
|
) {
|
||||||
|
self.init(
|
||||||
|
content: content,
|
||||||
|
minSize: nil,
|
||||||
|
action: action
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private init(
|
||||||
|
content: AnyComponent<Empty>,
|
||||||
|
minSize: CGSize?,
|
||||||
action: @escaping () -> Void
|
action: @escaping () -> Void
|
||||||
) {
|
) {
|
||||||
self.content = content
|
self.content = content
|
||||||
self.insets = insets
|
self.minSize = nil
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func minSize(_ minSize: CGSize?) -> Button {
|
||||||
|
return Button(
|
||||||
|
content: self.content,
|
||||||
|
minSize: minSize,
|
||||||
|
action: self.action
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
public static func ==(lhs: Button, rhs: Button) -> Bool {
|
public static func ==(lhs: Button, rhs: Button) -> Bool {
|
||||||
if lhs.content != rhs.content {
|
if lhs.content != rhs.content {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if lhs.insets != rhs.insets {
|
if lhs.minSize != rhs.minSize {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class State: ComponentState {
|
public final class View: UIButton {
|
||||||
var isHighlighted = false
|
private let contentView: ComponentHostView<Empty>
|
||||||
|
|
||||||
override init() {
|
private var component: Button?
|
||||||
super.init()
|
private var currentIsHighlighted: Bool = false {
|
||||||
|
didSet {
|
||||||
|
if self.currentIsHighlighted != oldValue {
|
||||||
|
self.contentView.alpha = self.currentIsHighlighted ? 0.6 : 1.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func makeState() -> State {
|
override init(frame: CGRect) {
|
||||||
return State()
|
self.contentView = ComponentHostView<Empty>()
|
||||||
|
self.contentView.isUserInteractionEnabled = false
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.addSubview(self.contentView)
|
||||||
|
|
||||||
|
self.addTarget(self, action: #selector(self.pressed), for: .touchUpInside)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static var body: Body {
|
required init?(coder: NSCoder) {
|
||||||
let content = Child(environment: Empty.self)
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
return { context in
|
@objc private func pressed() {
|
||||||
let content = content.update(
|
self.component?.action()
|
||||||
component: context.component.content,
|
}
|
||||||
availableSize: CGSize(width: context.availableSize.width, height: 44.0), transition: context.transition
|
|
||||||
|
override public func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
||||||
|
self.currentIsHighlighted = true
|
||||||
|
|
||||||
|
return super.beginTracking(touch, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func endTracking(_ touch: UITouch?, with event: UIEvent?) {
|
||||||
|
self.currentIsHighlighted = false
|
||||||
|
|
||||||
|
super.endTracking(touch, with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func cancelTracking(with event: UIEvent?) {
|
||||||
|
self.currentIsHighlighted = false
|
||||||
|
|
||||||
|
super.cancelTracking(with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: Button, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
let contentSize = self.contentView.update(
|
||||||
|
transition: transition,
|
||||||
|
component: component.content,
|
||||||
|
environment: {},
|
||||||
|
containerSize: availableSize
|
||||||
)
|
)
|
||||||
|
|
||||||
let size = CGSize(width: content.size.width + context.component.insets.left + context.component.insets.right, height: content.size.height + context.component.insets.top + context.component.insets.bottom)
|
var size = contentSize
|
||||||
|
if let minSize = component.minSize {
|
||||||
|
size.width = max(size.width, minSize.width)
|
||||||
|
size.height = max(size.height, minSize.height)
|
||||||
|
}
|
||||||
|
|
||||||
let component = context.component
|
self.component = component
|
||||||
|
|
||||||
context.add(content
|
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(x: floor((size.width - contentSize.width) / 2.0), y: floor((size.height - contentSize.height) / 2.0)), size: contentSize), completion: nil)
|
||||||
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0))
|
|
||||||
.opacity(context.state.isHighlighted ? 0.2 : 1.0)
|
|
||||||
.update(Transition.Update { component, view, transition in
|
|
||||||
view.frame = component.size.centered(around: component._position ?? CGPoint())
|
|
||||||
})
|
|
||||||
.gesture(.tap {
|
|
||||||
component.action()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
60
submodules/ComponentFlow/Source/Components/Circle.swift
Normal file
60
submodules/ComponentFlow/Source/Components/Circle.swift
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
public final class Circle: Component {
|
||||||
|
public let color: UIColor
|
||||||
|
public let size: CGSize
|
||||||
|
public let width: CGFloat
|
||||||
|
|
||||||
|
public init(color: UIColor, size: CGSize, width: CGFloat) {
|
||||||
|
self.color = color
|
||||||
|
self.size = size
|
||||||
|
self.width = width
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: Circle, rhs: Circle) -> Bool {
|
||||||
|
if !lhs.color.isEqual(rhs.color) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.size != rhs.size {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.width != rhs.width {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIImageView {
|
||||||
|
var component: Circle?
|
||||||
|
var currentSize: CGSize?
|
||||||
|
|
||||||
|
func update(component: Circle, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||||
|
let size = CGSize(width: min(availableSize.width, component.size.width), height: min(availableSize.height, component.size.height))
|
||||||
|
|
||||||
|
if self.currentSize != size || self.component != component {
|
||||||
|
self.currentSize = size
|
||||||
|
self.component = component
|
||||||
|
|
||||||
|
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
|
||||||
|
if let context = UIGraphicsGetCurrentContext() {
|
||||||
|
context.setStrokeColor(component.color.cgColor)
|
||||||
|
context.setLineWidth(component.width)
|
||||||
|
context.strokeEllipse(in: CGRect(origin: CGPoint(), size: size).insetBy(dx: component.width / 2.0, dy: component.width / 2.0))
|
||||||
|
}
|
||||||
|
self.image = UIGraphicsGetImageFromCurrentImageContext()
|
||||||
|
UIGraphicsEndImageContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
@ -44,7 +44,7 @@ public final class Image: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ public final class Rectangle: Component {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(view: UIView, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
public func update(view: UIView, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
var size = availableSize
|
var size = availableSize
|
||||||
if let width = self.width {
|
if let width = self.width {
|
||||||
size.width = min(size.width, width)
|
size.width = min(size.width, width)
|
||||||
|
@ -95,7 +95,7 @@ public final class Text: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize)
|
return view.update(component: self, availableSize: availableSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ public extension Gesture {
|
|||||||
enum PanGestureState {
|
enum PanGestureState {
|
||||||
case began
|
case began
|
||||||
case updated(offset: CGPoint)
|
case updated(offset: CGPoint)
|
||||||
case ended
|
case ended(velocity: CGPoint)
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class PanGesture: Gesture {
|
private final class PanGesture: Gesture {
|
||||||
@ -24,7 +24,7 @@ public extension Gesture {
|
|||||||
case .began:
|
case .began:
|
||||||
self.action(.began)
|
self.action(.began)
|
||||||
case .ended, .cancelled:
|
case .ended, .cancelled:
|
||||||
self.action(.ended)
|
self.action(.ended(velocity: self.velocity(in: self.view)))
|
||||||
case .changed:
|
case .changed:
|
||||||
let offset = self.translation(in: self.view)
|
let offset = self.translation(in: self.view)
|
||||||
self.action(.updated(offset: offset))
|
self.action(.updated(offset: offset))
|
||||||
|
@ -17,7 +17,7 @@ private func findTaggedViewImpl(view: UIView, tag: Any) -> UIView? {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class ComponentHostView<EnvironmentType>: UIView {
|
public final class ComponentHostView<EnvironmentType: Equatable>: UIView {
|
||||||
private var currentComponent: AnyComponent<EnvironmentType>?
|
private var currentComponent: AnyComponent<EnvironmentType>?
|
||||||
private var currentContainerSize: CGSize?
|
private var currentContainerSize: CGSize?
|
||||||
private var currentSize: CGSize?
|
private var currentSize: CGSize?
|
||||||
@ -33,19 +33,12 @@ public final class ComponentHostView<EnvironmentType>: UIView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func update(transition: Transition, component: AnyComponent<EnvironmentType>, @EnvironmentBuilder environment: () -> Environment<EnvironmentType>, containerSize: CGSize) -> CGSize {
|
public func update(transition: Transition, component: AnyComponent<EnvironmentType>, @EnvironmentBuilder environment: () -> Environment<EnvironmentType>, containerSize: CGSize) -> CGSize {
|
||||||
if let currentComponent = self.currentComponent, let currentContainerSize = self.currentContainerSize, let currentSize = self.currentSize {
|
let size = self._update(transition: transition, component: component, maybeEnvironment: environment, updateEnvironment: true, forceUpdate: false, containerSize: containerSize)
|
||||||
if currentContainerSize == containerSize && currentComponent == component {
|
|
||||||
return currentSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.currentComponent = component
|
|
||||||
self.currentContainerSize = containerSize
|
|
||||||
let size = self._update(transition: transition, component: component, maybeEnvironment: environment, updateEnvironment: true, containerSize: containerSize)
|
|
||||||
self.currentSize = size
|
self.currentSize = size
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
|
|
||||||
private func _update(transition: Transition, component: AnyComponent<EnvironmentType>, maybeEnvironment: () -> Environment<EnvironmentType>, updateEnvironment: Bool, containerSize: CGSize) -> CGSize {
|
private func _update(transition: Transition, component: AnyComponent<EnvironmentType>, maybeEnvironment: () -> Environment<EnvironmentType>, updateEnvironment: Bool, forceUpdate: Bool, containerSize: CGSize) -> CGSize {
|
||||||
precondition(!self.isUpdating)
|
precondition(!self.isUpdating)
|
||||||
self.isUpdating = true
|
self.isUpdating = true
|
||||||
|
|
||||||
@ -73,13 +66,27 @@ public final class ComponentHostView<EnvironmentType>: UIView {
|
|||||||
EnvironmentBuilder._environment = nil
|
EnvironmentBuilder._environment = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let isEnvironmentUpdated = context.erasedEnvironment.calculateIsUpdated()
|
||||||
|
if isEnvironmentUpdated {
|
||||||
|
context.erasedEnvironment._isUpdated = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !forceUpdate, !isEnvironmentUpdated, let currentComponent = self.currentComponent, let currentContainerSize = self.currentContainerSize, let currentSize = self.currentSize {
|
||||||
|
if currentContainerSize == containerSize && currentComponent == component {
|
||||||
|
self.isUpdating = false
|
||||||
|
return currentSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.currentComponent = component
|
||||||
|
self.currentContainerSize = containerSize
|
||||||
|
|
||||||
componentState._updated = { [weak self] transition in
|
componentState._updated = { [weak self] transition in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let _ = strongSelf._update(transition: transition, component: component, maybeEnvironment: {
|
let _ = strongSelf._update(transition: transition, component: component, maybeEnvironment: {
|
||||||
preconditionFailure()
|
preconditionFailure()
|
||||||
} as () -> Environment<EnvironmentType>, updateEnvironment: false, containerSize: containerSize)
|
} as () -> Environment<EnvironmentType>, updateEnvironment: false, forceUpdate: true, containerSize: containerSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
let updatedSize = component._update(view: componentView, availableSize: containerSize, environment: context.erasedEnvironment, transition: transition)
|
let updatedSize = component._update(view: componentView, availableSize: containerSize, environment: context.erasedEnvironment, transition: transition)
|
||||||
|
@ -1,134 +1,3 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
public final class RootHostView<EnvironmentType: Equatable>: UIViewController {
|
|
||||||
private let content: AnyComponent<(NavigationLayout, EnvironmentType)>
|
|
||||||
|
|
||||||
private var keyboardWillChangeFrameObserver: NSObjectProtocol?
|
|
||||||
private var inputHeight: CGFloat = 0.0
|
|
||||||
|
|
||||||
private let environment: Environment<EnvironmentType>
|
|
||||||
private var componentView: ComponentHostView<(NavigationLayout, EnvironmentType)>
|
|
||||||
|
|
||||||
private var scheduledTransition: Transition?
|
|
||||||
|
|
||||||
public init(
|
|
||||||
content: AnyComponent<(NavigationLayout, EnvironmentType)>,
|
|
||||||
@EnvironmentBuilder environment: () -> Environment<EnvironmentType>
|
|
||||||
) {
|
|
||||||
self.content = content
|
|
||||||
|
|
||||||
self.environment = Environment<EnvironmentType>()
|
|
||||||
self.componentView = ComponentHostView<(NavigationLayout, EnvironmentType)>()
|
|
||||||
|
|
||||||
EnvironmentBuilder._environment = self.environment
|
|
||||||
let _ = environment()
|
|
||||||
EnvironmentBuilder._environment = nil
|
|
||||||
|
|
||||||
super.init(nibName: nil, bundle: nil)
|
|
||||||
|
|
||||||
NotificationCenter.default.addObserver(forName: UIApplication.keyboardWillChangeFrameNotification, object: nil, queue: nil, using: { [weak self] notification in
|
|
||||||
guard let strongSelf = self else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard let keyboardFrame = notification.userInfo?[UIApplication.keyboardFrameEndUserInfoKey] as? CGRect else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var duration: Double = (notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0
|
|
||||||
if duration > Double.ulpOfOne {
|
|
||||||
duration = 0.5
|
|
||||||
}
|
|
||||||
let curve: UInt = (notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue ?? 7
|
|
||||||
|
|
||||||
let transition: Transition
|
|
||||||
if curve == 7 {
|
|
||||||
transition = Transition(animation: .curve(duration: duration, curve: .spring))
|
|
||||||
} else {
|
|
||||||
transition = Transition(animation: .curve(duration: duration, curve: .easeInOut))
|
|
||||||
}
|
|
||||||
|
|
||||||
strongSelf.updateKeyboardLayout(keyboardFrame: keyboardFrame, transition: transition)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
required public init?(coder: NSCoder) {
|
|
||||||
fatalError("init(coder:) has not been implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
self.keyboardWillChangeFrameObserver.flatMap(NotificationCenter.default.removeObserver)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateKeyboardLayout(keyboardFrame: CGRect, transition: Transition) {
|
|
||||||
self.inputHeight = max(0.0, self.view.bounds.height - keyboardFrame.minY)
|
|
||||||
if self.componentView.isUpdating || true {
|
|
||||||
if let _ = self.scheduledTransition {
|
|
||||||
if case .curve = transition.animation {
|
|
||||||
self.scheduledTransition = transition
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.scheduledTransition = transition
|
|
||||||
}
|
|
||||||
self.view.setNeedsLayout()
|
|
||||||
} else {
|
|
||||||
self.updateComponent(size: self.view.bounds.size, transition: transition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func updateComponent(size: CGSize, transition: Transition) {
|
|
||||||
self.environment._isUpdated = false
|
|
||||||
|
|
||||||
transition.setFrame(view: self.componentView, frame: CGRect(origin: CGPoint(), size: size))
|
|
||||||
let _ = self.componentView.update(
|
|
||||||
transition: transition,
|
|
||||||
component: self.content,
|
|
||||||
environment: {
|
|
||||||
NavigationLayout(
|
|
||||||
statusBarHeight: size.width > size.height ? 0.0 : 40.0,
|
|
||||||
inputHeight: self.inputHeight,
|
|
||||||
bottomNavigationHeight: 22.0
|
|
||||||
)
|
|
||||||
self.environment[EnvironmentType.self]
|
|
||||||
},
|
|
||||||
containerSize: size
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func updateEnvironment(@EnvironmentBuilder environment: () -> Environment<EnvironmentType>) {
|
|
||||||
EnvironmentBuilder._environment = self.environment
|
|
||||||
let _ = environment()
|
|
||||||
EnvironmentBuilder._environment = nil
|
|
||||||
|
|
||||||
if self.environment.calculateIsUpdated() {
|
|
||||||
if !self.view.bounds.size.width.isZero {
|
|
||||||
self.updateComponent(size: self.view.bounds.size, transition: .immediate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
self.view.addSubview(self.componentView)
|
|
||||||
|
|
||||||
if !self.view.bounds.size.width.isZero {
|
|
||||||
self.updateComponent(size: self.view.bounds.size, transition: .immediate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func viewDidLayoutSubviews() {
|
|
||||||
super.viewDidLayoutSubviews()
|
|
||||||
|
|
||||||
if let scheduledTransition = self.scheduledTransition {
|
|
||||||
self.scheduledTransition = nil
|
|
||||||
self.updateComponent(size: self.view.bounds.size, transition: scheduledTransition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override public func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
|
||||||
super.viewWillTransition(to: size, with: coordinator)
|
|
||||||
|
|
||||||
self.updateComponent(size: size, transition: coordinator.isAnimated ? .easeInOut(duration: 0.3) : .immediate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
28
submodules/ComponentFlow/Source/Utils/ActionSlot.swift
Normal file
28
submodules/ComponentFlow/Source/Utils/ActionSlot.swift
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
public final class Action<Arguments> {
|
||||||
|
public let action: (Arguments) -> Void
|
||||||
|
|
||||||
|
public init(_ action: @escaping (Arguments) -> Void) {
|
||||||
|
self.action = action
|
||||||
|
}
|
||||||
|
|
||||||
|
public func callAsFunction(_ arguments: Arguments) {
|
||||||
|
self.action(arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class ActionSlot<Arguments> {
|
||||||
|
private var target: ((Arguments) -> Void)?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func connect(_ target: @escaping (Arguments) -> Void) {
|
||||||
|
self.target = target
|
||||||
|
}
|
||||||
|
|
||||||
|
public func invoke(_ arguments: Arguments) {
|
||||||
|
self.target?(arguments)
|
||||||
|
}
|
||||||
|
}
|
@ -17,9 +17,9 @@ public final class LottieAnimationComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public let animation: Animation
|
public let animation: Animation
|
||||||
public let size: CGSize
|
public let size: CGSize?
|
||||||
|
|
||||||
public init(animation: Animation, size: CGSize) {
|
public init(animation: Animation, size: CGSize?) {
|
||||||
self.animation = animation
|
self.animation = animation
|
||||||
self.size = size
|
self.size = size
|
||||||
}
|
}
|
||||||
@ -41,8 +41,6 @@ public final class LottieAnimationComponent: Component {
|
|||||||
private var animationView: LOTAnimationView?
|
private var animationView: LOTAnimationView?
|
||||||
|
|
||||||
func update(component: LottieAnimationComponent, availableSize: CGSize, transition: Transition) -> CGSize {
|
func update(component: LottieAnimationComponent, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||||
let size = CGSize(width: min(component.size.width, availableSize.width), height: min(component.size.height, availableSize.height))
|
|
||||||
|
|
||||||
if self.currentAnimation != component.animation {
|
if self.currentAnimation != component.animation {
|
||||||
if let animationView = self.animationView, animationView.isAnimationPlaying {
|
if let animationView = self.animationView, animationView.isAnimationPlaying {
|
||||||
animationView.completionBlock = { [weak self] _ in
|
animationView.completionBlock = { [weak self] _ in
|
||||||
@ -64,8 +62,6 @@ public final class LottieAnimationComponent: Component {
|
|||||||
view.backgroundColor = .clear
|
view.backgroundColor = .clear
|
||||||
view.isOpaque = false
|
view.isOpaque = false
|
||||||
|
|
||||||
//view.logHierarchyKeypaths()
|
|
||||||
|
|
||||||
for (key, value) in component.animation.colors {
|
for (key, value) in component.animation.colors {
|
||||||
let colorCallback = LOTColorValueCallback(color: value.cgColor)
|
let colorCallback = LOTColorValueCallback(color: value.cgColor)
|
||||||
self.colorCallbacks.append(colorCallback)
|
self.colorCallbacks.append(colorCallback)
|
||||||
@ -78,8 +74,18 @@ public final class LottieAnimationComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var animationSize = CGSize()
|
||||||
|
if let animationView = self.animationView, let sceneModel = animationView.sceneModel {
|
||||||
|
animationSize = sceneModel.compBounds.size
|
||||||
|
}
|
||||||
|
if let customSize = component.size {
|
||||||
|
animationSize = customSize
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = CGSize(width: min(animationSize.width, availableSize.width), height: min(animationSize.height, availableSize.height))
|
||||||
|
|
||||||
if let animationView = self.animationView {
|
if let animationView = self.animationView {
|
||||||
animationView.frame = CGRect(origin: CGPoint(x: floor((size.width - component.size.width) / 2.0), y: floor((size.height - component.size.height) / 2.0)), size: component.size)
|
animationView.frame = CGRect(origin: CGPoint(x: floor((size.width - animationSize.width) / 2.0), y: floor((size.height - animationSize.height) / 2.0)), size: animationSize)
|
||||||
|
|
||||||
if !animationView.isAnimationPlaying {
|
if !animationView.isAnimationPlaying {
|
||||||
animationView.play { _ in
|
animationView.play { _ in
|
||||||
@ -95,7 +101,7 @@ public final class LottieAnimationComponent: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,7 @@ public final class ProgressIndicatorComponent: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,26 +90,6 @@ public final class NavigationBarPresentationData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func backArrowImage(color: UIColor) -> UIImage? {
|
|
||||||
var red: CGFloat = 0.0
|
|
||||||
var green: CGFloat = 0.0
|
|
||||||
var blue: CGFloat = 0.0
|
|
||||||
var alpha: CGFloat = 0.0
|
|
||||||
color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
|
|
||||||
|
|
||||||
let key = (Int32(alpha * 255.0) << 24) | (Int32(red * 255.0) << 16) | (Int32(green * 255.0) << 8) | Int32(blue * 255.0)
|
|
||||||
if let image = backArrowImageCache[key] {
|
|
||||||
return image
|
|
||||||
} else {
|
|
||||||
if let image = NavigationBarTheme.generateBackArrowImage(color: color) {
|
|
||||||
backArrowImageCache[key] = image
|
|
||||||
return image
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum NavigationPreviousAction: Equatable {
|
enum NavigationPreviousAction: Equatable {
|
||||||
case item(UINavigationItem)
|
case item(UINavigationItem)
|
||||||
case close
|
case close
|
||||||
@ -283,6 +263,26 @@ open class NavigationBar: ASDisplayNode {
|
|||||||
return 38.0
|
return 38.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func backArrowImage(color: UIColor) -> UIImage? {
|
||||||
|
var red: CGFloat = 0.0
|
||||||
|
var green: CGFloat = 0.0
|
||||||
|
var blue: CGFloat = 0.0
|
||||||
|
var alpha: CGFloat = 0.0
|
||||||
|
color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
|
||||||
|
|
||||||
|
let key = (Int32(alpha * 255.0) << 24) | (Int32(red * 255.0) << 16) | (Int32(green * 255.0) << 8) | Int32(blue * 255.0)
|
||||||
|
if let image = backArrowImageCache[key] {
|
||||||
|
return image
|
||||||
|
} else {
|
||||||
|
if let image = NavigationBarTheme.generateBackArrowImage(color: color) {
|
||||||
|
backArrowImageCache[key] = image
|
||||||
|
return image
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static let titleFont = Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers])
|
public static let titleFont = Font.with(size: 17.0, design: .regular, weight: .semibold, traits: [.monospacedNumbers])
|
||||||
|
|
||||||
var presentationData: NavigationBarPresentationData
|
var presentationData: NavigationBarPresentationData
|
||||||
@ -884,7 +884,7 @@ open class NavigationBar: ASDisplayNode {
|
|||||||
self.rightButtonNode.color = self.presentationData.theme.buttonColor
|
self.rightButtonNode.color = self.presentationData.theme.buttonColor
|
||||||
self.rightButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor
|
self.rightButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor
|
||||||
self.rightButtonNode.rippleColor = self.presentationData.theme.primaryTextColor.withAlphaComponent(0.05)
|
self.rightButtonNode.rippleColor = self.presentationData.theme.primaryTextColor.withAlphaComponent(0.05)
|
||||||
self.backButtonArrow.image = backArrowImage(color: self.presentationData.theme.buttonColor)
|
self.backButtonArrow.image = NavigationBar.backArrowImage(color: self.presentationData.theme.buttonColor)
|
||||||
if let title = self.title {
|
if let title = self.title {
|
||||||
self.titleNode.attributedText = NSAttributedString(string: title, font: NavigationBar.titleFont, textColor: self.presentationData.theme.primaryTextColor)
|
self.titleNode.attributedText = NSAttributedString(string: title, font: NavigationBar.titleFont, textColor: self.presentationData.theme.primaryTextColor)
|
||||||
self.titleNode.accessibilityLabel = title
|
self.titleNode.accessibilityLabel = title
|
||||||
@ -977,7 +977,7 @@ open class NavigationBar: ASDisplayNode {
|
|||||||
self.rightButtonNode.color = self.presentationData.theme.buttonColor
|
self.rightButtonNode.color = self.presentationData.theme.buttonColor
|
||||||
self.rightButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor
|
self.rightButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor
|
||||||
self.rightButtonNode.rippleColor = self.presentationData.theme.primaryTextColor.withAlphaComponent(0.05)
|
self.rightButtonNode.rippleColor = self.presentationData.theme.primaryTextColor.withAlphaComponent(0.05)
|
||||||
self.backButtonArrow.image = backArrowImage(color: self.presentationData.theme.buttonColor)
|
self.backButtonArrow.image = NavigationBar.backArrowImage(color: self.presentationData.theme.buttonColor)
|
||||||
if let title = self.title {
|
if let title = self.title {
|
||||||
self.titleNode.attributedText = NSAttributedString(string: title, font: NavigationBar.titleFont, textColor: self.presentationData.theme.primaryTextColor)
|
self.titleNode.attributedText = NSAttributedString(string: title, font: NavigationBar.titleFont, textColor: self.presentationData.theme.primaryTextColor)
|
||||||
self.titleNode.accessibilityLabel = title
|
self.titleNode.accessibilityLabel = title
|
||||||
@ -1314,7 +1314,7 @@ open class NavigationBar: ASDisplayNode {
|
|||||||
public func makeTransitionBackArrowNode(accentColor: UIColor) -> ASDisplayNode? {
|
public func makeTransitionBackArrowNode(accentColor: UIColor) -> ASDisplayNode? {
|
||||||
if self.backButtonArrow.supernode != nil {
|
if self.backButtonArrow.supernode != nil {
|
||||||
let node = ASImageNode()
|
let node = ASImageNode()
|
||||||
node.image = backArrowImage(color: accentColor)
|
node.image = NavigationBar.backArrowImage(color: accentColor)
|
||||||
node.frame = self.backButtonArrow.frame
|
node.frame = self.backButtonArrow.frame
|
||||||
node.displayWithoutProcessing = true
|
node.displayWithoutProcessing = true
|
||||||
node.displaysAsynchronously = false
|
node.displaysAsynchronously = false
|
||||||
|
@ -64,7 +64,7 @@ public final class MultilineText: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,7 +126,7 @@ public final class LottieAnimationComponent: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -249,7 +249,7 @@ private final class ScrollingTooltipAnimationComponent: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -400,7 +400,7 @@ public final class TooltipComponent: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -476,7 +476,7 @@ private final class RoundedRectangle: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -550,7 +550,7 @@ private final class ShadowRoundedRectangle: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
return view.update(component: self, availableSize: availableSize, environment: environment, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -701,7 +701,7 @@ public final class RollingText: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize)
|
return view.update(component: self, availableSize: availableSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -542,6 +542,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
|
|||||||
dict[-6249322] = { return Api.InputStickerSetItem.parse_inputStickerSetItem($0) }
|
dict[-6249322] = { return Api.InputStickerSetItem.parse_inputStickerSetItem($0) }
|
||||||
dict[-1728664459] = { return Api.help.PromoData.parse_promoDataEmpty($0) }
|
dict[-1728664459] = { return Api.help.PromoData.parse_promoDataEmpty($0) }
|
||||||
dict[-1942390465] = { return Api.help.PromoData.parse_promoData($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[1753266509] = { return Api.messages.PeerSettings.parse_peerSettings($0) }
|
||||||
dict[-1613493288] = { return Api.NotifyPeer.parse_notifyPeer($0) }
|
dict[-1613493288] = { return Api.NotifyPeer.parse_notifyPeer($0) }
|
||||||
dict[-1261946036] = { return Api.NotifyPeer.parse_notifyUsers($0) }
|
dict[-1261946036] = { return Api.NotifyPeer.parse_notifyUsers($0) }
|
||||||
@ -1336,6 +1337,8 @@ public struct Api {
|
|||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.help.PromoData:
|
case let _1 as Api.help.PromoData:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
|
case let _1 as Api.phone.GroupCallStreamRtmpUrl:
|
||||||
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.messages.PeerSettings:
|
case let _1 as Api.messages.PeerSettings:
|
||||||
_1.serialize(buffer, boxed)
|
_1.serialize(buffer, boxed)
|
||||||
case let _1 as Api.NotifyPeer:
|
case let _1 as Api.NotifyPeer:
|
||||||
|
@ -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 {
|
public enum GroupCall: TypeConstructorDescription {
|
||||||
case groupCall(call: Api.GroupCall, participants: [Api.GroupCallParticipant], participantsNextOffset: String, chats: [Api.Chat], users: [Api.User])
|
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
|
return result
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func getGroupCallStreamRtmpUrl(peer: Api.InputPeer, revoke: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.phone.GroupCallStreamRtmpUrl>) {
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -329,7 +329,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||||||
if previousCurrentGroupCall != nil && currentGroupCall == nil && availableState?.participantCount == 1 {
|
if previousCurrentGroupCall != nil && currentGroupCall == nil && availableState?.participantCount == 1 {
|
||||||
panelData = nil
|
panelData = nil
|
||||||
} else {
|
} else {
|
||||||
panelData = currentGroupCall != nil || (availableState?.participantCount == 0 && availableState?.info.scheduleTimestamp == nil) ? nil : availableState
|
panelData = currentGroupCall != nil || (availableState?.participantCount == 0 && availableState?.info.scheduleTimestamp == nil && availableState?.info.isStream == false) ? nil : availableState
|
||||||
}
|
}
|
||||||
|
|
||||||
let wasEmpty = strongSelf.groupCallPanelData == nil
|
let wasEmpty = strongSelf.groupCallPanelData == nil
|
||||||
@ -420,7 +420,7 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||||||
strongSelf.joinGroupCall(
|
strongSelf.joinGroupCall(
|
||||||
peerId: groupCallPanelData.peerId,
|
peerId: groupCallPanelData.peerId,
|
||||||
invite: nil,
|
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)
|
self.navigationBar?.additionalContentNode.addSubnode(groupCallAccessoryPanel)
|
||||||
|
@ -95,6 +95,8 @@ swift_library(
|
|||||||
"//submodules/Markdown:Markdown",
|
"//submodules/Markdown:Markdown",
|
||||||
"//submodules/ChatTitleActivityNode:ChatTitleActivityNode",
|
"//submodules/ChatTitleActivityNode:ChatTitleActivityNode",
|
||||||
"//third-party/libyuv:LibYuvBinding",
|
"//third-party/libyuv:LibYuvBinding",
|
||||||
|
"//submodules/ComponentFlow:ComponentFlow",
|
||||||
|
"//submodules/Components/LottieAnimationComponent:LottieAnimationComponent",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -0,0 +1,944 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import ComponentFlow
|
||||||
|
import Display
|
||||||
|
import AccountContext
|
||||||
|
import SwiftSignalKit
|
||||||
|
import AVKit
|
||||||
|
import TelegramCore
|
||||||
|
import Postbox
|
||||||
|
import ShareController
|
||||||
|
import UndoUI
|
||||||
|
import TelegramPresentationData
|
||||||
|
import LottieAnimationComponent
|
||||||
|
|
||||||
|
final class NavigationBackButtonComponent: Component {
|
||||||
|
let text: String
|
||||||
|
let color: UIColor
|
||||||
|
|
||||||
|
init(text: String, color: UIColor) {
|
||||||
|
self.text = text
|
||||||
|
self.color = color
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: NavigationBackButtonComponent, rhs: NavigationBackButtonComponent) -> Bool {
|
||||||
|
if lhs.text != rhs.text {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.color != rhs.color {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIView {
|
||||||
|
private let arrowView: UIImageView
|
||||||
|
private let textView: ComponentHostView<Empty>
|
||||||
|
|
||||||
|
private var component: NavigationBackButtonComponent?
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.arrowView = UIImageView()
|
||||||
|
self.textView = ComponentHostView<Empty>()
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.addSubview(self.arrowView)
|
||||||
|
self.addSubview(self.textView)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: NavigationBackButtonComponent, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||||
|
let spacing: CGFloat = 6.0
|
||||||
|
let innerArrowInset: CGFloat = -8.0
|
||||||
|
|
||||||
|
if self.component?.color != component.color {
|
||||||
|
self.arrowView.image = NavigationBarTheme.generateBackArrowImage(color: component.color)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.component = component
|
||||||
|
|
||||||
|
let textSize = self.textView.update(
|
||||||
|
transition: .immediate,
|
||||||
|
component: AnyComponent(Text(
|
||||||
|
text: component.text,
|
||||||
|
font: Font.regular(17.0),
|
||||||
|
color: component.color
|
||||||
|
)),
|
||||||
|
environment: {},
|
||||||
|
containerSize: availableSize
|
||||||
|
)
|
||||||
|
|
||||||
|
var leftInset: CGFloat = 0.0
|
||||||
|
var size = textSize
|
||||||
|
if let arrowImage = self.arrowView.image {
|
||||||
|
size.width += innerArrowInset + arrowImage.size.width + spacing
|
||||||
|
size.height = max(size.height, arrowImage.size.height)
|
||||||
|
|
||||||
|
self.arrowView.frame = CGRect(origin: CGPoint(x: innerArrowInset, y: floor((size.height - arrowImage.size.height) / 2.0)), size: arrowImage.size)
|
||||||
|
leftInset += innerArrowInset + arrowImage.size.width + spacing
|
||||||
|
}
|
||||||
|
self.textView.frame = CGRect(origin: CGPoint(x: leftInset, y: floor((size.height - textSize.height) / 2.0)), size: textSize)
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class BundleIconComponent: Component {
|
||||||
|
let name: String
|
||||||
|
let tintColor: UIColor?
|
||||||
|
|
||||||
|
init(name: String, tintColor: UIColor?) {
|
||||||
|
self.name = name
|
||||||
|
self.tintColor = tintColor
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: BundleIconComponent, rhs: BundleIconComponent) -> Bool {
|
||||||
|
if lhs.name != rhs.name {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.tintColor != rhs.tintColor {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIImageView {
|
||||||
|
private var component: BundleIconComponent?
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: BundleIconComponent, availableSize: CGSize, transition: Transition) -> CGSize {
|
||||||
|
if self.component?.name != component.name || self.component?.tintColor != component.tintColor {
|
||||||
|
if let tintColor = component.tintColor {
|
||||||
|
self.image = generateTintedImage(image: UIImage(bundleImageName: component.name), color: tintColor, backgroundColor: nil)
|
||||||
|
} else {
|
||||||
|
self.image = UIImage(bundleImageName: component.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.component = component
|
||||||
|
|
||||||
|
let imageSize = self.image?.size ?? CGSize()
|
||||||
|
|
||||||
|
return CGSize(width: min(imageSize.width, availableSize.width), height: min(imageSize.height, availableSize.height))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class NavigationBarComponent: CombinedComponent {
|
||||||
|
let topInset: CGFloat
|
||||||
|
let sideInset: CGFloat
|
||||||
|
let leftItem: AnyComponent<Empty>?
|
||||||
|
let rightItems: [AnyComponentWithIdentity<Empty>]
|
||||||
|
let centerItem: AnyComponent<Empty>?
|
||||||
|
|
||||||
|
init(
|
||||||
|
topInset: CGFloat,
|
||||||
|
sideInset: CGFloat,
|
||||||
|
leftItem: AnyComponent<Empty>?,
|
||||||
|
rightItems: [AnyComponentWithIdentity<Empty>],
|
||||||
|
centerItem: AnyComponent<Empty>?
|
||||||
|
) {
|
||||||
|
self.topInset = topInset
|
||||||
|
self.sideInset = sideInset
|
||||||
|
self.leftItem = leftItem
|
||||||
|
self.rightItems = rightItems
|
||||||
|
self.centerItem = centerItem
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: NavigationBarComponent, rhs: NavigationBarComponent) -> Bool {
|
||||||
|
if lhs.topInset != rhs.topInset {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.sideInset != rhs.sideInset {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.leftItem != rhs.leftItem {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.rightItems != rhs.rightItems {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.centerItem != rhs.centerItem {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
static var body: Body {
|
||||||
|
let background = Child(Rectangle.self)
|
||||||
|
let leftItem = Child(environment: Empty.self)
|
||||||
|
let rightItems = ChildMap(environment: Empty.self, keyedBy: AnyHashable.self)
|
||||||
|
let centerItem = Child(environment: Empty.self)
|
||||||
|
|
||||||
|
return { context in
|
||||||
|
var availableWidth = context.availableSize.width
|
||||||
|
let sideInset: CGFloat = 16.0 + context.component.sideInset
|
||||||
|
|
||||||
|
let contentHeight: CGFloat = 44.0
|
||||||
|
let size = CGSize(width: context.availableSize.width, height: context.component.topInset + contentHeight)
|
||||||
|
|
||||||
|
let background = background.update(component: Rectangle(color: UIColor(white: 0.0, alpha: 0.0)), availableSize: CGSize(width: size.width, height: size.height), transition: context.transition)
|
||||||
|
|
||||||
|
let leftItem = context.component.leftItem.flatMap { leftItemComponent in
|
||||||
|
return leftItem.update(
|
||||||
|
component: leftItemComponent,
|
||||||
|
availableSize: CGSize(width: availableWidth, height: contentHeight),
|
||||||
|
transition: context.transition
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if let leftItem = leftItem {
|
||||||
|
availableWidth -= leftItem.size.width
|
||||||
|
}
|
||||||
|
|
||||||
|
var rightItemList: [_UpdatedChildComponent] = []
|
||||||
|
for item in context.component.rightItems {
|
||||||
|
let item = rightItems[item.id].update(
|
||||||
|
component: item.component,
|
||||||
|
availableSize: CGSize(width: availableWidth, height: contentHeight),
|
||||||
|
transition: context.transition
|
||||||
|
)
|
||||||
|
rightItemList.append(item)
|
||||||
|
availableWidth -= item.size.width
|
||||||
|
}
|
||||||
|
|
||||||
|
let centerItem = context.component.centerItem.flatMap { centerItemComponent in
|
||||||
|
return centerItem.update(
|
||||||
|
component: centerItemComponent,
|
||||||
|
availableSize: CGSize(width: availableWidth, height: contentHeight),
|
||||||
|
transition: context.transition
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if let centerItem = centerItem {
|
||||||
|
availableWidth -= centerItem.size.width
|
||||||
|
}
|
||||||
|
|
||||||
|
context.add(background
|
||||||
|
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0))
|
||||||
|
)
|
||||||
|
|
||||||
|
var centerLeftInset = sideInset
|
||||||
|
if let leftItem = leftItem {
|
||||||
|
context.add(leftItem
|
||||||
|
.position(CGPoint(x: sideInset + leftItem.size.width / 2.0, y: context.component.topInset + contentHeight / 2.0))
|
||||||
|
)
|
||||||
|
centerLeftInset += leftItem.size.width + 4.0
|
||||||
|
}
|
||||||
|
|
||||||
|
var centerRightInset = sideInset
|
||||||
|
var rightItemX = context.availableSize.width - sideInset
|
||||||
|
for item in rightItemList.reversed() {
|
||||||
|
context.add(item
|
||||||
|
.position(CGPoint(x: rightItemX - item.size.width / 2.0, y: context.component.topInset + contentHeight / 2.0))
|
||||||
|
)
|
||||||
|
rightItemX -= item.size.width + 4.0
|
||||||
|
centerRightInset += item.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.component.topInset + contentHeight / 2.0))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class OriginInfoComponent: CombinedComponent {
|
||||||
|
let title: String
|
||||||
|
let subtitle: String
|
||||||
|
|
||||||
|
init(
|
||||||
|
title: String,
|
||||||
|
subtitle: String
|
||||||
|
) {
|
||||||
|
self.title = title
|
||||||
|
self.subtitle = subtitle
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: OriginInfoComponent, rhs: OriginInfoComponent) -> Bool {
|
||||||
|
if lhs.title != rhs.title {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.subtitle != rhs.subtitle {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
static var body: Body {
|
||||||
|
let title = Child(Text.self)
|
||||||
|
let subtitle = Child(Text.self)
|
||||||
|
|
||||||
|
return { context in
|
||||||
|
let spacing: CGFloat = 0.0
|
||||||
|
|
||||||
|
let title = title.update(
|
||||||
|
component: Text(
|
||||||
|
text: context.component.title, font: Font.semibold(17.0), color: .white),
|
||||||
|
availableSize: context.availableSize,
|
||||||
|
transition: context.transition
|
||||||
|
)
|
||||||
|
|
||||||
|
let subtitle = subtitle.update(
|
||||||
|
component: Text(
|
||||||
|
text: context.component.subtitle, font: Font.regular(14.0), color: .white),
|
||||||
|
availableSize: context.availableSize,
|
||||||
|
transition: context.transition
|
||||||
|
)
|
||||||
|
|
||||||
|
var size = CGSize(width: max(title.size.width, subtitle.size.width), height: title.size.height + spacing + subtitle.size.height)
|
||||||
|
size.width = min(size.width, context.availableSize.width)
|
||||||
|
size.height = min(size.height, context.availableSize.height)
|
||||||
|
|
||||||
|
context.add(title
|
||||||
|
.position(CGPoint(x: size.width / 2.0, y: title.size.height / 2.0))
|
||||||
|
)
|
||||||
|
context.add(subtitle
|
||||||
|
.position(CGPoint(x: size.width / 2.0, y: title.size.height + spacing + subtitle.size.height / 2.0))
|
||||||
|
)
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class ToolbarComponent: CombinedComponent {
|
||||||
|
let bottomInset: CGFloat
|
||||||
|
let sideInset: CGFloat
|
||||||
|
let leftItem: AnyComponent<Empty>?
|
||||||
|
let rightItem: AnyComponent<Empty>?
|
||||||
|
let centerItem: AnyComponent<Empty>?
|
||||||
|
|
||||||
|
init(
|
||||||
|
bottomInset: CGFloat,
|
||||||
|
sideInset: CGFloat,
|
||||||
|
leftItem: AnyComponent<Empty>?,
|
||||||
|
rightItem: AnyComponent<Empty>?,
|
||||||
|
centerItem: AnyComponent<Empty>?
|
||||||
|
) {
|
||||||
|
self.bottomInset = bottomInset
|
||||||
|
self.sideInset = sideInset
|
||||||
|
self.leftItem = leftItem
|
||||||
|
self.rightItem = rightItem
|
||||||
|
self.centerItem = centerItem
|
||||||
|
}
|
||||||
|
|
||||||
|
static func ==(lhs: ToolbarComponent, rhs: ToolbarComponent) -> Bool {
|
||||||
|
if lhs.bottomInset != rhs.bottomInset {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.sideInset != rhs.sideInset {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.leftItem != rhs.leftItem {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.rightItem != rhs.rightItem {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.centerItem != rhs.centerItem {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
static var body: Body {
|
||||||
|
let background = Child(Rectangle.self)
|
||||||
|
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 + context.component.sideInset
|
||||||
|
|
||||||
|
let contentHeight: CGFloat = 44.0
|
||||||
|
let size = CGSize(width: context.availableSize.width, height: contentHeight + context.component.bottomInset)
|
||||||
|
|
||||||
|
let background = background.update(component: Rectangle(color: UIColor(white: 0.0, alpha: 0.0)), availableSize: CGSize(width: size.width, height: size.height), transition: context.transition)
|
||||||
|
|
||||||
|
let leftItem = context.component.leftItem.flatMap { leftItemComponent in
|
||||||
|
return leftItem.update(
|
||||||
|
component: leftItemComponent,
|
||||||
|
availableSize: CGSize(width: availableWidth, height: contentHeight),
|
||||||
|
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: contentHeight),
|
||||||
|
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: contentHeight),
|
||||||
|
transition: context.transition
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if let centerItem = centerItem {
|
||||||
|
availableWidth -= centerItem.size.width
|
||||||
|
}
|
||||||
|
|
||||||
|
context.add(background
|
||||||
|
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0))
|
||||||
|
)
|
||||||
|
|
||||||
|
var centerLeftInset = sideInset
|
||||||
|
if let leftItem = leftItem {
|
||||||
|
context.add(leftItem
|
||||||
|
.position(CGPoint(x: sideInset + leftItem.size.width / 2.0, y: contentHeight / 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: contentHeight / 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: contentHeight / 2.0))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class MediaStreamComponent: CombinedComponent {
|
||||||
|
struct OriginInfo: Equatable {
|
||||||
|
var title: String
|
||||||
|
var memberCount: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
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?
|
||||||
|
private var infoDisposable: Disposable?
|
||||||
|
|
||||||
|
private(set) var originInfo: OriginInfo?
|
||||||
|
|
||||||
|
private(set) var displayUI: Bool = true
|
||||||
|
var dismissOffset: CGFloat = 0.0
|
||||||
|
|
||||||
|
let isPictureInPictureSupported: Bool
|
||||||
|
|
||||||
|
init(call: PresentationGroupCallImpl) {
|
||||||
|
self.call = call
|
||||||
|
|
||||||
|
if #available(iOSApplicationExtension 15.0, iOS 15.0, *), AVPictureInPictureController.isPictureInPictureSupported() {
|
||||||
|
self.isPictureInPictureSupported = true
|
||||||
|
} else {
|
||||||
|
self.isPictureInPictureSupported = false
|
||||||
|
}
|
||||||
|
|
||||||
|
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 peerId = call.peerId
|
||||||
|
let callPeer = call.accountContext.account.postbox.transaction { transaction -> Peer? in
|
||||||
|
return transaction.getPeer(peerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.infoDisposable = (combineLatest(queue: .mainQueue(), call.members, callPeer)
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] members, callPeer in
|
||||||
|
guard let strongSelf = self, let members = members, let callPeer = callPeer else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let originInfo = OriginInfo(title: callPeer.debugDisplayTitle, memberCount: members.totalCount)
|
||||||
|
if strongSelf.originInfo != originInfo {
|
||||||
|
strongSelf.originInfo = originInfo
|
||||||
|
strongSelf.updated(transition: .immediate)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let _ = call.accountContext.engine.calls.getGroupCallStreamCredentials(peerId: call.peerId, revokePreviousCredentials: false).start()
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.stateDisposable?.dispose()
|
||||||
|
self.infoDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
func toggleDisplayUI() {
|
||||||
|
self.displayUI = !self.displayUI
|
||||||
|
self.updated(transition: Transition(animation: .curve(duration: 0.4, curve: .easeInOut)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateDismissOffset(value: CGFloat, interactive: Bool) {
|
||||||
|
self.dismissOffset = value
|
||||||
|
if interactive {
|
||||||
|
self.updated(transition: .immediate)
|
||||||
|
} else {
|
||||||
|
self.updated(transition: Transition(animation: .curve(duration: 0.25, curve: .easeInOut)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
let toolbar = Child(ToolbarComponent.self)
|
||||||
|
|
||||||
|
let activatePictureInPicture = StoredActionSlot(Action<Void>.self)
|
||||||
|
|
||||||
|
return { context in
|
||||||
|
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
||||||
|
if !environment.isVisible {
|
||||||
|
context.state.dismissOffset = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let background = background.update(
|
||||||
|
component: Rectangle(color: .black),
|
||||||
|
availableSize: context.availableSize,
|
||||||
|
transition: context.transition
|
||||||
|
)
|
||||||
|
|
||||||
|
let call = context.component.call
|
||||||
|
let controller = environment.controller
|
||||||
|
|
||||||
|
let video = Condition(context.state.hasVideo) {
|
||||||
|
return video.update(
|
||||||
|
component: MediaStreamVideoComponent(
|
||||||
|
call: context.component.call,
|
||||||
|
activatePictureInPicture: activatePictureInPicture,
|
||||||
|
bringBackControllerForPictureInPictureDeactivation: { [weak call] completed in
|
||||||
|
guard let call = call else {
|
||||||
|
completed()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
call.accountContext.sharedContext.mainWindow?.inCallNavigate?()
|
||||||
|
|
||||||
|
completed()
|
||||||
|
}
|
||||||
|
),
|
||||||
|
availableSize: context.availableSize,
|
||||||
|
transition: context.transition
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var navigationRightItems: [AnyComponentWithIdentity<Empty>] = []
|
||||||
|
if context.state.isPictureInPictureSupported, let _ = video {
|
||||||
|
navigationRightItems.append(AnyComponentWithIdentity(id: "pip", component: AnyComponent(Button(
|
||||||
|
content: AnyComponent(BundleIconComponent(
|
||||||
|
name: "Media Gallery/PictureInPictureButton",
|
||||||
|
tintColor: .white
|
||||||
|
)),
|
||||||
|
action: {
|
||||||
|
activatePictureInPicture.invoke(Action {
|
||||||
|
guard let controller = controller() as? MediaStreamComponentController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.dismiss(closing: false, manual: true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
).minSize(CGSize(width: 44.0, height: 44.0)))))
|
||||||
|
}
|
||||||
|
|
||||||
|
/*let whiteColor = UIColor(white: 1.0, alpha: 1.0)
|
||||||
|
navigationRightItems.append(AnyComponentWithIdentity(id: "more", component: AnyComponent(Button(
|
||||||
|
content: AnyComponent(ZStack([
|
||||||
|
AnyComponentWithIdentity(id: "b", component: AnyComponent(Circle(
|
||||||
|
color: .white,
|
||||||
|
size: CGSize(width: 22.0, height: 22.0),
|
||||||
|
width: 1.5
|
||||||
|
))),
|
||||||
|
AnyComponentWithIdentity(id: "a", component: AnyComponent(LottieAnimationComponent(
|
||||||
|
animation: LottieAnimationComponent.Animation(
|
||||||
|
name: "anim_profilemore",
|
||||||
|
colors: [
|
||||||
|
"Point 2.Group 1.Fill 1": whiteColor,
|
||||||
|
"Point 3.Group 1.Fill 1": whiteColor,
|
||||||
|
"Point 1.Group 1.Fill 1": whiteColor
|
||||||
|
],
|
||||||
|
loop: true
|
||||||
|
),
|
||||||
|
size: CGSize(width: 22.0, height: 22.0)
|
||||||
|
))),
|
||||||
|
])),
|
||||||
|
action: {
|
||||||
|
activatePictureInPicture.invoke(Action {
|
||||||
|
guard let controller = controller() as? MediaStreamComponentController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.dismiss(closing: false, manual: true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
).minSize(CGSize(width: 44.0, height: 44.0)))))*/
|
||||||
|
|
||||||
|
//TODO:localize
|
||||||
|
let navigationBar = navigationBar.update(
|
||||||
|
component: NavigationBarComponent(
|
||||||
|
topInset: environment.statusBarHeight,
|
||||||
|
sideInset: environment.safeInsets.left,
|
||||||
|
leftItem: AnyComponent(Button(
|
||||||
|
content: AnyComponent(NavigationBackButtonComponent(text: environment.strings.Common_Back, color: .white)),
|
||||||
|
action: { [weak call] in
|
||||||
|
let _ = call?.leave(terminateIfPossible: false)
|
||||||
|
})
|
||||||
|
),
|
||||||
|
rightItems: navigationRightItems,
|
||||||
|
centerItem: AnyComponent(Text(text: "Live Stream", font: Font.semibold(17.0), color: .white))
|
||||||
|
),
|
||||||
|
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height),
|
||||||
|
transition: context.transition
|
||||||
|
)
|
||||||
|
|
||||||
|
let isLandscape = context.availableSize.width > context.availableSize.height
|
||||||
|
|
||||||
|
var infoItem: AnyComponent<Empty>?
|
||||||
|
if let originInfo = context.state.originInfo {
|
||||||
|
let memberCountString: String
|
||||||
|
if originInfo.memberCount == 1 {
|
||||||
|
memberCountString = "1 viewer"
|
||||||
|
} else {
|
||||||
|
memberCountString = "\(originInfo.memberCount) viewers"
|
||||||
|
}
|
||||||
|
infoItem = AnyComponent(OriginInfoComponent(
|
||||||
|
title: originInfo.title,
|
||||||
|
subtitle: memberCountString
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
let toolbar = toolbar.update(
|
||||||
|
component: ToolbarComponent(
|
||||||
|
bottomInset: environment.safeInsets.bottom,
|
||||||
|
sideInset: environment.safeInsets.left,
|
||||||
|
leftItem: AnyComponent(Button(
|
||||||
|
content: AnyComponent(BundleIconComponent(
|
||||||
|
name: "Chat/Input/Accessory Panels/MessageSelectionForward",
|
||||||
|
tintColor: .white
|
||||||
|
)),
|
||||||
|
action: {
|
||||||
|
guard let controller = controller() as? MediaStreamComponentController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
controller.presentShare()
|
||||||
|
}
|
||||||
|
).minSize(CGSize(width: 44.0, height: 44.0))),
|
||||||
|
rightItem: AnyComponent(Button(
|
||||||
|
content: AnyComponent(BundleIconComponent(
|
||||||
|
name: isLandscape ? "Media Gallery/Minimize" : "Media Gallery/Fullscreen",
|
||||||
|
tintColor: .white
|
||||||
|
)),
|
||||||
|
action: {
|
||||||
|
if let controller = controller() as? MediaStreamComponentController {
|
||||||
|
controller.updateOrientation(orientation: isLandscape ? .portrait : .landscapeRight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).minSize(CGSize(width: 44.0, height: 44.0))),
|
||||||
|
centerItem: infoItem
|
||||||
|
),
|
||||||
|
availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height),
|
||||||
|
transition: context.transition
|
||||||
|
)
|
||||||
|
|
||||||
|
let state = context.state
|
||||||
|
let height = context.availableSize.height
|
||||||
|
context.add(background
|
||||||
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
|
||||||
|
.gesture(.tap { [weak state] in
|
||||||
|
guard let state = state else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state.toggleDisplayUI()
|
||||||
|
})
|
||||||
|
.gesture(.pan { [weak state] panState in
|
||||||
|
guard let state = state else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch panState {
|
||||||
|
case .began:
|
||||||
|
break
|
||||||
|
case let .updated(offset):
|
||||||
|
state.updateDismissOffset(value: offset.y, interactive: true)
|
||||||
|
case let .ended(velocity):
|
||||||
|
if abs(velocity.y) > 200.0 {
|
||||||
|
state.updateDismissOffset(value: velocity.y < 0 ? -height : height, interactive: false)
|
||||||
|
(controller() as? MediaStreamComponentController)?.dismiss(closing: false, manual: true)
|
||||||
|
} else {
|
||||||
|
state.updateDismissOffset(value: 0.0, interactive: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
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))
|
||||||
|
.opacity(context.state.displayUI ? 1.0 : 0.0)
|
||||||
|
)
|
||||||
|
|
||||||
|
context.add(toolbar
|
||||||
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - toolbar.size.height / 2.0))
|
||||||
|
.opacity(context.state.displayUI ? 1.0 : 0.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)?
|
||||||
|
|
||||||
|
private var initialOrientation: UIInterfaceOrientation?
|
||||||
|
|
||||||
|
public init(call: PresentationGroupCall) {
|
||||||
|
self.call = call
|
||||||
|
|
||||||
|
super.init(context: call.accountContext, component: MediaStreamComponent(call: call as! PresentationGroupCallImpl))
|
||||||
|
|
||||||
|
self.statusBar.statusBarStyle = .White
|
||||||
|
self.view.disablesInteractiveModalDismiss = true
|
||||||
|
}
|
||||||
|
|
||||||
|
required public init(coder aDecoder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
if let initialOrientation = self.initialOrientation {
|
||||||
|
self.call.accountContext.sharedContext.applicationBindings.forceOrientation(initialOrientation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.onViewDidAppear?()
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.view.layer.allowsGroupOpacity = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func viewDidDisappear(_ animated: Bool) {
|
||||||
|
super.viewDidDisappear(animated)
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
self.onViewDidDisappear?()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func dismiss(closing: Bool, manual: Bool) {
|
||||||
|
self.dismiss(completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||||
|
self.view.layer.allowsGroupOpacity = true
|
||||||
|
self.view.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false, completion: { [weak self] _ in
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
completion?()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
strongSelf.view.layer.allowsGroupOpacity = false
|
||||||
|
strongSelf.dismissImpl(completion: completion)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private func dismissImpl(completion: (() -> Void)? = nil) {
|
||||||
|
super.dismiss(completion: completion)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateOrientation(orientation: UIInterfaceOrientation) {
|
||||||
|
if self.initialOrientation == nil {
|
||||||
|
self.initialOrientation = orientation == .portrait ? .landscapeRight : .portrait
|
||||||
|
} else if self.initialOrientation == orientation {
|
||||||
|
self.initialOrientation = nil
|
||||||
|
}
|
||||||
|
self.call.accountContext.sharedContext.applicationBindings.forceOrientation(orientation)
|
||||||
|
}
|
||||||
|
|
||||||
|
func presentShare() {
|
||||||
|
let formatSendTitle: (String) -> String = { string in
|
||||||
|
var string = string
|
||||||
|
if string.contains("[") && string.contains("]") {
|
||||||
|
if let startIndex = string.firstIndex(of: "["), let endIndex = string.firstIndex(of: "]") {
|
||||||
|
string.removeSubrange(startIndex ... endIndex)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
string = string.trimmingCharacters(in: CharacterSet(charactersIn: "0123456789-,."))
|
||||||
|
}
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = (combineLatest(self.call.accountContext.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
|
||||||
|
|
||||||
|
if let peer = peer as? TelegramChannel, let addressName = peer.addressName {
|
||||||
|
maybeInviteLinks = GroupCallInviteLinks(listenerLink: "https://t.me/\(addressName)", speakerLink: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let inviteLinks = maybeInviteLinks else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let presentationData = strongSelf.call.accountContext.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)
|
||||||
|
shareController.completed = { [weak self] peerIds in
|
||||||
|
if let strongSelf = self {
|
||||||
|
let _ = (strongSelf.call.accountContext.account.postbox.transaction { transaction -> [Peer] in
|
||||||
|
var peers: [Peer] = []
|
||||||
|
for peerId in peerIds {
|
||||||
|
if let peer = transaction.getPeer(peerId) {
|
||||||
|
peers.append(peer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return peers
|
||||||
|
} |> deliverOnMainQueue).start(next: { peers in
|
||||||
|
if let strongSelf = self {
|
||||||
|
let presentationData = strongSelf.call.accountContext.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)
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
text = presentationData.strings.VoiceChat_ForwardTooltip_ManyChats(peerName, "\(peers.count - 1)").string
|
||||||
|
} else {
|
||||||
|
text = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .forward(savedMessages: isSavedMessages, text: text), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shareController.actionCompleted = {
|
||||||
|
if let strongSelf = self {
|
||||||
|
let presentationData = strongSelf.call.accountContext.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
strongSelf.present(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: presentationData.strings.VoiceChat_InviteLinkCopiedText), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
strongSelf.present(shareController, in: .window(.root))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,167 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import ComponentFlow
|
||||||
|
import AccountContext
|
||||||
|
import AVKit
|
||||||
|
|
||||||
|
final class MediaStreamVideoComponent: Component {
|
||||||
|
let call: PresentationGroupCallImpl
|
||||||
|
let activatePictureInPicture: ActionSlot<Action<Void>>
|
||||||
|
let bringBackControllerForPictureInPictureDeactivation: (@escaping () -> Void) -> Void
|
||||||
|
|
||||||
|
init(call: PresentationGroupCallImpl, activatePictureInPicture: ActionSlot<Action<Void>>, bringBackControllerForPictureInPictureDeactivation: @escaping (@escaping () -> Void) -> Void) {
|
||||||
|
self.call = call
|
||||||
|
self.activatePictureInPicture = activatePictureInPicture
|
||||||
|
self.bringBackControllerForPictureInPictureDeactivation = bringBackControllerForPictureInPictureDeactivation
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: MediaStreamVideoComponent, rhs: MediaStreamVideoComponent) -> Bool {
|
||||||
|
if lhs.call !== rhs.call {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class State: ComponentState {
|
||||||
|
override init() {
|
||||||
|
super.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeState() -> State {
|
||||||
|
return State()
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class View: UIView, AVPictureInPictureControllerDelegate, AVPictureInPictureSampleBufferPlaybackDelegate {
|
||||||
|
private let videoRenderingContext = VideoRenderingContext()
|
||||||
|
private var videoView: VideoRenderingView?
|
||||||
|
private let blurTintView: UIView
|
||||||
|
private var videoBlurView: VideoRenderingView?
|
||||||
|
|
||||||
|
private var pictureInPictureController: AVPictureInPictureController?
|
||||||
|
|
||||||
|
private var component: MediaStreamVideoComponent?
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
self.blurTintView = UIView()
|
||||||
|
self.blurTintView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||||
|
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
self.isUserInteractionEnabled = false
|
||||||
|
self.clipsToBounds = true
|
||||||
|
|
||||||
|
self.addSubview(self.blurTintView)
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func update(component: MediaStreamVideoComponent, availableSize: CGSize, state: State, transition: Transition) -> CGSize {
|
||||||
|
if self.videoView == nil {
|
||||||
|
if let input = component.call.video(endpointId: "unified") {
|
||||||
|
if let videoBlurView = self.videoRenderingContext.makeView(input: input, blur: true) {
|
||||||
|
self.videoBlurView = videoBlurView
|
||||||
|
self.insertSubview(videoBlurView, belowSubview: self.blurTintView)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let videoView = self.videoRenderingContext.makeView(input: input, blur: false, forceSampleBufferDisplayLayer: true) {
|
||||||
|
self.videoView = videoView
|
||||||
|
self.addSubview(videoView)
|
||||||
|
|
||||||
|
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.canStartPictureInPictureAutomaticallyFromInline = true
|
||||||
|
pictureInPictureController.requiresLinearPlayback = true
|
||||||
|
pictureInPictureController.delegate = self
|
||||||
|
}
|
||||||
|
|
||||||
|
videoView.setOnOrientationUpdated { [weak state] _, _ in
|
||||||
|
state?.updated(transition: .immediate)
|
||||||
|
}
|
||||||
|
videoView.setOnFirstFrameReceived { [weak state] _ in
|
||||||
|
state?.updated(transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let videoView = self.videoView {
|
||||||
|
videoView.updateIsEnabled(true)
|
||||||
|
var aspect = videoView.getAspect()
|
||||||
|
if aspect <= 0.01 {
|
||||||
|
aspect = 3.0 / 4.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let videoSize = CGSize(width: aspect * 100.0, height: 100.0).aspectFitted(availableSize)
|
||||||
|
let blurredVideoSize = videoSize.aspectFilled(availableSize)
|
||||||
|
|
||||||
|
transition.withAnimation(.none).setFrame(view: videoView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - videoSize.width) / 2.0), y: floor((availableSize.height - videoSize.height) / 2.0)), size: videoSize), completion: nil)
|
||||||
|
|
||||||
|
if let videoBlurView = self.videoBlurView {
|
||||||
|
videoBlurView.updateIsEnabled(true)
|
||||||
|
transition.withAnimation(.none).setFrame(view: videoBlurView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - blurredVideoSize.width) / 2.0), y: floor((availableSize.height - blurredVideoSize.height) / 2.0)), size: blurredVideoSize), completion: nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.component = component
|
||||||
|
|
||||||
|
component.activatePictureInPicture.connect { [weak self] completion in
|
||||||
|
guard let strongSelf = self, let pictureInPictureController = strongSelf.pictureInPictureController else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pictureInPictureController.startPictureInPicture()
|
||||||
|
|
||||||
|
completion(Void())
|
||||||
|
}
|
||||||
|
|
||||||
|
return availableSize
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, setPlaying playing: Bool) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureControllerTimeRangeForPlayback(_ pictureInPictureController: AVPictureInPictureController) -> CMTimeRange {
|
||||||
|
return CMTimeRange(start: .zero, duration: .positiveInfinity)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureControllerIsPlaybackPaused(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, didTransitionToRenderSize newRenderSize: CMVideoDimensions) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, skipByInterval skipInterval: CMTime, completion completionHandler: @escaping () -> Void) {
|
||||||
|
completionHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureControllerShouldProhibitBackgroundAudioPlayback(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
|
||||||
|
guard let component = self.component else {
|
||||||
|
completionHandler(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
component.bringBackControllerForPictureInPictureDeactivation {
|
||||||
|
completionHandler(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func makeView() -> View {
|
||||||
|
return View(frame: CGRect())
|
||||||
|
}
|
||||||
|
|
||||||
|
public func update(view: View, availableSize: CGSize, state: State, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
|
return view.update(component: self, availableSize: availableSize, state: state, transition: transition)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,181 @@
|
|||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
import ComponentFlow
|
||||||
|
import Display
|
||||||
|
import SwiftSignalKit
|
||||||
|
import TelegramPresentationData
|
||||||
|
import AccountContext
|
||||||
|
|
||||||
|
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 let isVisible: Bool
|
||||||
|
public let strings: PresentationStrings
|
||||||
|
public let controller: () -> ViewController?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
statusBarHeight: CGFloat,
|
||||||
|
safeInsets: UIEdgeInsets,
|
||||||
|
isVisible: Bool,
|
||||||
|
strings: PresentationStrings,
|
||||||
|
controller: @escaping () -> ViewController?
|
||||||
|
) {
|
||||||
|
self.statusBarHeight = statusBarHeight
|
||||||
|
self.safeInsets = safeInsets
|
||||||
|
self.isVisible = isVisible
|
||||||
|
self.strings = strings
|
||||||
|
self.controller = controller
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: Environment, rhs: Environment) -> Bool {
|
||||||
|
if lhs === rhs {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if lhs.statusBarHeight != rhs.statusBarHeight {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.safeInsets != rhs.safeInsets {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.isVisible != rhs.isVisible {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if lhs.strings !== rhs.strings {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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>
|
||||||
|
|
||||||
|
private var currentIsVisible: Bool = false
|
||||||
|
private var currentLayout: ContainerViewLayout?
|
||||||
|
|
||||||
|
init(context: AccountContext, controller: ViewControllerComponentContainer, component: AnyComponent<ViewControllerComponentContainer.Environment>) {
|
||||||
|
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
|
self.controller = controller
|
||||||
|
|
||||||
|
self.component = component
|
||||||
|
self.hostView = ComponentHostView()
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
|
||||||
|
self.view.addSubview(self.hostView)
|
||||||
|
}
|
||||||
|
|
||||||
|
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: Transition) {
|
||||||
|
self.currentLayout = layout
|
||||||
|
|
||||||
|
let environment = ViewControllerComponentContainer.Environment(
|
||||||
|
statusBarHeight: layout.statusBarHeight ?? 0.0,
|
||||||
|
safeInsets: UIEdgeInsets(top: layout.intrinsicInsets.top + layout.safeInsets.top, left: layout.intrinsicInsets.left + layout.safeInsets.left, bottom: layout.intrinsicInsets.bottom + layout.safeInsets.bottom, right: layout.intrinsicInsets.right + layout.safeInsets.right),
|
||||||
|
isVisible: self.currentIsVisible,
|
||||||
|
strings: self.presentationData.strings,
|
||||||
|
controller: { [weak self] in
|
||||||
|
return self?.controller
|
||||||
|
}
|
||||||
|
)
|
||||||
|
let _ = self.hostView.update(
|
||||||
|
transition: transition,
|
||||||
|
component: self.component,
|
||||||
|
environment: {
|
||||||
|
environment
|
||||||
|
},
|
||||||
|
containerSize: layout.size
|
||||||
|
)
|
||||||
|
transition.setFrame(view: self.hostView, frame: CGRect(origin: CGPoint(), size: layout.size), completion: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateIsVisible(isVisible: Bool) {
|
||||||
|
if self.currentIsVisible == isVisible {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.currentIsVisible = isVisible
|
||||||
|
|
||||||
|
guard let currentLayout = self.currentLayout else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.containerLayoutUpdated(currentLayout, transition: .immediate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var node: Node {
|
||||||
|
return self.displayNode as! Node
|
||||||
|
}
|
||||||
|
|
||||||
|
private let context: AccountContext
|
||||||
|
private let component: AnyComponent<ViewControllerComponentContainer.Environment>
|
||||||
|
|
||||||
|
public init<C: Component>(context: AccountContext, component: C) where C.EnvironmentType == ViewControllerComponentContainer.Environment {
|
||||||
|
self.context = context
|
||||||
|
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(context: self.context, controller: self, component: self.component)
|
||||||
|
|
||||||
|
self.displayNodeDidLoad()
|
||||||
|
}
|
||||||
|
|
||||||
|
override open func viewDidAppear(_ animated: Bool) {
|
||||||
|
super.viewDidAppear(animated)
|
||||||
|
|
||||||
|
self.node.updateIsVisible(isVisible: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override open func viewDidDisappear(_ animated: Bool) {
|
||||||
|
super.viewDidDisappear(animated)
|
||||||
|
|
||||||
|
self.node.updateIsVisible(isVisible: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||||
|
super.containerLayoutUpdated(layout, transition: transition)
|
||||||
|
|
||||||
|
self.node.containerLayoutUpdated(layout, transition: Transition(transition))
|
||||||
|
}
|
||||||
|
}
|
@ -527,10 +527,12 @@ public final class GroupCallNavigationAccessoryPanel: ASDisplayNode {
|
|||||||
var joinText = self.strings.VoiceChat_PanelJoin
|
var joinText = self.strings.VoiceChat_PanelJoin
|
||||||
var title = self.strings.VoiceChat_Title
|
var title = self.strings.VoiceChat_Title
|
||||||
var isChannel = false
|
var isChannel = false
|
||||||
if let currentData = self.currentData, currentData.isChannel {
|
if let currentData = self.currentData {
|
||||||
|
if currentData.isChannel || currentData.info.isStream {
|
||||||
title = self.strings.VoiceChatChannel_Title
|
title = self.strings.VoiceChatChannel_Title
|
||||||
isChannel = true
|
isChannel = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
var text = self.currentText
|
var text = self.currentText
|
||||||
var isScheduled = false
|
var isScheduled = false
|
||||||
var isLate = false
|
var isLate = false
|
||||||
|
@ -107,7 +107,8 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
|
|||||||
sortAscending: true,
|
sortAscending: true,
|
||||||
defaultParticipantsAreMuted: nil,
|
defaultParticipantsAreMuted: nil,
|
||||||
isVideoEnabled: false,
|
isVideoEnabled: false,
|
||||||
unmutedVideoLimit: 0
|
unmutedVideoLimit: 0,
|
||||||
|
isStream: call.isStream
|
||||||
),
|
),
|
||||||
topParticipants: [],
|
topParticipants: [],
|
||||||
participantCount: 0,
|
participantCount: 0,
|
||||||
@ -160,7 +161,7 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
|
|||||||
return GroupCallPanelData(
|
return GroupCallPanelData(
|
||||||
peerId: peerId,
|
peerId: peerId,
|
||||||
isChannel: isChannel,
|
isChannel: isChannel,
|
||||||
info: GroupCallInfo(id: call.id, accessHash: call.accessHash, participantCount: state.totalCount, streamDcId: nil, title: state.title, scheduleTimestamp: state.scheduleTimestamp, subscribedToScheduled: state.subscribedToScheduled, recordingStartTimestamp: nil, sortAscending: state.sortAscending, defaultParticipantsAreMuted: state.defaultParticipantsAreMuted, isVideoEnabled: state.isVideoEnabled, unmutedVideoLimit: state.unmutedVideoLimit),
|
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,
|
topParticipants: topParticipants,
|
||||||
participantCount: state.totalCount,
|
participantCount: state.totalCount,
|
||||||
activeSpeakers: activeSpeakers,
|
activeSpeakers: activeSpeakers,
|
||||||
@ -629,6 +630,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
private var screencastAudioDataDisposable: Disposable?
|
private var screencastAudioDataDisposable: Disposable?
|
||||||
private var screencastStateDisposable: Disposable?
|
private var screencastStateDisposable: Disposable?
|
||||||
|
|
||||||
|
public var isStream = false
|
||||||
|
|
||||||
init(
|
init(
|
||||||
accountContext: AccountContext,
|
accountContext: AccountContext,
|
||||||
audioSession: ManagedAudioSession,
|
audioSession: ManagedAudioSession,
|
||||||
@ -656,6 +659,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
self.schedulePending = initialCall == nil
|
self.schedulePending = initialCall == nil
|
||||||
self.isScheduled = initialCall == nil || initialCall?.scheduleTimestamp != nil
|
self.isScheduled = initialCall == nil || initialCall?.scheduleTimestamp != nil
|
||||||
|
|
||||||
|
if let initialCall = initialCall {
|
||||||
|
self.isStream = initialCall.isStream
|
||||||
|
}
|
||||||
|
|
||||||
self.stateValue = PresentationGroupCallState.initialValue(myPeerId: self.joinAsPeerId, title: initialCall?.title, scheduleTimestamp: initialCall?.scheduleTimestamp, subscribedToScheduled: initialCall?.subscribedToScheduled ?? false)
|
self.stateValue = PresentationGroupCallState.initialValue(myPeerId: self.joinAsPeerId, title: initialCall?.title, scheduleTimestamp: initialCall?.scheduleTimestamp, subscribedToScheduled: initialCall?.subscribedToScheduled ?? false)
|
||||||
self.statePromise = ValuePromise(self.stateValue)
|
self.statePromise = ValuePromise(self.stateValue)
|
||||||
|
|
||||||
@ -672,7 +679,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
self.audioOutputStatePromise.set(.single(([], .speaker)))
|
self.audioOutputStatePromise.set(.single(([], .speaker)))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.audioSessionDisposable = audioSession.push(audioSessionType: .voiceCall, activateImmediately: true, manualActivate: { [weak self] control in
|
self.audioSessionDisposable = audioSession.push(audioSessionType: self.isStream ? .play : .voiceCall, activateImmediately: true, manualActivate: { [weak self] control in
|
||||||
Queue.mainQueue().async {
|
Queue.mainQueue().async {
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.updateSessionState(internalState: strongSelf.internalState, audioSessionControl: control)
|
strongSelf.updateSessionState(internalState: strongSelf.internalState, audioSessionControl: control)
|
||||||
@ -718,13 +725,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
if value {
|
if value {
|
||||||
if let audioSessionControl = strongSelf.audioSessionControl {
|
if let audioSessionControl = strongSelf.audioSessionControl {
|
||||||
//let audioSessionActive: Signal<Bool, NoError>
|
if !strongSelf.isStream, let callKitIntegration = strongSelf.callKitIntegration {
|
||||||
if let callKitIntegration = strongSelf.callKitIntegration {
|
|
||||||
_ = callKitIntegration.audioSessionActive
|
_ = callKitIntegration.audioSessionActive
|
||||||
|> filter { $0 }
|
|> filter { $0 }
|
||||||
|> timeout(2.0, queue: Queue.mainQueue(), alternate: Signal { subscriber in
|
|> timeout(2.0, queue: Queue.mainQueue(), alternate: Signal { subscriber in
|
||||||
/*if let strongSelf = self, let _ = strongSelf.audioSessionControl {
|
|
||||||
}*/
|
|
||||||
subscriber.putNext(true)
|
subscriber.putNext(true)
|
||||||
subscriber.putCompletion()
|
subscriber.putCompletion()
|
||||||
return EmptyDisposable
|
return EmptyDisposable
|
||||||
@ -837,7 +841,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if let initialCall = initialCall, let temporaryParticipantsContext = (self.accountContext.cachedGroupCallContexts as? AccountGroupCallContextCacheImpl)?.impl.syncWith({ impl in
|
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)
|
self.switchToTemporaryParticipantsContext(sourceContext: temporaryParticipantsContext.context.participantsContext, oldMyPeerId: self.joinAsPeerId)
|
||||||
} else {
|
} else {
|
||||||
@ -1312,7 +1316,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
strongSelf.stateValue.subscribedToScheduled = state.subscribedToScheduled
|
strongSelf.stateValue.subscribedToScheduled = state.subscribedToScheduled
|
||||||
strongSelf.stateValue.scheduleTimestamp = strongSelf.isScheduledStarted ? nil : state.scheduleTimestamp
|
strongSelf.stateValue.scheduleTimestamp = strongSelf.isScheduledStarted ? nil : state.scheduleTimestamp
|
||||||
if state.scheduleTimestamp == nil && !strongSelf.isScheduledStarted {
|
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 {
|
} else {
|
||||||
strongSelf.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo(
|
strongSelf.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo(
|
||||||
id: callInfo.id,
|
id: callInfo.id,
|
||||||
@ -1326,7 +1330,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
sortAscending: state.sortAscending,
|
sortAscending: state.sortAscending,
|
||||||
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
|
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
|
||||||
isVideoEnabled: state.isVideoEnabled,
|
isVideoEnabled: state.isVideoEnabled,
|
||||||
unmutedVideoLimit: state.unmutedVideoLimit
|
unmutedVideoLimit: state.unmutedVideoLimit,
|
||||||
|
isStream: callInfo.isStream
|
||||||
))))
|
))))
|
||||||
|
|
||||||
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
||||||
@ -1347,12 +1352,16 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
self.internalStatePromise.set(.single(internalState))
|
self.internalStatePromise.set(.single(internalState))
|
||||||
|
|
||||||
if let audioSessionControl = audioSessionControl, previousControl == nil {
|
if let audioSessionControl = audioSessionControl, previousControl == nil {
|
||||||
|
if self.isStream {
|
||||||
|
audioSessionControl.setOutputMode(.system)
|
||||||
|
} else {
|
||||||
switch self.currentSelectedAudioOutputValue {
|
switch self.currentSelectedAudioOutputValue {
|
||||||
case .speaker:
|
case .speaker:
|
||||||
audioSessionControl.setOutputMode(.custom(self.currentSelectedAudioOutputValue))
|
audioSessionControl.setOutputMode(.custom(self.currentSelectedAudioOutputValue))
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
audioSessionControl.setup(synchronous: false)
|
audioSessionControl.setup(synchronous: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1419,7 +1428,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
strongSelf.requestCall(movingFromBroadcastToRtc: false)
|
strongSelf.requestCall(movingFromBroadcastToRtc: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: self.isVideoEnabled ? .generic : .none, enableNoiseSuppression: false, preferX264: self.accountContext.sharedContext.immediateExperimentalUISettings.preferredVideoCodec == "H264")
|
}, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: self.isVideoEnabled ? .generic : .none, enableNoiseSuppression: false, disableAudioInput: self.isStream, preferX264: self.accountContext.sharedContext.immediateExperimentalUISettings.preferredVideoCodec == "H264")
|
||||||
|
|
||||||
self.genericCallContext = genericCallContext
|
self.genericCallContext = genericCallContext
|
||||||
self.stateVersionValue += 1
|
self.stateVersionValue += 1
|
||||||
@ -1513,10 +1522,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
strongSelf.currentConnectionMode = .rtc
|
strongSelf.currentConnectionMode = .rtc
|
||||||
strongSelf.genericCallContext?.setConnectionMode(.rtc, keepBroadcastConnectedIfWasEnabled: false, isUnifiedBroadcast: false)
|
strongSelf.genericCallContext?.setConnectionMode(.rtc, keepBroadcastConnectedIfWasEnabled: false, isUnifiedBroadcast: false)
|
||||||
strongSelf.genericCallContext?.setJoinResponse(payload: clientParams)
|
strongSelf.genericCallContext?.setJoinResponse(payload: clientParams)
|
||||||
case let .broadcast(isExternalStream):
|
case .broadcast:
|
||||||
strongSelf.currentConnectionMode = .broadcast
|
strongSelf.currentConnectionMode = .broadcast
|
||||||
strongSelf.genericCallContext?.setAudioStreamData(audioStreamData: OngoingGroupCallContext.AudioStreamData(engine: strongSelf.accountContext.engine, callId: callInfo.id, accessHash: callInfo.accessHash, isExternalStream: isExternalStream))
|
strongSelf.genericCallContext?.setAudioStreamData(audioStreamData: OngoingGroupCallContext.AudioStreamData(engine: strongSelf.accountContext.engine, callId: callInfo.id, accessHash: callInfo.accessHash, isExternalStream: callInfo.isStream))
|
||||||
strongSelf.genericCallContext?.setConnectionMode(.broadcast, keepBroadcastConnectedIfWasEnabled: false, isUnifiedBroadcast: isExternalStream)
|
strongSelf.genericCallContext?.setConnectionMode(.broadcast, keepBroadcastConnectedIfWasEnabled: false, isUnifiedBroadcast: callInfo.isStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
strongSelf.updateSessionState(internalState: .established(info: joinCallResult.callInfo, connectionMode: joinCallResult.connectionMode, clientParams: clientParams, localSsrc: ssrc, initialState: joinCallResult.state), audioSessionControl: strongSelf.audioSessionControl)
|
strongSelf.updateSessionState(internalState: .established(info: joinCallResult.callInfo, connectionMode: joinCallResult.connectionMode, clientParams: clientParams, localSsrc: ssrc, initialState: joinCallResult.state), audioSessionControl: strongSelf.audioSessionControl)
|
||||||
@ -1885,7 +1894,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let chatPeer = chatPeer, !participants.contains(where: { $0.peer.id == chatPeer.id }) {
|
/*if let chatPeer = chatPeer, !participants.contains(where: { $0.peer.id == chatPeer.id }) {
|
||||||
participants.append(GroupCallParticipantsContext.Participant(
|
participants.append(GroupCallParticipantsContext.Participant(
|
||||||
peer: chatPeer,
|
peer: chatPeer,
|
||||||
ssrc: 100,
|
ssrc: 100,
|
||||||
@ -1907,7 +1916,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
joinedVideo: false
|
joinedVideo: false
|
||||||
))
|
))
|
||||||
participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: state.sortAscending) })
|
participants.sort(by: { GroupCallParticipantsContext.Participant.compare(lhs: $0, rhs: $1, sortAscending: state.sortAscending) })
|
||||||
}
|
}*/
|
||||||
|
|
||||||
var otherParticipantsWithVideo = 0
|
var otherParticipantsWithVideo = 0
|
||||||
var videoWatchingParticipants = 0
|
var videoWatchingParticipants = 0
|
||||||
@ -2067,7 +2076,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
sortAscending: state.sortAscending,
|
sortAscending: state.sortAscending,
|
||||||
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
|
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
|
||||||
isVideoEnabled: state.isVideoEnabled,
|
isVideoEnabled: state.isVideoEnabled,
|
||||||
unmutedVideoLimit: state.unmutedVideoLimit
|
unmutedVideoLimit: state.unmutedVideoLimit,
|
||||||
|
isStream: callInfo.isStream
|
||||||
))))
|
))))
|
||||||
|
|
||||||
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
strongSelf.summaryParticipantsState.set(.single(SummaryParticipantsState(
|
||||||
@ -2703,7 +2713,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
|
|
||||||
self.hasScreencast = true
|
self.hasScreencast = true
|
||||||
|
|
||||||
let screencastCallContext = OngoingGroupCallContext(video: self.screencastCapturer, requestMediaChannelDescriptions: { _, _ in EmptyDisposable }, rejoinNeeded: { }, outgoingAudioBitrateKbit: nil, videoContentType: .screencast, enableNoiseSuppression: false, preferX264: false)
|
let screencastCallContext = OngoingGroupCallContext(video: self.screencastCapturer, requestMediaChannelDescriptions: { _, _ in EmptyDisposable }, rejoinNeeded: { }, outgoingAudioBitrateKbit: nil, videoContentType: .screencast, enableNoiseSuppression: false, disableAudioInput: true, preferX264: false)
|
||||||
self.screencastCallContext = screencastCallContext
|
self.screencastCallContext = screencastCallContext
|
||||||
|
|
||||||
self.screencastJoinDisposable.set((screencastCallContext.joinPayload
|
self.screencastJoinDisposable.set((screencastCallContext.joinPayload
|
||||||
@ -2814,9 +2824,13 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
))
|
))
|
||||||
|
|
||||||
if let audioSessionControl = self.audioSessionControl {
|
if let audioSessionControl = self.audioSessionControl {
|
||||||
|
if self.isStream {
|
||||||
|
audioSessionControl.setOutputMode(.system)
|
||||||
|
} else {
|
||||||
audioSessionControl.setOutputMode(.custom(output))
|
audioSessionControl.setOutputMode(.custom(output))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func updateProximityMonitoring() {
|
private func updateProximityMonitoring() {
|
||||||
var shouldMonitorProximity = false
|
var shouldMonitorProximity = false
|
||||||
@ -2980,7 +2994,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let value = value {
|
if let value = value {
|
||||||
strongSelf.initialCall = EngineGroupCallDescription(id: value.id, accessHash: value.accessHash, title: value.title, scheduleTimestamp: nil, subscribedToScheduled: false)
|
strongSelf.initialCall = EngineGroupCallDescription(id: value.id, accessHash: value.accessHash, title: value.title, scheduleTimestamp: nil, subscribedToScheduled: false, isStream: value.isStream)
|
||||||
|
strongSelf.isStream = value.isStream
|
||||||
|
|
||||||
strongSelf.updateSessionState(internalState: .active(value), audioSessionControl: strongSelf.audioSessionControl)
|
strongSelf.updateSessionState(internalState: .active(value), audioSessionControl: strongSelf.audioSessionControl)
|
||||||
} else {
|
} else {
|
||||||
|
@ -108,7 +108,7 @@ final class SampleBufferVideoRenderingView: UIView, VideoRenderingView {
|
|||||||
return AVSampleBufferDisplayLayer.self
|
return AVSampleBufferDisplayLayer.self
|
||||||
}
|
}
|
||||||
|
|
||||||
private var sampleBufferLayer: AVSampleBufferDisplayLayer {
|
var sampleBufferLayer: AVSampleBufferDisplayLayer {
|
||||||
return self.layer as! AVSampleBufferDisplayLayer
|
return self.layer as! AVSampleBufferDisplayLayer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,14 +33,18 @@ class VideoRenderingContext {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
func makeView(input: Signal<OngoingGroupCallContext.VideoFrameData, NoError>, blur: Bool) -> VideoRenderingView? {
|
func makeView(input: Signal<OngoingGroupCallContext.VideoFrameData, NoError>, blur: Bool, forceSampleBufferDisplayLayer: Bool = false) -> VideoRenderingView? {
|
||||||
#if targetEnvironment(simulator)
|
#if targetEnvironment(simulator)
|
||||||
if blur {
|
if blur {
|
||||||
|
#if DEBUG
|
||||||
|
return SampleBufferVideoRenderingView(input: input)
|
||||||
|
#else
|
||||||
return nil
|
return nil
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
return SampleBufferVideoRenderingView(input: input)
|
return SampleBufferVideoRenderingView(input: input)
|
||||||
#else
|
#else
|
||||||
if #available(iOS 13.0, *) {
|
if #available(iOS 13.0, *), !forceSampleBufferDisplayLayer {
|
||||||
return MetalVideoRenderingView(renderingContext: self.metalContext, input: input, blur: blur)
|
return MetalVideoRenderingView(renderingContext: self.metalContext, input: input, blur: blur)
|
||||||
} else {
|
} else {
|
||||||
if blur {
|
if blur {
|
||||||
|
@ -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 {
|
enum DisplayMode {
|
||||||
case modal(isExpanded: Bool, isFilled: Bool)
|
case modal(isExpanded: Bool, isFilled: Bool)
|
||||||
case fullscreen(controlsHidden: Bool)
|
case fullscreen(controlsHidden: Bool)
|
||||||
@ -748,7 +756,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
private var configuration: VoiceChatConfiguration?
|
private var configuration: VoiceChatConfiguration?
|
||||||
|
|
||||||
private weak var controller: VoiceChatController?
|
private weak var controller: VoiceChatControllerImpl?
|
||||||
private let sharedContext: SharedAccountContext
|
private let sharedContext: SharedAccountContext
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
private let call: PresentationGroupCall
|
private let call: PresentationGroupCall
|
||||||
@ -952,7 +960,7 @@ public final class VoiceChatController: ViewController {
|
|||||||
|
|
||||||
private var statsDisposable: Disposable?
|
private var statsDisposable: Disposable?
|
||||||
|
|
||||||
init(controller: VoiceChatController, sharedContext: SharedAccountContext, call: PresentationGroupCall) {
|
init(controller: VoiceChatControllerImpl, sharedContext: SharedAccountContext, call: PresentationGroupCall) {
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.sharedContext = sharedContext
|
self.sharedContext = sharedContext
|
||||||
self.context = call.accountContext
|
self.context = call.accountContext
|
||||||
|
@ -144,7 +144,7 @@ public final class VoiceChatJoinScreen: ViewController {
|
|||||||
defaultJoinAsPeerId = cachedData.callJoinPeerId
|
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 {
|
if availablePeers.count > 0 && defaultJoinAsPeerId == nil {
|
||||||
strongSelf.dismiss()
|
strongSelf.dismiss()
|
||||||
strongSelf.join(activeCall)
|
strongSelf.join(activeCall)
|
||||||
|
@ -3174,9 +3174,9 @@ func replayFinalState(
|
|||||||
if let info = GroupCallInfo(call) {
|
if let info = GroupCallInfo(call) {
|
||||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in
|
||||||
if let current = current as? CachedChannelData {
|
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 {
|
} 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 {
|
} else {
|
||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
|
@ -161,19 +161,22 @@ public final class CachedChannelData: CachedPeerData {
|
|||||||
public var title: String?
|
public var title: String?
|
||||||
public var scheduleTimestamp: Int32?
|
public var scheduleTimestamp: Int32?
|
||||||
public var subscribedToScheduled: Bool
|
public var subscribedToScheduled: Bool
|
||||||
|
public var isStream: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
id: Int64,
|
id: Int64,
|
||||||
accessHash: Int64,
|
accessHash: Int64,
|
||||||
title: String?,
|
title: String?,
|
||||||
scheduleTimestamp: Int32?,
|
scheduleTimestamp: Int32?,
|
||||||
subscribedToScheduled: Bool
|
subscribedToScheduled: Bool,
|
||||||
|
isStream: Bool
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.accessHash = accessHash
|
self.accessHash = accessHash
|
||||||
self.title = title
|
self.title = title
|
||||||
self.scheduleTimestamp = scheduleTimestamp
|
self.scheduleTimestamp = scheduleTimestamp
|
||||||
self.subscribedToScheduled = subscribedToScheduled
|
self.subscribedToScheduled = subscribedToScheduled
|
||||||
|
self.isStream = isStream
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(decoder: PostboxDecoder) {
|
public init(decoder: PostboxDecoder) {
|
||||||
@ -182,6 +185,7 @@ public final class CachedChannelData: CachedPeerData {
|
|||||||
self.title = decoder.decodeOptionalStringForKey("title")
|
self.title = decoder.decodeOptionalStringForKey("title")
|
||||||
self.scheduleTimestamp = decoder.decodeOptionalInt32ForKey("scheduleTimestamp")
|
self.scheduleTimestamp = decoder.decodeOptionalInt32ForKey("scheduleTimestamp")
|
||||||
self.subscribedToScheduled = decoder.decodeBoolForKey("subscribed", orElse: false)
|
self.subscribedToScheduled = decoder.decodeBoolForKey("subscribed", orElse: false)
|
||||||
|
self.isStream = decoder.decodeBoolForKey("isStream", orElse: false)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(_ encoder: PostboxEncoder) {
|
public func encode(_ encoder: PostboxEncoder) {
|
||||||
@ -198,6 +202,7 @@ public final class CachedChannelData: CachedPeerData {
|
|||||||
encoder.encodeNil(forKey: "scheduleTimestamp")
|
encoder.encodeNil(forKey: "scheduleTimestamp")
|
||||||
}
|
}
|
||||||
encoder.encodeBool(self.subscribedToScheduled, forKey: "subscribed")
|
encoder.encodeBool(self.subscribedToScheduled, forKey: "subscribed")
|
||||||
|
encoder.encodeBool(self.isStream, forKey: "isStream")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ public struct GroupCallInfo: Equatable {
|
|||||||
public var defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?
|
public var defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?
|
||||||
public var isVideoEnabled: Bool
|
public var isVideoEnabled: Bool
|
||||||
public var unmutedVideoLimit: Int
|
public var unmutedVideoLimit: Int
|
||||||
|
public var isStream: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
id: Int64,
|
id: Int64,
|
||||||
@ -29,7 +30,8 @@ public struct GroupCallInfo: Equatable {
|
|||||||
sortAscending: Bool,
|
sortAscending: Bool,
|
||||||
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?,
|
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?,
|
||||||
isVideoEnabled: Bool,
|
isVideoEnabled: Bool,
|
||||||
unmutedVideoLimit: Int
|
unmutedVideoLimit: Int,
|
||||||
|
isStream: Bool
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.accessHash = accessHash
|
self.accessHash = accessHash
|
||||||
@ -43,6 +45,7 @@ public struct GroupCallInfo: Equatable {
|
|||||||
self.defaultParticipantsAreMuted = defaultParticipantsAreMuted
|
self.defaultParticipantsAreMuted = defaultParticipantsAreMuted
|
||||||
self.isVideoEnabled = isVideoEnabled
|
self.isVideoEnabled = isVideoEnabled
|
||||||
self.unmutedVideoLimit = unmutedVideoLimit
|
self.unmutedVideoLimit = unmutedVideoLimit
|
||||||
|
self.isStream = isStream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +70,8 @@ extension GroupCallInfo {
|
|||||||
sortAscending: (flags & (1 << 6)) != 0,
|
sortAscending: (flags & (1 << 6)) != 0,
|
||||||
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: (flags & (1 << 1)) != 0, canChange: (flags & (1 << 2)) != 0),
|
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: (flags & (1 << 1)) != 0, canChange: (flags & (1 << 2)) != 0),
|
||||||
isVideoEnabled: (flags & (1 << 9)) != 0,
|
isVideoEnabled: (flags & (1 << 9)) != 0,
|
||||||
unmutedVideoLimit: Int(unmutedVideoLimit)
|
unmutedVideoLimit: Int(unmutedVideoLimit),
|
||||||
|
isStream: (flags & (1 << 12)) != 0
|
||||||
)
|
)
|
||||||
case .groupCallDiscarded:
|
case .groupCallDiscarded:
|
||||||
return nil
|
return nil
|
||||||
@ -111,9 +115,9 @@ func _internal_getCurrentGroupCall(account: Account, callId: Int64, accessHash:
|
|||||||
if let peerId = peerId {
|
if let peerId = peerId {
|
||||||
transaction.updatePeerCachedData(peerIds: [peerId], update: { _, current in
|
transaction.updatePeerCachedData(peerIds: [peerId], update: { _, current in
|
||||||
if let cachedData = current as? CachedChannelData {
|
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 {
|
} 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 {
|
} else {
|
||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
@ -191,9 +195,9 @@ func _internal_createGroupCall(account: Account, peerId: PeerId, title: String?,
|
|||||||
return account.postbox.transaction { transaction -> GroupCallInfo in
|
return account.postbox.transaction { transaction -> GroupCallInfo in
|
||||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
|
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
|
||||||
if let cachedData = cachedData as? CachedChannelData {
|
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 {
|
} 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 {
|
} else {
|
||||||
return cachedData
|
return cachedData
|
||||||
}
|
}
|
||||||
@ -236,9 +240,9 @@ func _internal_startScheduledGroupCall(account: Account, peerId: PeerId, callId:
|
|||||||
return account.postbox.transaction { transaction -> GroupCallInfo in
|
return account.postbox.transaction { transaction -> GroupCallInfo in
|
||||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
|
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
|
||||||
if let cachedData = cachedData as? CachedChannelData {
|
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 {
|
} 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 {
|
} else {
|
||||||
return cachedData
|
return cachedData
|
||||||
}
|
}
|
||||||
@ -280,9 +284,9 @@ func _internal_toggleScheduledGroupCallSubscription(account: Account, peerId: Pe
|
|||||||
return account.postbox.transaction { transaction in
|
return account.postbox.transaction { transaction in
|
||||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
|
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
|
||||||
if let cachedData = cachedData as? CachedChannelData {
|
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 {
|
} 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 {
|
} else {
|
||||||
return cachedData
|
return cachedData
|
||||||
}
|
}
|
||||||
@ -607,9 +611,9 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId, joinAs: PeerId?,
|
|||||||
return account.postbox.transaction { transaction -> JoinGroupCallResult in
|
return account.postbox.transaction { transaction -> JoinGroupCallResult in
|
||||||
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
|
transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData -> CachedPeerData? in
|
||||||
if let cachedData = cachedData as? CachedChannelData {
|
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 {
|
} 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 {
|
} else {
|
||||||
return cachedData
|
return cachedData
|
||||||
}
|
}
|
||||||
@ -2530,7 +2534,7 @@ func _internal_getVideoBroadcastPart(dataSource: AudioBroadcastDataSource, callI
|
|||||||
status: .notReady,
|
status: .notReady,
|
||||||
responseTimestamp: responseTimestamp
|
responseTimestamp: responseTimestamp
|
||||||
))
|
))
|
||||||
} else if error.errorDescription == "TIME_INVALID" || error.errorDescription == "TIME_TOO_SMALL" {
|
} else if error.errorDescription == "TIME_INVALID" || error.errorDescription == "TIME_TOO_SMALL" || error.errorDescription.hasSuffix("_CHANNEL_INVALID") {
|
||||||
return .single(GetAudioBroadcastPartResult(
|
return .single(GetAudioBroadcastPartResult(
|
||||||
status: .resyncNeeded,
|
status: .resyncNeeded,
|
||||||
responseTimestamp: responseTimestamp
|
responseTimestamp: responseTimestamp
|
||||||
@ -2606,3 +2610,35 @@ private extension GroupCallParticipantsContext.Participant.VideoDescription {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct GroupCallStreamCredentials : Equatable {
|
||||||
|
public var url: String
|
||||||
|
public var streamKey: String
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum GetGroupCallStreamCredentialsError {
|
||||||
|
case generic
|
||||||
|
}
|
||||||
|
|
||||||
|
func _internal_getGroupCallStreamCredentials(account: Account, peerId: PeerId, revokePreviousCredentials: Bool) -> Signal<GroupCallStreamCredentials, GetGroupCallStreamCredentialsError> {
|
||||||
|
return account.postbox.transaction { transaction -> Api.InputPeer? in
|
||||||
|
return transaction.getPeer(peerId).flatMap(apiInputPeer)
|
||||||
|
}
|
||||||
|
|> castError(GetGroupCallStreamCredentialsError.self)
|
||||||
|
|> mapToSignal { inputPeer -> Signal<GroupCallStreamCredentials, GetGroupCallStreamCredentialsError> 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -143,16 +143,16 @@ public extension TelegramEngine {
|
|||||||
return EngineCallStreamState.Channel(id: channel, scale: scale, latestTimestamp: lastTimestampMs)
|
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)
|
return .single(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//|> restartIfError
|
|
||||||
|> `catch` { _ -> Signal<EngineCallStreamState?, NoError> in
|
|> `catch` { _ -> Signal<EngineCallStreamState?, NoError> in
|
||||||
return .single(nil)
|
return .single(nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func getGroupCallStreamCredentials(peerId: EnginePeer.Id, revokePreviousCredentials: Bool) -> Signal<GroupCallStreamCredentials, GetGroupCallStreamCredentialsError> {
|
||||||
|
return _internal_getGroupCallStreamCredentials(account: self.account, peerId: peerId, revokePreviousCredentials: revokePreviousCredentials)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,19 +6,22 @@ public final class EngineGroupCallDescription {
|
|||||||
public let title: String?
|
public let title: String?
|
||||||
public let scheduleTimestamp: Int32?
|
public let scheduleTimestamp: Int32?
|
||||||
public let subscribedToScheduled: Bool
|
public let subscribedToScheduled: Bool
|
||||||
|
public let isStream: Bool
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
id: Int64,
|
id: Int64,
|
||||||
accessHash: Int64,
|
accessHash: Int64,
|
||||||
title: String?,
|
title: String?,
|
||||||
scheduleTimestamp: Int32?,
|
scheduleTimestamp: Int32?,
|
||||||
subscribedToScheduled: Bool
|
subscribedToScheduled: Bool,
|
||||||
|
isStream: Bool
|
||||||
) {
|
) {
|
||||||
self.id = id
|
self.id = id
|
||||||
self.accessHash = accessHash
|
self.accessHash = accessHash
|
||||||
self.title = title
|
self.title = title
|
||||||
self.scheduleTimestamp = scheduleTimestamp
|
self.scheduleTimestamp = scheduleTimestamp
|
||||||
self.subscribedToScheduled = subscribedToScheduled
|
self.subscribedToScheduled = subscribedToScheduled
|
||||||
|
self.isStream = isStream
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -29,7 +32,8 @@ public extension EngineGroupCallDescription {
|
|||||||
accessHash: activeCall.accessHash,
|
accessHash: activeCall.accessHash,
|
||||||
title: activeCall.title,
|
title: activeCall.title,
|
||||||
scheduleTimestamp: activeCall.scheduleTimestamp,
|
scheduleTimestamp: activeCall.scheduleTimestamp,
|
||||||
subscribedToScheduled: activeCall.subscribedToScheduled
|
subscribedToScheduled: activeCall.subscribedToScheduled,
|
||||||
|
isStream: activeCall.isStream
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -349,7 +349,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
|
|||||||
if let inputCall = chatFullCall {
|
if let inputCall = chatFullCall {
|
||||||
switch inputCall {
|
switch inputCall {
|
||||||
case let .inputGroupCall(id, accessHash):
|
case let .inputGroupCall(id, accessHash):
|
||||||
updatedActiveCall = CachedChannelData.ActiveCall(id: id, accessHash: accessHash, title: previous.activeCall?.title, scheduleTimestamp: previous.activeCall?.scheduleTimestamp, subscribedToScheduled: previous.activeCall?.subscribedToScheduled ?? false)
|
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 {
|
if let inputCall = inputCall {
|
||||||
switch inputCall {
|
switch inputCall {
|
||||||
case let .inputGroupCall(id, accessHash):
|
case let .inputGroupCall(id, accessHash):
|
||||||
updatedActiveCall = CachedChannelData.ActiveCall(id: id, accessHash: accessHash, title: previous.activeCall?.title, scheduleTimestamp: previous.activeCall?.scheduleTimestamp, subscribedToScheduled: previous.activeCall?.subscribedToScheduled ?? false)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -648,7 +648,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
}
|
}
|
||||||
case .groupPhoneCall, .inviteToGroupPhoneCall:
|
case .groupPhoneCall, .inviteToGroupPhoneCall:
|
||||||
if let activeCall = strongSelf.presentationInterfaceState.activeGroupCallInfo?.activeCall {
|
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 {
|
} else {
|
||||||
var canManageGroupCalls = false
|
var canManageGroupCalls = false
|
||||||
if let channel = strongSelf.presentationInterfaceState.renderedPeer?.chatMainPeer as? TelegramChannel {
|
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 {
|
guard let strongSelf = self else {
|
||||||
return
|
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
|
}, error: { [weak self] error in
|
||||||
dismissStatus?()
|
dismissStatus?()
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ final class BlurredRoundedRectangle: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -166,7 +166,7 @@ final class RadialProgressComponent: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -308,7 +308,7 @@ final class CheckComponent: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -558,7 +558,7 @@ final class AvatarComponent: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -656,7 +656,7 @@ private final class WallpaperBlurComponent: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -979,7 +979,7 @@ final class OverscrollContentsComponent: Component {
|
|||||||
return View()
|
return View()
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: Transition) -> CGSize {
|
||||||
return view.update(component: self, availableSize: availableSize, transition: transition)
|
return view.update(component: self, availableSize: availableSize, transition: transition)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4151,7 +4151,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
|
|||||||
}
|
}
|
||||||
strongSelf.context.joinGroupCall(peerId: peerId, invite: nil, requestJoinAsPeerId: { result in
|
strongSelf.context.joinGroupCall(peerId: peerId, invite: nil, requestJoinAsPeerId: { result in
|
||||||
result(joinAsPeerId)
|
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
|
}, error: { [weak self] error in
|
||||||
guard let strongSelf = self else {
|
guard let strongSelf = self else {
|
||||||
return
|
return
|
||||||
|
@ -650,14 +650,16 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
|> deliverOnMainQueue).start(next: { [weak self] call in
|
|> deliverOnMainQueue).start(next: { [weak self] call in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if call !== strongSelf.groupCallController?.call {
|
if call !== strongSelf.groupCallController?.call {
|
||||||
strongSelf.groupCallController?.dismiss(closing: true)
|
strongSelf.groupCallController?.dismiss(closing: true, manual: false)
|
||||||
strongSelf.groupCallController = nil
|
strongSelf.groupCallController = nil
|
||||||
strongSelf.hasOngoingCall.set(false)
|
strongSelf.hasOngoingCall.set(false)
|
||||||
|
|
||||||
if let call = call, let navigationController = mainWindow.viewController as? NavigationController {
|
if let call = call, let navigationController = mainWindow.viewController as? NavigationController {
|
||||||
mainWindow.hostView.containerView.endEditing(true)
|
mainWindow.hostView.containerView.endEditing(true)
|
||||||
|
|
||||||
|
if call.isStream {
|
||||||
strongSelf.hasGroupCallOnScreenPromise.set(true)
|
strongSelf.hasGroupCallOnScreenPromise.set(true)
|
||||||
let groupCallController = VoiceChatController(sharedContext: strongSelf, accountContext: call.accountContext, call: call)
|
let groupCallController = MediaStreamComponentController(call: call)
|
||||||
groupCallController.onViewDidAppear = { [weak self] in
|
groupCallController.onViewDidAppear = { [weak self] in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
strongSelf.hasGroupCallOnScreenPromise.set(true)
|
strongSelf.hasGroupCallOnScreenPromise.set(true)
|
||||||
@ -672,6 +674,25 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
groupCallController.parentNavigationController = navigationController
|
groupCallController.parentNavigationController = navigationController
|
||||||
strongSelf.groupCallController = groupCallController
|
strongSelf.groupCallController = groupCallController
|
||||||
navigationController.pushViewController(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)
|
||||||
|
}
|
||||||
|
|
||||||
strongSelf.hasOngoingCall.set(true)
|
strongSelf.hasOngoingCall.set(true)
|
||||||
} else {
|
} else {
|
||||||
strongSelf.hasOngoingCall.set(false)
|
strongSelf.hasOngoingCall.set(false)
|
||||||
|
@ -382,7 +382,7 @@ public final class OngoingGroupCallContext {
|
|||||||
|
|
||||||
private let broadcastPartsSource = Atomic<BroadcastPartSource?>(value: nil)
|
private let broadcastPartsSource = Atomic<BroadcastPartSource?>(value: nil)
|
||||||
|
|
||||||
init(queue: Queue, inputDeviceId: String, outputDeviceId: String, video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set<UInt32>, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, preferX264: Bool) {
|
init(queue: Queue, inputDeviceId: String, outputDeviceId: String, video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set<UInt32>, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, preferX264: Bool) {
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
|
|
||||||
var networkStateUpdatedImpl: ((GroupCallNetworkState) -> Void)?
|
var networkStateUpdatedImpl: ((GroupCallNetworkState) -> Void)?
|
||||||
@ -488,6 +488,7 @@ public final class OngoingGroupCallContext {
|
|||||||
outgoingAudioBitrateKbit: outgoingAudioBitrateKbit ?? 32,
|
outgoingAudioBitrateKbit: outgoingAudioBitrateKbit ?? 32,
|
||||||
videoContentType: _videoContentType,
|
videoContentType: _videoContentType,
|
||||||
enableNoiseSuppression: enableNoiseSuppression,
|
enableNoiseSuppression: enableNoiseSuppression,
|
||||||
|
disableAudioInput: disableAudioInput,
|
||||||
preferX264: preferX264
|
preferX264: preferX264
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -900,10 +901,10 @@ public final class OngoingGroupCallContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(inputDeviceId: String = "", outputDeviceId: String = "", video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set<UInt32>, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, preferX264: Bool) {
|
public init(inputDeviceId: String = "", outputDeviceId: String = "", video: OngoingCallVideoCapturer?, requestMediaChannelDescriptions: @escaping (Set<UInt32>, @escaping ([MediaChannelDescription]) -> Void) -> Disposable, rejoinNeeded: @escaping () -> Void, outgoingAudioBitrateKbit: Int32?, videoContentType: VideoContentType, enableNoiseSuppression: Bool, disableAudioInput: Bool, preferX264: Bool) {
|
||||||
let queue = self.queue
|
let queue = self.queue
|
||||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||||
return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, video: video, requestMediaChannelDescriptions: requestMediaChannelDescriptions, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: videoContentType, enableNoiseSuppression: enableNoiseSuppression, preferX264: preferX264)
|
return Impl(queue: queue, inputDeviceId: inputDeviceId, outputDeviceId: outputDeviceId, video: video, requestMediaChannelDescriptions: requestMediaChannelDescriptions, rejoinNeeded: rejoinNeeded, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: videoContentType, enableNoiseSuppression: enableNoiseSuppression, disableAudioInput: disableAudioInput, preferX264: preferX264)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,8 +8,6 @@ config_setting(
|
|||||||
|
|
||||||
optimization_flags = select({
|
optimization_flags = select({
|
||||||
":debug_build": [
|
":debug_build": [
|
||||||
"-O2",
|
|
||||||
"-DNDEBUG",
|
|
||||||
],
|
],
|
||||||
"//conditions:default": ["-DNDEBUG"],
|
"//conditions:default": ["-DNDEBUG"],
|
||||||
})
|
})
|
||||||
|
@ -350,6 +350,7 @@ typedef NS_ENUM(int32_t, OngoingGroupCallRequestedVideoQuality) {
|
|||||||
outgoingAudioBitrateKbit:(int32_t)outgoingAudioBitrateKbit
|
outgoingAudioBitrateKbit:(int32_t)outgoingAudioBitrateKbit
|
||||||
videoContentType:(OngoingGroupCallVideoContentType)videoContentType
|
videoContentType:(OngoingGroupCallVideoContentType)videoContentType
|
||||||
enableNoiseSuppression:(bool)enableNoiseSuppression
|
enableNoiseSuppression:(bool)enableNoiseSuppression
|
||||||
|
disableAudioInput:(bool)disableAudioInput
|
||||||
preferX264:(bool)preferX264;
|
preferX264:(bool)preferX264;
|
||||||
|
|
||||||
- (void)stop;
|
- (void)stop;
|
||||||
|
@ -1364,6 +1364,7 @@ private:
|
|||||||
outgoingAudioBitrateKbit:(int32_t)outgoingAudioBitrateKbit
|
outgoingAudioBitrateKbit:(int32_t)outgoingAudioBitrateKbit
|
||||||
videoContentType:(OngoingGroupCallVideoContentType)videoContentType
|
videoContentType:(OngoingGroupCallVideoContentType)videoContentType
|
||||||
enableNoiseSuppression:(bool)enableNoiseSuppression
|
enableNoiseSuppression:(bool)enableNoiseSuppression
|
||||||
|
disableAudioInput:(bool)disableAudioInput
|
||||||
preferX264:(bool)preferX264 {
|
preferX264:(bool)preferX264 {
|
||||||
self = [super init];
|
self = [super init];
|
||||||
if (self != nil) {
|
if (self != nil) {
|
||||||
@ -1532,6 +1533,7 @@ private:
|
|||||||
},
|
},
|
||||||
.outgoingAudioBitrateKbit = outgoingAudioBitrateKbit,
|
.outgoingAudioBitrateKbit = outgoingAudioBitrateKbit,
|
||||||
.disableOutgoingAudioProcessing = disableOutgoingAudioProcessing,
|
.disableOutgoingAudioProcessing = disableOutgoingAudioProcessing,
|
||||||
|
.disableAudioInput = disableAudioInput,
|
||||||
.videoContentType = _videoContentType,
|
.videoContentType = _videoContentType,
|
||||||
.videoCodecPreferences = videoCodecPreferences,
|
.videoCodecPreferences = videoCodecPreferences,
|
||||||
.initialEnableNoiseSuppression = enableNoiseSuppression,
|
.initialEnableNoiseSuppression = enableNoiseSuppression,
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 7214d1df903dfcfb58a78858414d5724cfae5578
|
Subproject commit a424c9919c425454abbf31bc41f8496756e6d8bf
|
24
third-party/webrtc/BUILD
vendored
24
third-party/webrtc/BUILD
vendored
@ -10,8 +10,6 @@ config_setting(
|
|||||||
|
|
||||||
optimization_flags = select({
|
optimization_flags = select({
|
||||||
":debug_build": [
|
":debug_build": [
|
||||||
"-O2",
|
|
||||||
"-DNDEBUG",
|
|
||||||
],
|
],
|
||||||
"//conditions:default": ["-DNDEBUG"],
|
"//conditions:default": ["-DNDEBUG"],
|
||||||
})
|
})
|
||||||
@ -992,7 +990,6 @@ webrtc_sources = [
|
|||||||
"modules/audio_device/audio_device_buffer.cc",
|
"modules/audio_device/audio_device_buffer.cc",
|
||||||
"modules/audio_device/audio_device_data_observer.cc",
|
"modules/audio_device/audio_device_data_observer.cc",
|
||||||
"modules/audio_device/audio_device_generic.cc",
|
"modules/audio_device/audio_device_generic.cc",
|
||||||
"modules/audio_device/audio_device_impl.cc",
|
|
||||||
"modules/audio_device/audio_device_name.cc",
|
"modules/audio_device/audio_device_name.cc",
|
||||||
"modules/audio_device/dummy/audio_device_dummy.cc",
|
"modules/audio_device/dummy/audio_device_dummy.cc",
|
||||||
"modules/audio_device/dummy/file_audio_device.cc",
|
"modules/audio_device/dummy/file_audio_device.cc",
|
||||||
@ -1905,7 +1902,6 @@ webrtc_sources = [
|
|||||||
"modules/audio_coding/neteq/timestamp_scaler.h",
|
"modules/audio_coding/neteq/timestamp_scaler.h",
|
||||||
"modules/audio_device/audio_device_buffer.h",
|
"modules/audio_device/audio_device_buffer.h",
|
||||||
"modules/audio_device/audio_device_generic.h",
|
"modules/audio_device/audio_device_generic.h",
|
||||||
"modules/audio_device/audio_device_impl.h",
|
|
||||||
"modules/audio_device/dummy/audio_device_dummy.h",
|
"modules/audio_device/dummy/audio_device_dummy.h",
|
||||||
"modules/audio_device/dummy/file_audio_device.h",
|
"modules/audio_device/dummy/file_audio_device.h",
|
||||||
"modules/audio_device/dummy/file_audio_device_factory.h",
|
"modules/audio_device/dummy/file_audio_device_factory.h",
|
||||||
@ -2964,8 +2960,6 @@ ios_objc_sources = [
|
|||||||
"objc/base/RTCVideoEncoderQpThresholds.m",
|
"objc/base/RTCVideoEncoderQpThresholds.m",
|
||||||
"objc/base/RTCVideoEncoderSettings.h",
|
"objc/base/RTCVideoEncoderSettings.h",
|
||||||
"objc/base/RTCVideoEncoderSettings.m",
|
"objc/base/RTCVideoEncoderSettings.m",
|
||||||
"objc/components/audio/RTCAudioSessionConfiguration.h",
|
|
||||||
"objc/components/audio/RTCAudioSessionConfiguration.m",
|
|
||||||
"objc/components/renderer/opengl/RTCNV12TextureCache.h",
|
"objc/components/renderer/opengl/RTCNV12TextureCache.h",
|
||||||
"objc/components/renderer/opengl/RTCNV12TextureCache.m",
|
"objc/components/renderer/opengl/RTCNV12TextureCache.m",
|
||||||
"objc/helpers/RTCDispatcher.h",
|
"objc/helpers/RTCDispatcher.h",
|
||||||
@ -2980,14 +2974,8 @@ ios_objc_sources = [
|
|||||||
|
|
||||||
ios_sources = [
|
ios_sources = [
|
||||||
"objc/native/src/audio/audio_session_observer.h",
|
"objc/native/src/audio/audio_session_observer.h",
|
||||||
"objc/native/src/audio/audio_device_ios.h",
|
|
||||||
"objc/native/src/audio/audio_device_ios.mm",
|
|
||||||
"objc/native/src/audio/audio_device_module_ios.h",
|
|
||||||
"objc/native/src/audio/audio_device_module_ios.mm",
|
|
||||||
"objc/native/src/audio/helpers.h",
|
"objc/native/src/audio/helpers.h",
|
||||||
"objc/native/src/audio/helpers.mm",
|
"objc/native/src/audio/helpers.mm",
|
||||||
"objc/native/src/audio/voice_processing_audio_unit.h",
|
|
||||||
"objc/native/src/audio/voice_processing_audio_unit.mm",
|
|
||||||
"objc/native/src/objc_frame_buffer.h",
|
"objc/native/src/objc_frame_buffer.h",
|
||||||
"objc/native/src/objc_frame_buffer.mm",
|
"objc/native/src/objc_frame_buffer.mm",
|
||||||
"objc/native/src/objc_video_decoder_factory.h",
|
"objc/native/src/objc_video_decoder_factory.h",
|
||||||
@ -3024,10 +3012,6 @@ ios_sources = [
|
|||||||
"objc/base/RTCVideoFrameBuffer.h",
|
"objc/base/RTCVideoFrameBuffer.h",
|
||||||
"objc/base/RTCVideoRenderer.h",
|
"objc/base/RTCVideoRenderer.h",
|
||||||
"objc/base/RTCYUVPlanarBuffer.h",
|
"objc/base/RTCYUVPlanarBuffer.h",
|
||||||
"objc/components/audio/RTCAudioSession+Configuration.mm",
|
|
||||||
"objc/components/audio/RTCAudioSession+Private.h",
|
|
||||||
"objc/components/audio/RTCAudioSession.h",
|
|
||||||
"objc/components/audio/RTCAudioSession.mm",
|
|
||||||
"objc/api/peerconnection/RTCVideoSource+Private.h",
|
"objc/api/peerconnection/RTCVideoSource+Private.h",
|
||||||
"objc/api/peerconnection/RTCVideoSource.h",
|
"objc/api/peerconnection/RTCVideoSource.h",
|
||||||
"objc/api/peerconnection/RTCVideoSource.mm",
|
"objc/api/peerconnection/RTCVideoSource.mm",
|
||||||
@ -3073,8 +3057,6 @@ ios_sources = [
|
|||||||
"objc/api/video_codec/RTCWrappedNativeVideoDecoder.mm",
|
"objc/api/video_codec/RTCWrappedNativeVideoDecoder.mm",
|
||||||
"objc/api/video_codec/RTCWrappedNativeVideoEncoder.h",
|
"objc/api/video_codec/RTCWrappedNativeVideoEncoder.h",
|
||||||
"objc/api/video_codec/RTCWrappedNativeVideoEncoder.mm",
|
"objc/api/video_codec/RTCWrappedNativeVideoEncoder.mm",
|
||||||
"objc/components/audio/RTCNativeAudioSessionDelegateAdapter.h",
|
|
||||||
"objc/components/audio/RTCNativeAudioSessionDelegateAdapter.mm",
|
|
||||||
"objc/native/api/video_capturer.h",
|
"objc/native/api/video_capturer.h",
|
||||||
"objc/native/api/video_capturer.mm",
|
"objc/native/api/video_capturer.mm",
|
||||||
"objc/native/api/video_decoder_factory.h",
|
"objc/native/api/video_decoder_factory.h",
|
||||||
@ -3087,7 +3069,6 @@ ios_sources = [
|
|||||||
"objc/native/api/video_frame_buffer.mm",
|
"objc/native/api/video_frame_buffer.mm",
|
||||||
"objc/native/api/video_renderer.h",
|
"objc/native/api/video_renderer.h",
|
||||||
"objc/native/api/video_renderer.mm",
|
"objc/native/api/video_renderer.mm",
|
||||||
"objc/native/api/audio_device_module.h",
|
|
||||||
"objc/components/renderer/metal/RTCMTLNV12Renderer.h",
|
"objc/components/renderer/metal/RTCMTLNV12Renderer.h",
|
||||||
"objc/components/renderer/metal/RTCMTLNV12Renderer.mm",
|
"objc/components/renderer/metal/RTCMTLNV12Renderer.mm",
|
||||||
"objc/components/renderer/metal/RTCMTLRGBRenderer.h",
|
"objc/components/renderer/metal/RTCMTLRGBRenderer.h",
|
||||||
@ -3113,7 +3094,6 @@ ios_sources = [
|
|||||||
"objc/api/peerconnection/RTCMediaStream+Private.h",
|
"objc/api/peerconnection/RTCMediaStream+Private.h",
|
||||||
"objc/api/peerconnection/RTCPeerConnection+Private.h",
|
"objc/api/peerconnection/RTCPeerConnection+Private.h",
|
||||||
"objc/api/peerconnection/RTCPeerConnectionFactory+Native.h",
|
"objc/api/peerconnection/RTCPeerConnectionFactory+Native.h",
|
||||||
"objc/api/peerconnection/RTCPeerConnectionFactoryBuilder.h",
|
|
||||||
"objc/api/peerconnection/RTCRtcpParameters+Private.h",
|
"objc/api/peerconnection/RTCRtcpParameters+Private.h",
|
||||||
"objc/api/peerconnection/RTCRtpCodecParameters+Private.h",
|
"objc/api/peerconnection/RTCRtpCodecParameters+Private.h",
|
||||||
"objc/api/peerconnection/RTCRtpEncodingParameters+Private.h",
|
"objc/api/peerconnection/RTCRtpEncodingParameters+Private.h",
|
||||||
@ -3132,11 +3112,9 @@ ios_sources = [
|
|||||||
"objc/api/peerconnection/RTCEncodedImage+Private.mm",
|
"objc/api/peerconnection/RTCEncodedImage+Private.mm",
|
||||||
"objc/api/peerconnection/RTCPeerConnection+DataChannel.mm",
|
"objc/api/peerconnection/RTCPeerConnection+DataChannel.mm",
|
||||||
"objc/api/peerconnection/RTCPeerConnection+Stats.mm",
|
"objc/api/peerconnection/RTCPeerConnection+Stats.mm",
|
||||||
"objc/api/peerconnection/RTCPeerConnectionFactoryBuilder+DefaultComponents.mm",
|
|
||||||
"objc/api/peerconnection/RTCVideoCodecInfo+Private.mm",
|
"objc/api/peerconnection/RTCVideoCodecInfo+Private.mm",
|
||||||
"objc/api/peerconnection/RTCVideoEncoderSettings+Private.mm",
|
"objc/api/peerconnection/RTCVideoEncoderSettings+Private.mm",
|
||||||
"objc/helpers/NSString+StdString.mm",
|
"objc/helpers/NSString+StdString.mm",
|
||||||
"objc/api/peerconnection/RTCPeerConnectionFactoryBuilder.mm",
|
|
||||||
"objc/api/peerconnection/RTCLegacyStatsReport.mm",
|
"objc/api/peerconnection/RTCLegacyStatsReport.mm",
|
||||||
"objc/api/peerconnection/RTCPeerConnection.mm",
|
"objc/api/peerconnection/RTCPeerConnection.mm",
|
||||||
"objc/api/peerconnection/RTCStatisticsReport.mm",
|
"objc/api/peerconnection/RTCStatisticsReport.mm",
|
||||||
@ -3165,7 +3143,6 @@ ios_sources = [
|
|||||||
"objc/api/peerconnection/RTCRtcpParameters.mm",
|
"objc/api/peerconnection/RTCRtcpParameters.mm",
|
||||||
"media_constraints.cc",
|
"media_constraints.cc",
|
||||||
"media_constraints.h",
|
"media_constraints.h",
|
||||||
"objc/native/api/audio_device_module.mm",
|
|
||||||
"objc/helpers/UIDevice+RTCDevice.h",
|
"objc/helpers/UIDevice+RTCDevice.h",
|
||||||
"objc/Framework/Classes/VideoToolbox/nalu_rewriter.h",
|
"objc/Framework/Classes/VideoToolbox/nalu_rewriter.h",
|
||||||
"objc/api/RTCVideoRendererAdapter.h",
|
"objc/api/RTCVideoRendererAdapter.h",
|
||||||
@ -3195,7 +3172,6 @@ ios_sources = [
|
|||||||
"objc/api/peerconnection/RTCMediaStreamTrack+Private.h",
|
"objc/api/peerconnection/RTCMediaStreamTrack+Private.h",
|
||||||
"objc/api/peerconnection/RTCDataChannelConfiguration+Private.h",
|
"objc/api/peerconnection/RTCDataChannelConfiguration+Private.h",
|
||||||
"objc/api/peerconnection/RTCPeerConnectionFactory+Private.h",
|
"objc/api/peerconnection/RTCPeerConnectionFactory+Private.h",
|
||||||
"objc/api/peerconnection/RTCPeerConnectionFactoryBuilder+DefaultComponents.h",
|
|
||||||
"objc/api/peerconnection/RTCRtpReceiver+Native.h",
|
"objc/api/peerconnection/RTCRtpReceiver+Native.h",
|
||||||
"objc/api/peerconnection/RTCMediaConstraints+Private.h",
|
"objc/api/peerconnection/RTCMediaConstraints+Private.h",
|
||||||
"objc/api/peerconnection/RTCMediaConstraints.h",
|
"objc/api/peerconnection/RTCMediaConstraints.h",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user