Stream improvements

This commit is contained in:
Ali 2022-02-26 02:22:10 +04:00
parent 6fb5008c0f
commit 089642a0bb
35 changed files with 1227 additions and 309 deletions

View File

@ -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)
} }
} }

View File

@ -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

View File

@ -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>()
}
} }

View File

@ -118,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 {
@ -131,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 {

View File

@ -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 {

View File

@ -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)
}
} }

View 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)
}
}

View File

@ -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)
} }
} }

View File

@ -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)

View File

@ -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)
} }
} }

View File

@ -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))

View File

@ -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)

View File

@ -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)
}
}

View 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)
}
}

View File

@ -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)
} }
} }

View File

@ -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)
} }
} }

View File

@ -86,26 +86,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
@ -279,6 +259,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
@ -880,7 +880,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
@ -973,7 +973,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
@ -1310,7 +1310,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

View File

@ -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)
} }
} }

View File

@ -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

View File

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

View File

@ -4,39 +4,396 @@ import ComponentFlow
import Display import Display
import AccountContext import AccountContext
import SwiftSignalKit 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 { 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 leftItem: AnyComponent<Empty>?
let rightItem: AnyComponent<Empty>? let rightItem: AnyComponent<Empty>?
let centerItem: AnyComponent<Empty>? let centerItem: AnyComponent<Empty>?
init( init(
bottomInset: CGFloat,
sideInset: CGFloat,
leftItem: AnyComponent<Empty>?, leftItem: AnyComponent<Empty>?,
rightItem: AnyComponent<Empty>?, rightItem: AnyComponent<Empty>?,
centerItem: AnyComponent<Empty>? centerItem: AnyComponent<Empty>?
) { ) {
self.bottomInset = bottomInset
self.sideInset = sideInset
self.leftItem = leftItem self.leftItem = leftItem
self.rightItem = rightItem self.rightItem = rightItem
self.centerItem = centerItem self.centerItem = centerItem
} }
static func ==(lhs: NavigationBarComponent, rhs: NavigationBarComponent) -> Bool { 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 return true
} }
static var body: Body { static var body: Body {
let background = Child(Rectangle.self)
let leftItem = Child(environment: Empty.self) let leftItem = Child(environment: Empty.self)
let rightItem = Child(environment: Empty.self) let rightItem = Child(environment: Empty.self)
let centerItem = Child(environment: Empty.self) let centerItem = Child(environment: Empty.self)
return { context in return { context in
var availableWidth = context.availableSize.width var availableWidth = context.availableSize.width
let sideInset: CGFloat = 16.0 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 let leftItem = context.component.leftItem.flatMap { leftItemComponent in
return leftItem.update( return leftItem.update(
component: leftItemComponent, component: leftItemComponent,
availableSize: CGSize(width: availableWidth, height: context.availableSize.height), availableSize: CGSize(width: availableWidth, height: contentHeight),
transition: context.transition transition: context.transition
) )
} }
@ -47,7 +404,7 @@ private final class NavigationBarComponent: CombinedComponent {
let rightItem = context.component.rightItem.flatMap { rightItemComponent in let rightItem = context.component.rightItem.flatMap { rightItemComponent in
return rightItem.update( return rightItem.update(
component: rightItemComponent, component: rightItemComponent,
availableSize: CGSize(width: availableWidth, height: context.availableSize.height), availableSize: CGSize(width: availableWidth, height: contentHeight),
transition: context.transition transition: context.transition
) )
} }
@ -58,7 +415,7 @@ private final class NavigationBarComponent: CombinedComponent {
let centerItem = context.component.centerItem.flatMap { centerItemComponent in let centerItem = context.component.centerItem.flatMap { centerItemComponent in
return centerItem.update( return centerItem.update(
component: centerItemComponent, component: centerItemComponent,
availableSize: CGSize(width: availableWidth, height: context.availableSize.height), availableSize: CGSize(width: availableWidth, height: contentHeight),
transition: context.transition transition: context.transition
) )
} }
@ -66,10 +423,14 @@ private final class NavigationBarComponent: CombinedComponent {
availableWidth -= centerItem.size.width availableWidth -= centerItem.size.width
} }
context.add(background
.position(CGPoint(x: size.width / 2.0, y: size.height / 2.0))
)
var centerLeftInset = sideInset var centerLeftInset = sideInset
if let leftItem = leftItem { if let leftItem = leftItem {
context.add(leftItem context.add(leftItem
.position(CGPoint(x: sideInset + leftItem.size.width / 2.0, y: context.availableSize.height / 2.0)) .position(CGPoint(x: sideInset + leftItem.size.width / 2.0, y: contentHeight / 2.0))
) )
centerLeftInset += leftItem.size.width + 4.0 centerLeftInset += leftItem.size.width + 4.0
} }
@ -77,7 +438,7 @@ private final class NavigationBarComponent: CombinedComponent {
var centerRightInset = sideInset var centerRightInset = sideInset
if let rightItem = rightItem { if let rightItem = rightItem {
context.add(rightItem context.add(rightItem
.position(CGPoint(x: context.availableSize.width - sideInset - rightItem.size.width / 2.0, y: context.availableSize.height / 2.0)) .position(CGPoint(x: context.availableSize.width - sideInset - rightItem.size.width / 2.0, y: contentHeight / 2.0))
) )
centerRightInset += rightItem.size.width + 4.0 centerRightInset += rightItem.size.width + 4.0
} }
@ -85,16 +446,21 @@ private final class NavigationBarComponent: CombinedComponent {
let maxCenterInset = max(centerLeftInset, centerRightInset) let maxCenterInset = max(centerLeftInset, centerRightInset)
if let centerItem = centerItem { if let centerItem = centerItem {
context.add(centerItem context.add(centerItem
.position(CGPoint(x: maxCenterInset + (context.availableSize.width - maxCenterInset - maxCenterInset) / 2.0, y: context.availableSize.height / 2.0)) .position(CGPoint(x: maxCenterInset + (context.availableSize.width - maxCenterInset - maxCenterInset) / 2.0, y: contentHeight / 2.0))
) )
} }
return context.availableSize return size
} }
} }
} }
public final class MediaStreamComponent: CombinedComponent { public final class MediaStreamComponent: CombinedComponent {
struct OriginInfo: Equatable {
var title: String
var memberCount: Int
}
public typealias EnvironmentType = ViewControllerComponentContainer.Environment public typealias EnvironmentType = ViewControllerComponentContainer.Environment
public let call: PresentationGroupCallImpl public let call: PresentationGroupCallImpl
@ -116,10 +482,24 @@ public final class MediaStreamComponent: CombinedComponent {
private(set) var hasVideo: Bool = false private(set) var hasVideo: Bool = false
private var stateDisposable: Disposable? 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) { init(call: PresentationGroupCallImpl) {
self.call = call self.call = call
if #available(iOSApplicationExtension 15.0, iOS 15.0, *), AVPictureInPictureController.isPictureInPictureSupported() {
self.isPictureInPictureSupported = true
} else {
self.isPictureInPictureSupported = false
}
super.init() super.init()
self.stateDisposable = (call.state self.stateDisposable = (call.state
@ -140,11 +520,44 @@ public final class MediaStreamComponent: CombinedComponent {
strongSelf.updated(transition: .immediate) 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() let _ = call.accountContext.engine.calls.getGroupCallStreamCredentials(peerId: call.peerId, revokePreviousCredentials: false).start()
} }
deinit { deinit {
self.stateDisposable?.dispose() 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)))
}
} }
} }
@ -156,9 +569,15 @@ public final class MediaStreamComponent: CombinedComponent {
let background = Child(Rectangle.self) let background = Child(Rectangle.self)
let video = Child(MediaStreamVideoComponent.self) let video = Child(MediaStreamVideoComponent.self)
let navigationBar = Child(NavigationBarComponent.self) let navigationBar = Child(NavigationBarComponent.self)
let toolbar = Child(ToolbarComponent.self)
let activatePictureInPicture = StoredActionSlot(Action<Void>.self)
return { context in return { context in
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
if !environment.isVisible {
context.state.dismissOffset = 0.0
}
let background = background.update( let background = background.update(
component: Rectangle(color: .black), component: Rectangle(color: .black),
@ -166,44 +585,190 @@ public final class MediaStreamComponent: CombinedComponent {
transition: context.transition transition: context.transition
) )
let call = context.component.call
let controller = environment.controller
let video = Condition(context.state.hasVideo) { let video = Condition(context.state.hasVideo) {
return video.update( return video.update(
component: MediaStreamVideoComponent( component: MediaStreamVideoComponent(
call: context.component.call 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: CGSize(width: context.availableSize.width, height: floor(context.availableSize.width * 9.0 / 16.0)), availableSize: context.availableSize,
transition: context.transition transition: context.transition
) )
} }
let call = context.component.call 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( let navigationBar = navigationBar.update(
component: NavigationBarComponent( component: NavigationBarComponent(
topInset: environment.statusBarHeight,
sideInset: environment.safeInsets.left,
leftItem: AnyComponent(Button( leftItem: AnyComponent(Button(
content: AnyComponent(Text(text: "Leave", font: Font.regular(17.0), color: .white)), content: AnyComponent(NavigationBackButtonComponent(text: environment.strings.Common_Back, color: .white)),
insets: UIEdgeInsets(),
action: { [weak call] in action: { [weak call] in
let _ = call?.leave(terminateIfPossible: false) let _ = call?.leave(terminateIfPossible: false)
}) })
), ),
rightItem: nil, rightItems: navigationRightItems,
centerItem: AnyComponent(Text(text: "Live Stream", font: Font.semibold(17.0), color: .white)) centerItem: AnyComponent(Text(text: "Live Stream", font: Font.semibold(17.0), color: .white))
), ),
availableSize: CGSize(width: context.availableSize.width, height: 44.0), availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height),
transition: context.transition 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 context.add(background
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0)) .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 { if let video = video {
context.add(video context.add(video
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))) .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0 + context.state.dismissOffset))
)
} }
context.add(navigationBar context.add(navigationBar
.position(CGPoint(x: context.availableSize.width / 2.0, y: environment.statusBarHeight + navigationBar.size.height / 2.0)) .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 return context.availableSize
@ -219,24 +784,41 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
public var onViewDidAppear: (() -> Void)? public var onViewDidAppear: (() -> Void)?
public var onViewDidDisappear: (() -> Void)? public var onViewDidDisappear: (() -> Void)?
private var initialOrientation: UIInterfaceOrientation?
public init(call: PresentationGroupCall) { public init(call: PresentationGroupCall) {
self.call = call self.call = call
super.init(MediaStreamComponent(call: call as! PresentationGroupCallImpl)) super.init(context: call.accountContext, component: MediaStreamComponent(call: call as! PresentationGroupCallImpl))
self.statusBar.statusBarStyle = .White self.statusBar.statusBarStyle = .White
self.view.disablesInteractiveModalDismiss = true
} }
required public init(coder aDecoder: NSCoder) { required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented") 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) { override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated) super.viewDidAppear(animated)
DispatchQueue.main.async { DispatchQueue.main.async {
self.onViewDidAppear?() 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) { override public func viewDidDisappear(_ animated: Bool) {
@ -248,6 +830,115 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
} }
public func dismiss(closing: Bool, manual: Bool) { public func dismiss(closing: Bool, manual: Bool) {
self.dismiss(animated: true, completion: nil) 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))
}
})
} }
} }

View File

@ -2,12 +2,17 @@ import Foundation
import UIKit import UIKit
import ComponentFlow import ComponentFlow
import AccountContext import AccountContext
import AVKit
final class MediaStreamVideoComponent: Component { final class MediaStreamVideoComponent: Component {
let call: PresentationGroupCallImpl let call: PresentationGroupCallImpl
let activatePictureInPicture: ActionSlot<Action<Void>>
let bringBackControllerForPictureInPictureDeactivation: (@escaping () -> Void) -> Void
init(call: PresentationGroupCallImpl) { init(call: PresentationGroupCallImpl, activatePictureInPicture: ActionSlot<Action<Void>>, bringBackControllerForPictureInPictureDeactivation: @escaping (@escaping () -> Void) -> Void) {
self.call = call self.call = call
self.activatePictureInPicture = activatePictureInPicture
self.bringBackControllerForPictureInPictureDeactivation = bringBackControllerForPictureInPictureDeactivation
} }
public static func ==(lhs: MediaStreamVideoComponent, rhs: MediaStreamVideoComponent) -> Bool { public static func ==(lhs: MediaStreamVideoComponent, rhs: MediaStreamVideoComponent) -> Bool {
@ -18,34 +23,145 @@ final class MediaStreamVideoComponent: Component {
return true return true
} }
public final class View: UIView { 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 let videoRenderingContext = VideoRenderingContext()
private var videoView: VideoRenderingView? private var videoView: VideoRenderingView?
private let blurTintView: UIView
private var videoBlurView: VideoRenderingView?
func update(component: MediaStreamVideoComponent, availableSize: CGSize, transition: Transition) -> CGSize { 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 self.videoView == nil {
if let input = component.call.video(endpointId: "unified") { if let input = component.call.video(endpointId: "unified") {
if let videoView = self.videoRenderingContext.makeView(input: input, blur: false) { 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.videoView = videoView
self.addSubview(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 { if let videoView = self.videoView {
videoView.updateIsEnabled(true) videoView.updateIsEnabled(true)
videoView.frame = CGRect(origin: CGPoint(), size: availableSize) 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 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 { public func makeView() -> View {
return View() return View(frame: CGRect())
} }
public func update(view: View, availableSize: CGSize, environment: Environment<Empty>, transition: Transition) -> CGSize { public func update(view: View, availableSize: CGSize, state: State, environment: Environment<Empty>, transition: Transition) -> CGSize {
return view.update(component: self, availableSize: availableSize, transition: transition) return view.update(component: self, availableSize: availableSize, state: state, transition: transition)
} }
} }

View File

@ -3,6 +3,8 @@ import UIKit
import ComponentFlow import ComponentFlow
import Display import Display
import SwiftSignalKit import SwiftSignalKit
import TelegramPresentationData
import AccountContext
public extension Transition.Animation.Curve { public extension Transition.Animation.Curve {
init(_ curve: ContainedViewLayoutTransitionCurve) { init(_ curve: ContainedViewLayoutTransitionCurve) {
@ -36,34 +38,59 @@ open class ViewControllerComponentContainer: ViewController {
public final class Environment: Equatable { public final class Environment: Equatable {
public let statusBarHeight: CGFloat public let statusBarHeight: CGFloat
public let safeInsets: UIEdgeInsets public let safeInsets: UIEdgeInsets
public let isVisible: Bool
public let strings: PresentationStrings
public let controller: () -> ViewController?
public init( public init(
statusBarHeight: CGFloat, statusBarHeight: CGFloat,
safeInsets: UIEdgeInsets safeInsets: UIEdgeInsets,
isVisible: Bool,
strings: PresentationStrings,
controller: @escaping () -> ViewController?
) { ) {
self.statusBarHeight = statusBarHeight self.statusBarHeight = statusBarHeight
self.safeInsets = safeInsets self.safeInsets = safeInsets
self.isVisible = isVisible
self.strings = strings
self.controller = controller
} }
public static func ==(lhs: Environment, rhs: Environment) -> Bool { public static func ==(lhs: Environment, rhs: Environment) -> Bool {
if lhs === rhs {
return true
}
if lhs.statusBarHeight != rhs.statusBarHeight { if lhs.statusBarHeight != rhs.statusBarHeight {
return false return false
} }
if lhs.safeInsets != rhs.safeInsets { if lhs.safeInsets != rhs.safeInsets {
return false return false
} }
if lhs.isVisible != rhs.isVisible {
return false
}
if lhs.strings !== rhs.strings {
return false
}
return true return true
} }
} }
private final class Node: ViewControllerTracingNode { private final class Node: ViewControllerTracingNode {
private var presentationData: PresentationData
private weak var controller: ViewControllerComponentContainer? private weak var controller: ViewControllerComponentContainer?
private let component: AnyComponent<ViewControllerComponentContainer.Environment> private let component: AnyComponent<ViewControllerComponentContainer.Environment>
private let hostView: ComponentHostView<ViewControllerComponentContainer.Environment> private let hostView: ComponentHostView<ViewControllerComponentContainer.Environment>
init(controller: ViewControllerComponentContainer, component: AnyComponent<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.controller = controller
self.component = component self.component = component
@ -74,20 +101,39 @@ open class ViewControllerComponentContainer: ViewController {
self.view.addSubview(self.hostView) self.view.addSubview(self.hostView)
} }
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: Transition) {
self.currentLayout = layout
let environment = ViewControllerComponentContainer.Environment( let environment = ViewControllerComponentContainer.Environment(
statusBarHeight: layout.statusBarHeight ?? 0.0, statusBarHeight: layout.statusBarHeight ?? 0.0,
safeInsets: layout.intrinsicInsets 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( let _ = self.hostView.update(
transition: Transition(transition), transition: transition,
component: self.component, component: self.component,
environment: { environment: {
environment environment
}, },
containerSize: layout.size containerSize: layout.size
) )
transition.updateFrame(view: self.hostView, frame: CGRect(origin: CGPoint(), size: 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)
} }
} }
@ -95,9 +141,11 @@ open class ViewControllerComponentContainer: ViewController {
return self.displayNode as! Node return self.displayNode as! Node
} }
private let context: AccountContext
private let component: AnyComponent<ViewControllerComponentContainer.Environment> private let component: AnyComponent<ViewControllerComponentContainer.Environment>
public init<C: Component>(_ component: C) where C.EnvironmentType == ViewControllerComponentContainer.Environment { public init<C: Component>(context: AccountContext, component: C) where C.EnvironmentType == ViewControllerComponentContainer.Environment {
self.context = context
self.component = AnyComponent(component) self.component = AnyComponent(component)
super.init(navigationBarPresentationData: nil) super.init(navigationBarPresentationData: nil)
@ -108,14 +156,26 @@ open class ViewControllerComponentContainer: ViewController {
} }
override open func loadDisplayNode() { override open func loadDisplayNode() {
self.displayNode = Node(controller: self, component: self.component) self.displayNode = Node(context: self.context, controller: self, component: self.component)
self.displayNodeDidLoad() 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) { override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition) super.containerLayoutUpdated(layout, transition: transition)
self.node.containerLayoutUpdated(layout, transition: transition) self.node.containerLayoutUpdated(layout, transition: Transition(transition))
} }
} }

View File

@ -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

View File

@ -679,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)
@ -725,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
@ -1355,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)
} }
@ -1427,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
@ -1521,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)
@ -1893,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,
@ -1915,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
@ -2712,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
@ -2823,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

View File

@ -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
} }

View File

@ -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 {

View File

@ -2534,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

View File

@ -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)
} }
} }

View File

@ -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)
}) })
} }

View File

@ -8,8 +8,6 @@ config_setting(
optimization_flags = select({ optimization_flags = select({
":debug_build": [ ":debug_build": [
"-O2",
"-DNDEBUG",
], ],
"//conditions:default": ["-DNDEBUG"], "//conditions:default": ["-DNDEBUG"],
}) })

View File

@ -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;

View File

@ -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 a5ae22266f4113c60dd21bc405371b98af0359cd Subproject commit 25177c8e7354c5e3ac892ad3561c26915eb24d4e

View File

@ -10,8 +10,6 @@ config_setting(
optimization_flags = select({ optimization_flags = select({
":debug_build": [ ":debug_build": [
"-O2",
"-DNDEBUG",
], ],
"//conditions:default": ["-DNDEBUG"], "//conditions:default": ["-DNDEBUG"],
}) })