mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Refactoring
This commit is contained in:
parent
cd09a54fe4
commit
197128c3fe
@ -477,7 +477,7 @@ public final class StandaloneShimmerEffect {
|
|||||||
self.updateLayer()
|
self.updateLayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func testUpdate(background: UIColor, foreground: UIColor) {
|
public func updateHorizontal(background: UIColor, foreground: UIColor) {
|
||||||
if self.background == background && self.foreground == foreground {
|
if self.background == background && self.foreground == foreground {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -503,7 +503,7 @@ public final class StandaloneShimmerEffect {
|
|||||||
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.3), options: CGGradientDrawingOptions())
|
context.drawLinearGradient(gradient, start: CGPoint(x: 0.0, y: 0.0), end: CGPoint(x: size.width, y: 0.3), options: CGGradientDrawingOptions())
|
||||||
})
|
})
|
||||||
|
|
||||||
self.testUpdateLayer()
|
self.updateHorizontalLayer()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func updateLayer() {
|
public func updateLayer() {
|
||||||
@ -525,7 +525,7 @@ public final class StandaloneShimmerEffect {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func testUpdateLayer() {
|
private func updateHorizontalLayer() {
|
||||||
guard let layer = self.layer, let image = self.image else {
|
guard let layer = self.layer, let image = self.image else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ private let latePink = UIColor(rgb: 0xf0436c)
|
|||||||
|
|
||||||
public final class AnimatedCountView: UIView {
|
public final class AnimatedCountView: UIView {
|
||||||
let countLabel = AnimatedCountLabel()
|
let countLabel = AnimatedCountLabel()
|
||||||
// let titleLabel = UILabel()
|
|
||||||
let subtitleLabel = UILabel()
|
let subtitleLabel = UILabel()
|
||||||
|
|
||||||
private let foregroundView = UIView()
|
private let foregroundView = UIView()
|
||||||
@ -31,7 +30,6 @@ public final class AnimatedCountView: UIView {
|
|||||||
self.foregroundView.layer.addSublayer(self.foregroundGradientLayer)
|
self.foregroundView.layer.addSublayer(self.foregroundGradientLayer)
|
||||||
|
|
||||||
self.addSubview(self.foregroundView)
|
self.addSubview(self.foregroundView)
|
||||||
// self.addSubview(self.titleLabel)
|
|
||||||
self.addSubview(self.subtitleLabel)
|
self.addSubview(self.subtitleLabel)
|
||||||
|
|
||||||
self.maskingView.addSubview(countLabel)
|
self.maskingView.addSubview(countLabel)
|
||||||
@ -40,7 +38,6 @@ public final class AnimatedCountView: UIView {
|
|||||||
self.clipsToBounds = false
|
self.clipsToBounds = false
|
||||||
|
|
||||||
subtitleLabel.textColor = .white
|
subtitleLabel.textColor = .white
|
||||||
// self.backgroundColor = UIColor.white.withAlphaComponent(0.1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func layoutSubviews() {
|
override public func layoutSubviews() {
|
||||||
@ -56,34 +53,12 @@ public final class AnimatedCountView: UIView {
|
|||||||
func update(countString: String, subtitle: String) {
|
func update(countString: String, subtitle: String) {
|
||||||
self.setupGradientAnimations()
|
self.setupGradientAnimations()
|
||||||
|
|
||||||
let text: String = countString// presentationStringsFormattedNumber(Int32(count), ",")
|
let text: String = countString
|
||||||
|
|
||||||
// self.titleNode.attributedText = NSAttributedString(string: "", font: Font.with(size: 23.0, design: .round, weight: .semibold, traits: []), textColor: .white)
|
|
||||||
// let titleSize = self.titleNode.updateLayout(size)
|
|
||||||
// self.titleNode.frame = CGRect(x: floor((size.width - titleSize.width) / 2.0), y: 48.0, width: titleSize.width, height: titleSize.height)
|
|
||||||
// if CGFloat(text.count * 40) < bounds.width - 32 {
|
|
||||||
// self.countLabel.attributedText = NSAttributedString(string: text, attributes: [.font: UIFont.systemFont(ofSize: 60, weight: .semibold)])
|
|
||||||
// } else {
|
|
||||||
// self.countLabel.attributedText = NSAttributedString(string: text, attributes: [.font: UIFont.systemFont(ofSize: 54, weight: .semibold)])
|
|
||||||
//
|
|
||||||
self.countLabel.fontSize = 48
|
self.countLabel.fontSize = 48
|
||||||
self.countLabel.attributedText = NSAttributedString(string: text, font: Font.with(size: 48, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: .white)
|
self.countLabel.attributedText = NSAttributedString(string: text, font: Font.with(size: 48, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: .white)
|
||||||
// self.countLabel.attributedText = NSAttributedString(string: text, attributes: [.font: UIFont.systemFont(ofSize: 60, weight: .semibold)])
|
|
||||||
// var timerSize = self.timerNode.updateLayout(CGSize(width: size.width + 100.0, height: size.height))
|
|
||||||
// if timerSize.width > size.width - 32.0 {
|
|
||||||
// self.timerNode.attributedText = NSAttributedString(string: text, font: Font.with(size: 60.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: .white)
|
|
||||||
// timerSize = self.timerNode.updateLayout(CGSize(width: size.width + 100.0, height: size.height))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// self.timerNode.frame = CGRect(x: floor((size.width - timerSize.width) / 2.0), y: 78.0, width: timerSize.width, height: timerSize.height)
|
|
||||||
|
|
||||||
self.subtitleLabel.attributedText = NSAttributedString(string: subtitle, attributes: [.font: UIFont.systemFont(ofSize: 16, weight: .semibold)])
|
self.subtitleLabel.attributedText = NSAttributedString(string: subtitle, attributes: [.font: UIFont.systemFont(ofSize: 16, weight: .semibold)])
|
||||||
self.subtitleLabel.isHidden = subtitle.isEmpty
|
self.subtitleLabel.isHidden = subtitle.isEmpty
|
||||||
// let subtitleSize = self.subtitleNode.updateLayout(size)
|
|
||||||
// self.subtitleNode.frame = CGRect(x: floor((size.width - subtitleSize.width) / 2.0), y: 164.0, width: subtitleSize.width, height: subtitleSize.height)
|
|
||||||
|
|
||||||
// self.foregroundView.frame = CGRect(origin: CGPoint(), size: size)
|
|
||||||
// self.setNeedsLayout()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
@ -105,9 +80,7 @@ public final class AnimatedCountView: UIView {
|
|||||||
animation.toValue = newValue
|
animation.toValue = newValue
|
||||||
|
|
||||||
CATransaction.setCompletionBlock { [weak self] in
|
CATransaction.setCompletionBlock { [weak self] in
|
||||||
// if let isCurrentlyInHierarchy = self?.isCurrentlyInHierarchy, isCurrentlyInHierarchy {
|
self?.setupGradientAnimations()
|
||||||
self?.setupGradientAnimations()
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
self.foregroundGradientLayer.add(animation, forKey: "movement")
|
self.foregroundGradientLayer.add(animation, forKey: "movement")
|
||||||
CATransaction.commit()
|
CATransaction.commit()
|
||||||
@ -126,7 +99,7 @@ class AnimatedCharLayer: CATextLayer {
|
|||||||
}
|
}
|
||||||
var attributedText: NSAttributedString? {
|
var attributedText: NSAttributedString? {
|
||||||
get {
|
get {
|
||||||
self.string as? NSAttributedString //?? (self.string as? String).map { NSAttributed.init
|
self.string as? NSAttributedString
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
self.string = newValue
|
self.string = newValue
|
||||||
@ -214,37 +187,17 @@ class AnimatedCountLabel: UILabel {
|
|||||||
}
|
}
|
||||||
return offset
|
return offset
|
||||||
} else {
|
} else {
|
||||||
var offset = self.chars[0..<index].reduce(0) {
|
return offsetForChar(at: index, within: self.chars.compactMap(\.attributedText))
|
||||||
if $1.attributedText?.string == "," {
|
|
||||||
return $0 + commaWidthForSpacing + interItemSpacing
|
|
||||||
}
|
|
||||||
return $0 + itemWidth + interItemSpacing
|
|
||||||
}
|
|
||||||
if self.chars.count > index && self.chars[index].attributedText?.string == "," {
|
|
||||||
if index > 0, let prevChar = self.chars[index - 1].attributedText?.string, ["1", "7"].contains(prevChar) {
|
|
||||||
offset -= commaWidthForSpacing * 0.7
|
|
||||||
} else {
|
|
||||||
offset -= commaWidthForSpacing / 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return offset
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutSubviews() {
|
override func layoutSubviews() {
|
||||||
super.layoutSubviews()
|
super.layoutSubviews()
|
||||||
let countWidth = offsetForChar(at: chars.count) /*chars.reduce(0) {
|
let countWidth = offsetForChar(at: chars.count) - interItemSpacing
|
||||||
if $1.attributedText?.string == "," {
|
|
||||||
return $0 + commaWidth + interItemSpacing
|
|
||||||
}
|
|
||||||
return $0 + itemWidth + interItemSpacing
|
|
||||||
}*/ - interItemSpacing
|
|
||||||
containerView.frame = .init(x: bounds.midX - countWidth / 2 * scaleFactor, y: 0, width: countWidth * scaleFactor, height: bounds.height)
|
containerView.frame = .init(x: bounds.midX - countWidth / 2 * scaleFactor, y: 0, width: countWidth * scaleFactor, height: bounds.height)
|
||||||
chars.enumerated().forEach { (index, char) in
|
chars.enumerated().forEach { (index, char) in
|
||||||
let offset = offsetForChar(at: index)
|
let offset = offsetForChar(at: index)
|
||||||
// char.frame.size.width = char.attributedText?.string == "," ? commaFrameWidth : itemWidth
|
|
||||||
char.frame.origin.x = offset
|
char.frame.origin.x = offset
|
||||||
// char.frame.origin.x = CGFloat(chars.count - 1 - index) * (40 + interItemSpacing)
|
|
||||||
char.frame.origin.y = 0
|
char.frame.origin.y = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -274,10 +227,7 @@ class AnimatedCountLabel: UILabel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let initialDuration: TimeInterval = min(0.25, maxAnimationDuration / Double(numberOfChanges)) /// 0.25
|
let initialDuration: TimeInterval = min(0.25, maxAnimationDuration / Double(numberOfChanges))
|
||||||
|
|
||||||
// let currentWidth = itemWidth * CGFloat(currentChars.count)
|
|
||||||
// let newWidth = itemWidth * CGFloat(newChars.count)
|
|
||||||
|
|
||||||
let interItemDelay: TimeInterval = 0.08
|
let interItemDelay: TimeInterval = 0.08
|
||||||
var changeIndex = 0
|
var changeIndex = 0
|
||||||
@ -288,11 +238,7 @@ class AnimatedCountLabel: UILabel {
|
|||||||
let newCharIndex = newChars.count - 1 - index
|
let newCharIndex = newChars.count - 1 - index
|
||||||
let currCharIndex = currentChars.count - 1 - index
|
let currCharIndex = currentChars.count - 1 - index
|
||||||
|
|
||||||
if true || newChars[newCharIndex] != currentChars[currCharIndex] {
|
if newChars[newCharIndex] != currentChars[currCharIndex] {
|
||||||
// if newChars[newCharIndex].string != "," {
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
|
|
||||||
let initialDuration = newChars[newCharIndex] != currentChars[currCharIndex] ? initialDuration : 0
|
let initialDuration = newChars[newCharIndex] != currentChars[currCharIndex] ? initialDuration : 0
|
||||||
|
|
||||||
if !isInitialSet && newChars[newCharIndex] != currentChars[currCharIndex] {
|
if !isInitialSet && newChars[newCharIndex] != currentChars[currCharIndex] {
|
||||||
@ -302,19 +248,14 @@ class AnimatedCountLabel: UILabel {
|
|||||||
}
|
}
|
||||||
let newLayer = AnimatedCharLayer()
|
let newLayer = AnimatedCharLayer()
|
||||||
newLayer.attributedText = newChars[newCharIndex]
|
newLayer.attributedText = newChars[newCharIndex]
|
||||||
let offset = offsetForChar(at: newCharIndex, within: newChars)/* newChars[0..<newCharIndex].reduce(0) {
|
let offset = offsetForChar(at: newCharIndex, within: newChars)
|
||||||
if $1.string == "," {
|
|
||||||
return $0 + commaWidth + interItemSpacing
|
|
||||||
}
|
|
||||||
return $0 + itemWidth + interItemSpacing
|
|
||||||
}*/
|
|
||||||
newLayer.frame = .init(
|
newLayer.frame = .init(
|
||||||
x: offset/*CGFloat(newCharIndex) * (40 + interItemSpacing)*/,
|
x: offset,
|
||||||
y: 0,
|
y: 0,
|
||||||
width: newChars[newCharIndex].string == "," ? commaFrameWidth : itemWidth,
|
width: newChars[newCharIndex].string == "," ? commaFrameWidth : itemWidth,
|
||||||
height: itemWidth * 1.8 + (newChars[newCharIndex].string == "," ? 4 : 0)
|
height: itemWidth * 1.8 + (newChars[newCharIndex].string == "," ? 4 : 0)
|
||||||
)
|
)
|
||||||
// newLayer.frame = .init(x: CGFloat(chars.count - 1 - index) * (40 + interItemSpacing), y: 0, width: itemWidth, height: itemWidth * 1.8)
|
|
||||||
containerView.layer.addSublayer(newLayer)
|
containerView.layer.addSublayer(newLayer)
|
||||||
if !isInitialSet && newChars[newCharIndex] != currentChars[currCharIndex] {
|
if !isInitialSet && newChars[newCharIndex] != currentChars[currCharIndex] {
|
||||||
newLayer.layer.opacity = 0
|
newLayer.layer.opacity = 0
|
||||||
@ -322,19 +263,20 @@ class AnimatedCountLabel: UILabel {
|
|||||||
changeIndex += 1
|
changeIndex += 1
|
||||||
}
|
}
|
||||||
newLayers.append(newLayer)
|
newLayers.append(newLayer)
|
||||||
// if newChars[newCharIndex].string == "," {
|
|
||||||
// newLayer.backgroundColor = UIColor.systemBlue.withAlphaComponent(0.6).cgColor
|
|
||||||
// } else {
|
|
||||||
// newLayer.backgroundColor = UIColor.green.withAlphaComponent(0.6).cgColor
|
|
||||||
// }
|
|
||||||
} else {
|
} else {
|
||||||
newLayers.append(chars[currCharIndex])
|
newLayers.append(chars[currCharIndex])
|
||||||
|
let offset = offsetForChar(at: newCharIndex, within: newChars)
|
||||||
|
chars[currCharIndex].frame = .init(
|
||||||
|
x: offset,
|
||||||
|
y: 0,
|
||||||
|
width: newChars[newCharIndex].string == "," ? commaFrameWidth : itemWidth,
|
||||||
|
height: itemWidth * 1.8 + (newChars[newCharIndex].string == "," ? 4 : 0)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for index in min(newChars.count, currentChars.count)..<currentChars.count {
|
for index in min(newChars.count, currentChars.count)..<currentChars.count {
|
||||||
let currCharIndex = currentChars.count - 1 - index
|
let currCharIndex = currentChars.count - 1 - index
|
||||||
// remove unused
|
|
||||||
animateOut(for: chars[currCharIndex].layer, duration: initialDuration, beginTime: TimeInterval(changeIndex) * interItemDelay)
|
animateOut(for: chars[currCharIndex].layer, duration: initialDuration, beginTime: TimeInterval(changeIndex) * interItemDelay)
|
||||||
changeIndex += 1
|
changeIndex += 1
|
||||||
}
|
}
|
||||||
@ -342,19 +284,11 @@ class AnimatedCountLabel: UILabel {
|
|||||||
for index in min(newChars.count, currentChars.count)..<newChars.count {
|
for index in min(newChars.count, currentChars.count)..<newChars.count {
|
||||||
|
|
||||||
let newCharIndex = newChars.count - 1 - index
|
let newCharIndex = newChars.count - 1 - index
|
||||||
// if newChars[newCharIndex].string != "," {
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
let newLayer = AnimatedCharLayer()
|
let newLayer = AnimatedCharLayer()
|
||||||
newLayer.attributedText = newChars[newCharIndex]
|
newLayer.attributedText = newChars[newCharIndex]
|
||||||
|
|
||||||
let offset = offsetForChar(at: newCharIndex, within: newChars)/*newChars[0..<newCharIndex].reduce(0) {
|
let offset = offsetForChar(at: newCharIndex, within: newChars)
|
||||||
if $1.string == "," {
|
newLayer.frame = .init(x: offset, y: 0, width: newChars[newCharIndex].string == "," ? commaFrameWidth : itemWidth, height: itemWidth * 1.8 + (newChars[newCharIndex].string == "," ? 4 : 0))
|
||||||
return $0 + commaWidth + interItemSpacing
|
|
||||||
}
|
|
||||||
return $0 + itemWidth + interItemSpacing
|
|
||||||
}*/
|
|
||||||
newLayer.frame = .init(x: offset/*CGFloat(newCharIndex) * (40 + interItemSpacing)*/, y: 0, width: newChars[newCharIndex].string == "," ? commaFrameWidth : itemWidth, height: itemWidth * 1.8 + (newChars[newCharIndex].string == "," ? 4 : 0))
|
|
||||||
containerView.layer.addSublayer(newLayer)
|
containerView.layer.addSublayer(newLayer)
|
||||||
if !isInitialSet {
|
if !isInitialSet {
|
||||||
animateIn(for: newLayer.layer, duration: initialDuration, beginTime: TimeInterval(changeIndex) * interItemDelay)
|
animateIn(for: newLayer.layer, duration: initialDuration, beginTime: TimeInterval(changeIndex) * interItemDelay)
|
||||||
@ -365,14 +299,9 @@ class AnimatedCountLabel: UILabel {
|
|||||||
let prevCount = chars.count
|
let prevCount = chars.count
|
||||||
chars = newLayers.reversed()
|
chars = newLayers.reversed()
|
||||||
|
|
||||||
let countWidth = offsetForChar(at: newChars.count, within: newChars) - interItemSpacing/*newChars.reduce(-interItemSpacing) {
|
let countWidth = offsetForChar(at: newChars.count, within: newChars) - interItemSpacing
|
||||||
if $1.string == "," {
|
|
||||||
return $0 + commaWidth + interItemSpacing
|
|
||||||
}
|
|
||||||
return $0 + itemWidth + interItemSpacing
|
|
||||||
}*/
|
|
||||||
if didBegin && prevCount != chars.count {
|
if didBegin && prevCount != chars.count {
|
||||||
UIView.animate(withDuration: Double(changeIndex) * initialDuration/*, delay: initialDuration * Double(changeIndex)*/) { [self] in
|
UIView.animate(withDuration: Double(changeIndex) * initialDuration) { [self] in
|
||||||
containerView.frame = .init(x: self.bounds.midX - countWidth / 2, y: 0, width: countWidth, height: self.bounds.height)
|
containerView.frame = .init(x: self.bounds.midX - countWidth / 2, y: 0, width: countWidth, height: self.bounds.height)
|
||||||
if countWidth * scaleFactor > self.bounds.width {
|
if countWidth * scaleFactor > self.bounds.width {
|
||||||
let scale = (self.bounds.width - 32) / (countWidth * scaleFactor)
|
let scale = (self.bounds.width - 32) / (countWidth * scaleFactor)
|
||||||
@ -380,71 +309,31 @@ class AnimatedCountLabel: UILabel {
|
|||||||
} else {
|
} else {
|
||||||
containerView.transform = .init(scaleX: scaleFactor, y: scaleFactor)
|
containerView.transform = .init(scaleX: scaleFactor, y: scaleFactor)
|
||||||
}
|
}
|
||||||
// containerView.backgroundColor = .red.withAlphaComponent(0.3)
|
|
||||||
}
|
}
|
||||||
} else if countWidth > 0 {
|
} else if countWidth > 0 {
|
||||||
containerView.frame = .init(x: self.bounds.midX - countWidth / 2 * scaleFactor, y: 0, width: countWidth * scaleFactor, height: self.bounds.height)
|
containerView.frame = .init(x: self.bounds.midX - countWidth / 2 * scaleFactor, y: 0, width: countWidth * scaleFactor, height: self.bounds.height)
|
||||||
didBegin = true
|
didBegin = true
|
||||||
}
|
}
|
||||||
// self.backgroundColor = .green.withAlphaComponent(0.2)
|
|
||||||
self.clipsToBounds = false
|
self.clipsToBounds = false
|
||||||
}
|
}
|
||||||
func animateOut(for layer: CALayer, duration: CFTimeInterval, beginTime: CFTimeInterval) {
|
func animateOut(for layer: CALayer, duration: CFTimeInterval, beginTime: CFTimeInterval) {
|
||||||
// let animation = CAKeyframeAnimation()
|
|
||||||
// animation.keyPath = "opacity"
|
|
||||||
// animation.values = [layer.presentation()?.value(forKey: "opacity") ?? 1, 0.0]
|
|
||||||
// animation.keyTimes = [0, 1]
|
|
||||||
// animation.duration = duration
|
|
||||||
// animation.beginTime = CACurrentMediaTime() + beginTime
|
|
||||||
//// animation.isAdditive = true
|
|
||||||
// animation.isRemovedOnCompletion = false
|
|
||||||
// animation.fillMode = .backwards
|
|
||||||
// layer.opacity = 0
|
|
||||||
// layer.add(animation, forKey: "opacity")
|
|
||||||
//
|
|
||||||
//
|
|
||||||
let beginTimeOffset: CFTimeInterval = 0/*beginTime == .zero ? 0 :*/ // CFTimeInterval(DispatchTime.now().uptimeNanoseconds / 1000000000) /*layer.convertTime(*/// CACurrentMediaTime()//, to: nil)
|
let beginTimeOffset: CFTimeInterval = 0/*beginTime == .zero ? 0 :*/ // CFTimeInterval(DispatchTime.now().uptimeNanoseconds / 1000000000) /*layer.convertTime(*/// CACurrentMediaTime()//, to: nil)
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + beginTime) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + beginTime) {
|
||||||
let currentTime = CFTimeInterval(DispatchTime.now().uptimeNanoseconds / 1000000000)
|
|
||||||
let beginTime: CFTimeInterval = 0
|
let beginTime: CFTimeInterval = 0
|
||||||
print("[DIFF-out] \(currentTime - beginTimeOffset)")
|
|
||||||
let opacityInAnimation = CABasicAnimation(keyPath: "opacity")
|
let opacityInAnimation = CABasicAnimation(keyPath: "opacity")
|
||||||
opacityInAnimation.fromValue = 1
|
opacityInAnimation.fromValue = 1
|
||||||
opacityInAnimation.toValue = 0
|
opacityInAnimation.toValue = 0
|
||||||
opacityInAnimation.fillMode = .forwards
|
opacityInAnimation.fillMode = .forwards
|
||||||
opacityInAnimation.isRemovedOnCompletion = false
|
opacityInAnimation.isRemovedOnCompletion = false
|
||||||
// opacityInAnimation.duration = duration
|
|
||||||
// opacityInAnimation.beginTime = beginTimeOffset + beginTime
|
|
||||||
// opacityInAnimation.completion = { _ in
|
|
||||||
// layer.removeFromSuperlayer()
|
|
||||||
// }
|
|
||||||
// layer.add(opacityInAnimation, forKey: "opacity")
|
|
||||||
|
|
||||||
// let timer = Timer.scheduledTimer(withTimeInterval: duration + beginTime, repeats: false) { timer in
|
|
||||||
// DispatchQueue.main.asyncAfter(deadline: .now() + duration + beginTime) {
|
|
||||||
// DispatchQueue.main.async {
|
|
||||||
// layer.backgroundColor = UIColor.red.withAlphaComponent(0.3).cgColor
|
|
||||||
// }
|
|
||||||
// DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
|
||||||
// layer.removeFromSuperlayer()
|
|
||||||
// }
|
|
||||||
// timer.invalidate()
|
|
||||||
// }
|
|
||||||
// RunLoop.current.add(timer, forMode: .common)
|
|
||||||
|
|
||||||
let scaleOutAnimation = CABasicAnimation(keyPath: "transform.scale")
|
let scaleOutAnimation = CABasicAnimation(keyPath: "transform.scale")
|
||||||
scaleOutAnimation.fromValue = 1 // layer.presentation()?.value(forKey: "transform.scale") ?? 1
|
scaleOutAnimation.fromValue = 1
|
||||||
scaleOutAnimation.toValue = 0.0
|
scaleOutAnimation.toValue = 0.0
|
||||||
// scaleOutAnimation.duration = duration
|
|
||||||
// scaleOutAnimation.beginTime = beginTimeOffset + beginTime
|
|
||||||
// layer.add(scaleOutAnimation, forKey: "scaleout")
|
|
||||||
|
|
||||||
let translate = CABasicAnimation(keyPath: "transform.translation")
|
let translate = CABasicAnimation(keyPath: "transform.translation")
|
||||||
translate.fromValue = CGPoint.zero
|
translate.fromValue = CGPoint.zero
|
||||||
translate.toValue = CGPoint(x: 0, y: -layer.bounds.height * 0.3)// -layer.bounds.height + 3.0)
|
translate.toValue = CGPoint(x: 0, y: -layer.bounds.height * 0.3)
|
||||||
// translate.duration = duration
|
|
||||||
// translate.beginTime = beginTimeOffset + beginTime
|
|
||||||
// layer.add(translate, forKey: "translate")
|
|
||||||
|
|
||||||
let group = CAAnimationGroup()
|
let group = CAAnimationGroup()
|
||||||
group.animations = [opacityInAnimation, scaleOutAnimation, translate]
|
group.animations = [opacityInAnimation, scaleOutAnimation, translate]
|
||||||
@ -455,38 +344,31 @@ class AnimatedCountLabel: UILabel {
|
|||||||
group.completion = { _ in
|
group.completion = { _ in
|
||||||
layer.removeFromSuperlayer()
|
layer.removeFromSuperlayer()
|
||||||
}
|
}
|
||||||
// layer.opacity = 0
|
|
||||||
layer.add(group, forKey: "out")
|
layer.add(group, forKey: "out")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateIn(for newLayer: CALayer, duration: CFTimeInterval, beginTime: CFTimeInterval) {
|
func animateIn(for newLayer: CALayer, duration: CFTimeInterval, beginTime: CFTimeInterval) {
|
||||||
|
|
||||||
let beginTimeOffset: CFTimeInterval = 0// CFTimeInterval(DispatchTime.now().uptimeNanoseconds / 1000000000)// CACurrentMediaTime()
|
let beginTimeOffset: CFTimeInterval = 0 // CACurrentMediaTime()
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + beginTime) { [self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + beginTime) { [self] in
|
||||||
let currentTime = CFTimeInterval(DispatchTime.now().uptimeNanoseconds / 1000000000)
|
|
||||||
let beginTime: CFTimeInterval = 0
|
let beginTime: CFTimeInterval = 0
|
||||||
print("[DIFF-in] \(currentTime - beginTimeOffset)")
|
|
||||||
newLayer.opacity = 0
|
newLayer.opacity = 0
|
||||||
// newLayer.backgroundColor = UIColor.red.cgColor
|
|
||||||
|
|
||||||
let opacityInAnimation = CABasicAnimation(keyPath: "opacity")
|
let opacityInAnimation = CABasicAnimation(keyPath: "opacity")
|
||||||
opacityInAnimation.fromValue = 0
|
opacityInAnimation.fromValue = 0
|
||||||
opacityInAnimation.toValue = 1
|
opacityInAnimation.toValue = 1
|
||||||
opacityInAnimation.duration = duration
|
opacityInAnimation.duration = duration
|
||||||
opacityInAnimation.beginTime = beginTimeOffset + beginTime
|
opacityInAnimation.beginTime = beginTimeOffset + beginTime
|
||||||
// opacityInAnimation.isAdditive = true
|
|
||||||
opacityInAnimation.fillMode = .backwards
|
opacityInAnimation.fillMode = .backwards
|
||||||
newLayer.opacity = 1
|
newLayer.opacity = 1
|
||||||
newLayer.add(opacityInAnimation, forKey: "opacity")
|
newLayer.add(opacityInAnimation, forKey: "opacity")
|
||||||
// newLayer.opacity = 1
|
|
||||||
|
|
||||||
let scaleOutAnimation = CABasicAnimation(keyPath: "transform.scale")
|
let scaleOutAnimation = CABasicAnimation(keyPath: "transform.scale")
|
||||||
scaleOutAnimation.fromValue = 0
|
scaleOutAnimation.fromValue = 0
|
||||||
scaleOutAnimation.toValue = 1
|
scaleOutAnimation.toValue = 1
|
||||||
scaleOutAnimation.duration = duration
|
scaleOutAnimation.duration = duration
|
||||||
scaleOutAnimation.beginTime = beginTimeOffset + beginTime
|
scaleOutAnimation.beginTime = beginTimeOffset + beginTime
|
||||||
// scaleOutAnimation.isAdditive = true
|
|
||||||
newLayer.add(scaleOutAnimation, forKey: "scalein")
|
newLayer.add(scaleOutAnimation, forKey: "scalein")
|
||||||
|
|
||||||
let animation = CAKeyframeAnimation()
|
let animation = CAKeyframeAnimation()
|
||||||
|
@ -48,8 +48,6 @@ 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 var infoDisposable: Disposable?
|
||||||
private var connectionDisposable: Disposable?
|
|
||||||
private var networkStateDisposable: Disposable?
|
|
||||||
|
|
||||||
private(set) var originInfo: OriginInfo?
|
private(set) var originInfo: OriginInfo?
|
||||||
|
|
||||||
@ -113,36 +111,6 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
strongSelf.updated(transition: .immediate)
|
strongSelf.updated(transition: .immediate)
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: retest to uncomment or delete. Relying only on video frames
|
|
||||||
/*self.networkStateDisposable = (call.account.networkState |> deliverOnMainQueue).start(next: { [weak self] state in
|
|
||||||
guard let strongSelf = self else { return }
|
|
||||||
switch state {
|
|
||||||
case .waitingForNetwork, .connecting:
|
|
||||||
print("[NEW] videoStalled")
|
|
||||||
strongSelf.videoStalled = true
|
|
||||||
default:
|
|
||||||
strongSelf.videoStalled = !strongSelf.hasVideo
|
|
||||||
}
|
|
||||||
strongSelf.updated(transition: .immediate)
|
|
||||||
// if let strongSelf = self, case .standard(previewing: false) = strongSelf.presentationInterfaceState.mode {
|
|
||||||
// strongSelf.chatTitleView?.networkState = state
|
|
||||||
// }
|
|
||||||
})
|
|
||||||
|
|
||||||
self.connectionDisposable = call.state.start(next: { [weak self] state in
|
|
||||||
let prev = self?.videoStalled
|
|
||||||
switch state.networkState {
|
|
||||||
case .connected:
|
|
||||||
self?.videoStalled = false
|
|
||||||
default:
|
|
||||||
print("[ALERT] video stalled")
|
|
||||||
self?.videoStalled = true
|
|
||||||
}
|
|
||||||
if prev != self?.videoStalled {
|
|
||||||
self?.updated(transition: .immediate)
|
|
||||||
}
|
|
||||||
})*/
|
|
||||||
|
|
||||||
let callPeer = call.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: call.peerId))
|
let callPeer = call.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: call.peerId))
|
||||||
|
|
||||||
self.infoDisposable = (combineLatest(queue: .mainQueue(), call.state, call.members, callPeer)
|
self.infoDisposable = (combineLatest(queue: .mainQueue(), call.state, call.members, callPeer)
|
||||||
@ -153,8 +121,8 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
|
|
||||||
var updated = false
|
var updated = false
|
||||||
// TODO: remove debug timer
|
// TODO: remove debug timer
|
||||||
// Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
|
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
|
||||||
strongSelf.infoThrottler.publish(members.totalCount/*Int.random(in: 0..<10000000)*/) { [weak strongSelf] latestCount in
|
strongSelf.infoThrottler.publish(/*members.totalCount*/Int.random(in: 0..<1000000000)) { [weak strongSelf] latestCount in
|
||||||
print(members.totalCount)
|
print(members.totalCount)
|
||||||
guard let strongSelf = strongSelf else { return }
|
guard let strongSelf = strongSelf else { return }
|
||||||
var updated = false
|
var updated = false
|
||||||
@ -167,7 +135,7 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
strongSelf.updated(transition: .immediate)
|
strongSelf.updated(transition: .immediate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// }.fire()
|
}.fire()
|
||||||
if state.canManageCall != strongSelf.canManageCall {
|
if state.canManageCall != strongSelf.canManageCall {
|
||||||
strongSelf.canManageCall = state.canManageCall
|
strongSelf.canManageCall = state.canManageCall
|
||||||
updated = true
|
updated = true
|
||||||
@ -188,12 +156,6 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
updated = true
|
updated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// let originInfo = OriginInfo(title: callPeer.debugDisplayTitle, memberCount: members.totalCount)
|
|
||||||
// if strongSelf.originInfo != originInfo {
|
|
||||||
// strongSelf.originInfo = originInfo
|
|
||||||
// updated = true
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
if updated {
|
if updated {
|
||||||
strongSelf.updated(transition: .immediate)
|
strongSelf.updated(transition: .immediate)
|
||||||
}
|
}
|
||||||
@ -225,8 +187,6 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
self.stateDisposable?.dispose()
|
self.stateDisposable?.dispose()
|
||||||
self.infoDisposable?.dispose()
|
self.infoDisposable?.dispose()
|
||||||
self.isVisibleInHierarchyDisposable?.dispose()
|
self.isVisibleInHierarchyDisposable?.dispose()
|
||||||
self.connectionDisposable?.dispose()
|
|
||||||
self.networkStateDisposable?.dispose()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func toggleDisplayUI() {
|
func toggleDisplayUI() {
|
||||||
@ -272,9 +232,6 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
let background = Child(Rectangle.self)
|
let background = Child(Rectangle.self)
|
||||||
let dismissTapComponent = Child(Rectangle.self)
|
let dismissTapComponent = Child(Rectangle.self)
|
||||||
let video = Child(MediaStreamVideoComponent.self)
|
let video = Child(MediaStreamVideoComponent.self)
|
||||||
// let navigationBar = Child(NavigationBarComponent.self)
|
|
||||||
// let toolbar = Child(ToolbarComponent.self)
|
|
||||||
|
|
||||||
let sheet = Child(StreamSheetComponent.self)
|
let sheet = Child(StreamSheetComponent.self)
|
||||||
let fullscreenOverlay = Child(StreamSheetComponent.self)
|
let fullscreenOverlay = Child(StreamSheetComponent.self)
|
||||||
|
|
||||||
@ -312,11 +269,10 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
state.updated(transition: .easeInOut(duration: 3))
|
state.updated(transition: .easeInOut(duration: 3))
|
||||||
deactivatePictureInPicture.invoke(Void())
|
deactivatePictureInPicture.invoke(Void())
|
||||||
}
|
}
|
||||||
let isFullscreen: Bool // = state.isFullscreen
|
let isFullscreen: Bool
|
||||||
let isLandscape = context.availableSize.width > context.availableSize.height
|
let isLandscape = context.availableSize.width > context.availableSize.height
|
||||||
|
|
||||||
// if let videoSize = context.state.videoSize {
|
// Always fullscreen in landscape
|
||||||
// Always fullscreen in landscape
|
|
||||||
// TODO: support landscape sheet (wrap in scrollview, video size same as portrait)
|
// TODO: support landscape sheet (wrap in scrollview, video size same as portrait)
|
||||||
if forceFullScreenInLandscape && isLandscape && !state.isFullscreen {
|
if forceFullScreenInLandscape && isLandscape && !state.isFullscreen {
|
||||||
state.isFullscreen = true
|
state.isFullscreen = true
|
||||||
@ -327,7 +283,7 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
} else {
|
} else {
|
||||||
isFullscreen = state.isFullscreen
|
isFullscreen = state.isFullscreen
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
let videoInset: CGFloat
|
let videoInset: CGFloat
|
||||||
if !isFullscreen {
|
if !isFullscreen {
|
||||||
videoInset = 16
|
videoInset = 16
|
||||||
@ -346,7 +302,7 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
|
|
||||||
var dragOffset = context.state.dismissOffset
|
var dragOffset = context.state.dismissOffset
|
||||||
if isFullyDragged {
|
if isFullyDragged {
|
||||||
dragOffset = max(context.state.dismissOffset, sheetHeight - context.availableSize.height + context.view.safeAreaInsets.top)// sheetHeight - UIScreen.main.bounds.height
|
dragOffset = max(context.state.dismissOffset, sheetHeight - context.availableSize.height + context.view.safeAreaInsets.top)
|
||||||
}
|
}
|
||||||
|
|
||||||
let dismissTapAreaHeight = isFullscreen ? 0 : (context.availableSize.height - sheetHeight + dragOffset)
|
let dismissTapAreaHeight = isFullscreen ? 0 : (context.availableSize.height - sheetHeight + dragOffset)
|
||||||
@ -356,7 +312,6 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
transition: context.transition
|
transition: context.transition
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
let video = video.update(
|
let video = video.update(
|
||||||
component: MediaStreamVideoComponent(
|
component: MediaStreamVideoComponent(
|
||||||
call: context.component.call,
|
call: context.component.call,
|
||||||
@ -425,8 +380,7 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
var topLeftButton: AnyComponent<Empty>?
|
var topLeftButton: AnyComponent<Empty>?
|
||||||
if context.state.canManageCall {
|
if context.state.canManageCall {
|
||||||
let whiteColor = UIColor(white: 1.0, alpha: 1.0)
|
let whiteColor = UIColor(white: 1.0, alpha: 1.0)
|
||||||
/*navigationRightItems.append(*/ topLeftButton = //AnyComponentWithIdentity(id: "more", component:
|
topLeftButton = AnyComponent(Button(
|
||||||
AnyComponent(Button(
|
|
||||||
content: AnyComponent(ZStack([
|
content: AnyComponent(ZStack([
|
||||||
AnyComponentWithIdentity(id: "b", component: AnyComponent(Circle(
|
AnyComponentWithIdentity(id: "b", component: AnyComponent(Circle(
|
||||||
fillColor: .white.withAlphaComponent(0.08),
|
fillColor: .white.withAlphaComponent(0.08),
|
||||||
@ -562,7 +516,6 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let presentationData = call.accountContext.sharedContext.currentPresentationData.with { $0 }
|
let presentationData = call.accountContext.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
|
||||||
if let title = title {
|
if let title = title {
|
||||||
@ -659,22 +612,11 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
let navigationComponent = NavigationBarComponent(
|
let navigationComponent = NavigationBarComponent(
|
||||||
topInset: environment.statusBarHeight,
|
topInset: environment.statusBarHeight,
|
||||||
sideInset: environment.safeInsets.left,
|
sideInset: environment.safeInsets.left,
|
||||||
leftItem: topLeftButton/*AnyComponent(Button(
|
leftItem: topLeftButton,
|
||||||
content: AnyComponent(Text(text: environment.strings.Common_Close, font: Font.regular(17.0), color: .white)),
|
|
||||||
action: { [weak call] in
|
|
||||||
let _ = call?.leave(terminateIfPossible: false)
|
|
||||||
})
|
|
||||||
)*/,
|
|
||||||
rightItems: navigationRightItems,
|
rightItems: navigationRightItems,
|
||||||
centerItem: AnyComponent(StreamTitleComponent(text: state.peerTitle, isRecording: state.recordingStartTimestamp != nil, isActive: context.state.videoIsPlayable))
|
centerItem: AnyComponent(StreamTitleComponent(text: state.peerTitle, isRecording: state.recordingStartTimestamp != nil, isActive: context.state.videoIsPlayable))
|
||||||
)
|
)
|
||||||
|
|
||||||
// let navigationBar = navigationBar.update(
|
|
||||||
// component: navigationComponent,
|
|
||||||
// availableSize: CGSize(width: context.availableSize.width, height: context.availableSize.height),
|
|
||||||
// transition: context.transition
|
|
||||||
// )
|
|
||||||
|
|
||||||
if context.state.storedIsFullscreen != isFullscreen {
|
if context.state.storedIsFullscreen != isFullscreen {
|
||||||
context.state.storedIsFullscreen = isFullscreen
|
context.state.storedIsFullscreen = isFullscreen
|
||||||
if isFullscreen {
|
if isFullscreen {
|
||||||
@ -751,8 +693,6 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
onPanGesture(panState)
|
onPanGesture(panState)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
// var bottomComponent: AnyComponent<Empty>?
|
|
||||||
// var fullScreenToolbarComponent: AnyComponent<Empty>?
|
|
||||||
|
|
||||||
context.add(dismissTapComponent
|
context.add(dismissTapComponent
|
||||||
.position(CGPoint(x: context.availableSize.width / 2, y: dismissTapAreaHeight / 2))
|
.position(CGPoint(x: context.availableSize.width / 2, y: dismissTapAreaHeight / 2))
|
||||||
@ -821,7 +761,6 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
context.setLineWidth(2.4 * imageRenderScale - UIScreenPixel)
|
context.setLineWidth(2.4 * imageRenderScale - UIScreenPixel)
|
||||||
context.setLineCap(.round)
|
context.setLineCap(.round)
|
||||||
context.setStrokeColor(imageColor.cgColor)
|
context.setStrokeColor(imageColor.cgColor)
|
||||||
// context.setLineJoin(.round)
|
|
||||||
|
|
||||||
let lineSide = size.width / 5
|
let lineSide = size.width / 5
|
||||||
let centerOffset = size.width / 20
|
let centerOffset = size.width / 20
|
||||||
@ -860,9 +799,8 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
controller.updateOrientation(orientation: .portrait)
|
controller.updateOrientation(orientation: .portrait)
|
||||||
}
|
}
|
||||||
if !canEnforceOrientation {
|
if !canEnforceOrientation {
|
||||||
state.updated() // updated(.easeInOut(duration: 0.3))
|
state.updated()
|
||||||
}
|
}
|
||||||
// controller.updateOrientation(orientation: isLandscape ? .portrait : .landscapeRight)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).minSize(CGSize(width: 44.0, height: 44.0)))
|
).minSize(CGSize(width: 44.0, height: 44.0)))
|
||||||
@ -877,7 +815,6 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
backgroundColor: isFullscreen ? .clear : (isFullyDragged ? fullscreenBackgroundColor : panelBackgroundColor),
|
backgroundColor: isFullscreen ? .clear : (isFullyDragged ? fullscreenBackgroundColor : panelBackgroundColor),
|
||||||
bottomPadding: bottomPadding,
|
bottomPadding: bottomPadding,
|
||||||
participantsCount: context.state.originInfo?.memberCount ?? 0, // Int.random(in: 0...999998)// [0, 5, 15, 16, 95, 100, 16042, 942539].randomElement()!
|
participantsCount: context.state.originInfo?.memberCount ?? 0, // Int.random(in: 0...999998)// [0, 5, 15, 16, 95, 100, 16042, 942539].randomElement()!
|
||||||
//
|
|
||||||
isFullyExtended: isFullyDragged,
|
isFullyExtended: isFullyDragged,
|
||||||
deviceCornerRadius: (controller() as? MediaStreamComponentController)?.validLayout?.deviceMetrics.screenCornerRadius ?? 0,
|
deviceCornerRadius: (controller() as? MediaStreamComponentController)?.validLayout?.deviceMetrics.screenCornerRadius ?? 0,
|
||||||
videoHeight: videoHeight
|
videoHeight: videoHeight
|
||||||
@ -888,7 +825,7 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
|
|
||||||
let sheetOffset: CGFloat = context.availableSize.height - sheetHeight + dragOffset
|
let sheetOffset: CGFloat = context.availableSize.height - sheetHeight + dragOffset
|
||||||
let sheetPosition = sheetOffset + sheetHeight / 2
|
let sheetPosition = sheetOffset + sheetHeight / 2
|
||||||
// Sheet underneath the video when in sheet
|
// Sheet underneath the video when in modal sheet
|
||||||
context.add(sheet
|
context.add(sheet
|
||||||
.position(.init(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2))
|
.position(.init(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2))
|
||||||
)
|
)
|
||||||
@ -900,7 +837,7 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
videoPos = sheetPosition - sheetHeight / 2 + videoHeight / 2 + 50 + 12
|
videoPos = sheetPosition - sheetHeight / 2 + videoHeight / 2 + 50 + 12
|
||||||
}
|
}
|
||||||
context.add(video
|
context.add(video
|
||||||
.position(CGPoint(x: context.availableSize.width / 2.0, y: videoPos)/*sheetPosition + videoHeight / 2 + 50 - context.availableSize.height / 2*/)// context.availableSize.height / 2.0 + context.state.dismissOffset))
|
.position(CGPoint(x: context.availableSize.width / 2.0, y: videoPos))
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
context.add(video
|
context.add(video
|
||||||
@ -950,7 +887,7 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
sheetHeight: max(sheetHeight - context.state.dismissOffset, sheetHeight),
|
sheetHeight: max(sheetHeight - context.state.dismissOffset, sheetHeight),
|
||||||
backgroundColor: isFullscreen ? .clear : (isFullyDragged ? fullscreenBackgroundColor : panelBackgroundColor),
|
backgroundColor: isFullscreen ? .clear : (isFullyDragged ? fullscreenBackgroundColor : panelBackgroundColor),
|
||||||
bottomPadding: 12,
|
bottomPadding: 12,
|
||||||
participantsCount: -1, // context.state.originInfo?.memberCount ?? 0
|
participantsCount: -1,
|
||||||
isFullyExtended: isFullyDragged,
|
isFullyExtended: isFullyDragged,
|
||||||
deviceCornerRadius: (controller() as? MediaStreamComponentController)?.validLayout?.deviceMetrics.screenCornerRadius ?? 0,
|
deviceCornerRadius: (controller() as? MediaStreamComponentController)?.validLayout?.deviceMetrics.screenCornerRadius ?? 0,
|
||||||
videoHeight: videoHeight
|
videoHeight: videoHeight
|
||||||
@ -964,24 +901,11 @@ public final class MediaStreamComponent: CombinedComponent {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// context.add(navigationBar
|
|
||||||
// .position(CGPoint(x: context.availableSize.width / 2.0, y: navigationBar.size.height / 2.0))
|
|
||||||
// .opacity(context.state.displayUI ? 1.0 : 0.0)
|
|
||||||
// )
|
|
||||||
|
|
||||||
// context.add(toolbar
|
|
||||||
// .position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height - toolbar.size.height / 2.0))
|
|
||||||
// .opacity(context.state.displayUI ? 1.0 : 0.0)
|
|
||||||
// )
|
|
||||||
|
|
||||||
return context.availableSize
|
return context.availableSize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: pass to component properly
|
|
||||||
//internal var deviceCornerRadius: CGFloat? = nil
|
|
||||||
|
|
||||||
public final class MediaStreamComponentController: ViewControllerComponentContainer, VoiceChatController {
|
public final class MediaStreamComponentController: ViewControllerComponentContainer, VoiceChatController {
|
||||||
private let context: AccountContext
|
private let context: AccountContext
|
||||||
public let call: PresentationGroupCall
|
public let call: PresentationGroupCall
|
||||||
@ -1048,11 +972,6 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
|
|||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
self.onViewDidDisappear?()
|
self.onViewDidDisappear?()
|
||||||
}
|
}
|
||||||
|
|
||||||
// if let initialOrientation = self.initialOrientation {
|
|
||||||
// self.initialOrientation = nil
|
|
||||||
// self.call.accountContext.sharedContext.applicationBindings.forceOrientation(initialOrientation)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override public func viewDidLoad() {
|
override public func viewDidLoad() {
|
||||||
@ -1088,17 +1007,8 @@ public final class MediaStreamComponentController: ViewControllerComponentContai
|
|||||||
strongSelf.dismissImpl(completion: completion)
|
strongSelf.dismissImpl(completion: completion)
|
||||||
})
|
})
|
||||||
self.backgroundDimView.layer.animateAlpha(from: 1.0, to: 0, duration: 0.3, removeOnCompletion: false)
|
self.backgroundDimView.layer.animateAlpha(from: 1.0, to: 0, duration: 0.3, removeOnCompletion: false)
|
||||||
// if let validLayout = self.validLayout {
|
self.view.layer.animatePosition(from: self.view.center, to: CGPoint(x: self.view.center.x, y: self.view.bounds.maxY + self.view.bounds.height / 2), duration: 0.4, completion: { _ in
|
||||||
// self.view.clipsToBounds = true
|
|
||||||
// self.view.layer.cornerRadius = validLayout.deviceMetrics.screenCornerRadius
|
|
||||||
// if #available(iOS 13.0, *) {
|
|
||||||
// self.view.layer.cornerCurve = .continuous
|
|
||||||
// }
|
|
||||||
|
|
||||||
self.view.layer.animatePosition(from: self.view.center, to: CGPoint(x: self.view.center.x, y: self.view.bounds.maxY + self.view.bounds.height / 2), duration: 0.4, /*timingFunction: kCAMediaTimingFunctionSpring, */completion: { _ in
|
|
||||||
})
|
})
|
||||||
// self.view.layer.animateScale(from: 1.0, to: 0.001, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring)
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func dismissImpl(completion: (() -> Void)? = nil) {
|
private func dismissImpl(completion: (() -> Void)? = nil) {
|
||||||
@ -1505,21 +1415,18 @@ private final class NavigationBarComponent: CombinedComponent {
|
|||||||
centerLeftInset += leftItem.size.width + 4.0
|
centerLeftInset += leftItem.size.width + 4.0
|
||||||
}
|
}
|
||||||
|
|
||||||
// var centerRightInset = sideInset
|
|
||||||
var rightItemX = context.availableSize.width - sideInset
|
var rightItemX = context.availableSize.width - sideInset
|
||||||
for item in rightItemList.reversed() {
|
for item in rightItemList.reversed() {
|
||||||
context.add(item
|
context.add(item
|
||||||
.position(CGPoint(x: rightItemX - item.size.width / 2.0, y: context.component.topInset + contentHeight / 2.0))
|
.position(CGPoint(x: rightItemX - item.size.width / 2.0, y: context.component.topInset + contentHeight / 2.0))
|
||||||
)
|
)
|
||||||
rightItemX -= item.size.width + 8.0
|
rightItemX -= item.size.width + 8.0
|
||||||
// centerRightInset += item.size.width + 8.0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// let maxCenterInset = max(centerLeftInset, centerRightInset)
|
|
||||||
let someUndesiredOffset: CGFloat = 16
|
let someUndesiredOffset: CGFloat = 16
|
||||||
if let centerItem = centerItem {
|
if let centerItem = centerItem {
|
||||||
context.add(centerItem
|
context.add(centerItem
|
||||||
.position(CGPoint(x: context.availableSize.width / 2 - someUndesiredOffset /*maxCenterInset + (context.availableSize.width - maxCenterInset - maxCenterInset) / 2.0*/, y: context.component.topInset + contentHeight / 2.0))
|
.position(CGPoint(x: context.availableSize.width / 2 - someUndesiredOffset, y: context.component.topInset + contentHeight / 2.0))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,23 +12,6 @@ import SwiftSignalKit
|
|||||||
import AvatarNode
|
import AvatarNode
|
||||||
import Postbox
|
import Postbox
|
||||||
|
|
||||||
class CustomIntensityVisualEffectView: UIVisualEffectView {
|
|
||||||
init(effect: UIVisualEffect, intensity: CGFloat) {
|
|
||||||
super.init(effect: nil)
|
|
||||||
animator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [unowned self] in self.effect = effect }
|
|
||||||
animator.startAnimation()
|
|
||||||
animator.pauseAnimation()
|
|
||||||
animator.fractionComplete = intensity
|
|
||||||
animator.pausesOnCompletion = true
|
|
||||||
}
|
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
|
|
||||||
var animator: UIViewPropertyAnimator!
|
|
||||||
}
|
|
||||||
|
|
||||||
final class MediaStreamVideoComponent: Component {
|
final class MediaStreamVideoComponent: Component {
|
||||||
let call: PresentationGroupCallImpl
|
let call: PresentationGroupCallImpl
|
||||||
let hasVideo: Bool
|
let hasVideo: Bool
|
||||||
@ -137,6 +120,34 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
private var noSignalTimer: Foundation.Timer?
|
private var noSignalTimer: Foundation.Timer?
|
||||||
private var noSignalTimeout: Bool = false
|
private var noSignalTimeout: Bool = false
|
||||||
|
|
||||||
|
private let maskGradientLayer = CAGradientLayer()
|
||||||
|
private var wasVisible = true
|
||||||
|
private var borderShimmer = StandaloneShimmerEffect()
|
||||||
|
private let shimmerBorderLayer = CALayer()
|
||||||
|
private let placeholderView = UIImageView()
|
||||||
|
|
||||||
|
private var videoStalled = false {
|
||||||
|
didSet {
|
||||||
|
if videoStalled != oldValue {
|
||||||
|
self.updateVideoStalled(isStalled: self.videoStalled)
|
||||||
|
// state?.updated()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var onVideoPlaybackChange: ((Bool) -> Void) = { _ in }
|
||||||
|
|
||||||
|
private var frameInputDisposable: Disposable?
|
||||||
|
|
||||||
|
private var stallTimer: Foundation.Timer?
|
||||||
|
private let fullScreenBackgroundPlaceholder = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
|
||||||
|
|
||||||
|
private var avatarDisposable: Disposable?
|
||||||
|
private var didBeginLoadingAvatar = false
|
||||||
|
private var timeLastFrameReceived: CFAbsoluteTime?
|
||||||
|
|
||||||
|
private var isFullscreen: Bool = false
|
||||||
|
private let videoLoadingThrottler = Throttler<Bool>(duration: 1, queue: .main)
|
||||||
|
|
||||||
private weak var state: State?
|
private weak var state: State?
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
@ -154,6 +165,11 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
avatarDisposable?.dispose()
|
||||||
|
frameInputDisposable?.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
public func matches(tag: Any) -> Bool {
|
public func matches(tag: Any) -> Bool {
|
||||||
if let _ = tag as? Tag {
|
if let _ = tag as? Tag {
|
||||||
return true
|
return true
|
||||||
@ -167,23 +183,6 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
self.pictureInPictureController?.stopPictureInPicture()
|
self.pictureInPictureController?.stopPictureInPicture()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let maskGradientLayer = CAGradientLayer()
|
|
||||||
private var wasVisible = true
|
|
||||||
var borderShimmer = StandaloneShimmerEffect()
|
|
||||||
let shimmerBorderLayer = CALayer()
|
|
||||||
let placeholderView = UIImageView()
|
|
||||||
|
|
||||||
var videoStalled = false {
|
|
||||||
didSet {
|
|
||||||
if videoStalled != oldValue {
|
|
||||||
self.updateVideoStalled(isStalled: self.videoStalled)
|
|
||||||
// state?.updated()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var onVideoPlaybackChange: ((Bool) -> Void) = { _ in }
|
|
||||||
|
|
||||||
private var frameInputDisposable: Disposable?
|
|
||||||
|
|
||||||
private func updateVideoStalled(isStalled: Bool) {
|
private func updateVideoStalled(isStalled: Bool) {
|
||||||
if isStalled {
|
if isStalled {
|
||||||
@ -215,7 +214,6 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if shimmerBorderLayer.superlayer == nil {
|
if shimmerBorderLayer.superlayer == nil {
|
||||||
// loadingBlurView.contentView.layer.addSublayer(shimmerOverlayLayer)
|
|
||||||
loadingBlurView.contentView.layer.addSublayer(shimmerBorderLayer)
|
loadingBlurView.contentView.layer.addSublayer(shimmerBorderLayer)
|
||||||
}
|
}
|
||||||
loadingBlurView.clipsToBounds = true
|
loadingBlurView.clipsToBounds = true
|
||||||
@ -234,7 +232,7 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
|
|
||||||
borderShimmer = .init()
|
borderShimmer = .init()
|
||||||
borderShimmer.layer = shimmerBorderLayer
|
borderShimmer.layer = shimmerBorderLayer
|
||||||
borderShimmer.testUpdate(background: .clear, foreground: .white)
|
borderShimmer.updateHorizontal(background: .clear, foreground: .white)
|
||||||
loadingBlurView.alpha = 1
|
loadingBlurView.alpha = 1
|
||||||
} else {
|
} else {
|
||||||
if hadVideo {
|
if hadVideo {
|
||||||
@ -251,60 +249,12 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
self?.loadingBlurView.layer.removeAllAnimations()
|
self?.loadingBlurView.layer.removeAllAnimations()
|
||||||
}
|
}
|
||||||
loadingBlurView.layer.add(anim, forKey: "opacity")
|
loadingBlurView.layer.add(anim, forKey: "opacity")
|
||||||
} else {
|
|
||||||
// Wait for state to update with first frame
|
|
||||||
// Accounting for delay in first frame received
|
|
||||||
/*DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
|
|
||||||
guard self?.videoStalled == false else { return }
|
|
||||||
|
|
||||||
// TODO: animate blur intesity with UIPropertyAnimator
|
|
||||||
self?.loadingBlurView.layer.removeAllAnimations()
|
|
||||||
let anim = CABasicAnimation(keyPath: "opacity")
|
|
||||||
anim.duration = 0.5
|
|
||||||
anim.fromValue = 1
|
|
||||||
anim.toValue = 0
|
|
||||||
anim.fillMode = .forwards
|
|
||||||
anim.isRemovedOnCompletion = false
|
|
||||||
anim.completion = { [weak self] _ in
|
|
||||||
guard self?.videoStalled == false else { return }
|
|
||||||
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [self] in
|
|
||||||
self?.loadingBlurView.removeFromSuperview()
|
|
||||||
self?.placeholderView.removeFromSuperview()
|
|
||||||
}
|
|
||||||
self?.loadingBlurView.layer.add(anim, forKey: "opacity")
|
|
||||||
// UIView.transition(with: self, duration: 0.2, animations: {
|
|
||||||
//// self.loadingBlurView.animator.fractionComplete = 0
|
|
||||||
//// self.loadingBlurView.effect = nil
|
|
||||||
//// self.loadingBlurView.alpha = 0
|
|
||||||
// }, completion: { _ in
|
|
||||||
// self.loadingBlurView = .init(effect: UIBlurEffect(style: .light), intensity: 0.4)
|
|
||||||
// })
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
// loadingBlurView.backgroundColor = .yellow.withAlphaComponent(0.4)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var stallTimer: Foundation.Timer?
|
|
||||||
let fullScreenBackgroundPlaceholder = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
|
|
||||||
|
|
||||||
var avatarDisposable: Disposable?
|
|
||||||
var didBeginLoadingAvatar = false
|
|
||||||
// let avatarPlaceholderView = UIImageView()
|
|
||||||
var timeLastFrameReceived: CFAbsoluteTime?
|
|
||||||
|
|
||||||
var isFullscreen: Bool = false
|
|
||||||
let videoLoadingThrottler = Throttler<Bool>(duration: 1, queue: .main)
|
|
||||||
|
|
||||||
deinit {
|
|
||||||
avatarDisposable?.dispose()
|
|
||||||
frameInputDisposable?.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(component: MediaStreamVideoComponent, availableSize: CGSize, state: State, transition: Transition) -> CGSize {
|
func update(component: MediaStreamVideoComponent, availableSize: CGSize, state: State, transition: Transition) -> CGSize {
|
||||||
self.state = state
|
self.state = state
|
||||||
// placeholderView.alpha = 0.7
|
|
||||||
// placeholderView.image = lastFrame[component.call.peerId.id.description]
|
|
||||||
self.component = component
|
self.component = component
|
||||||
self.onVideoPlaybackChange = component.onVideoPlaybackLiveChange
|
self.onVideoPlaybackChange = component.onVideoPlaybackLiveChange
|
||||||
self.isFullscreen = component.isFullscreen
|
self.isFullscreen = component.isFullscreen
|
||||||
@ -331,19 +281,16 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
var _stallTimer: Foundation.Timer { Foundation.Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
|
var _stallTimer: Foundation.Timer { Foundation.Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
|
||||||
guard let strongSelf = self else { return timer.invalidate() }
|
guard let strongSelf = self else { return timer.invalidate() }
|
||||||
|
|
||||||
// print("Timer emitting \(timer)")
|
|
||||||
let currentTime = CFAbsoluteTimeGetCurrent()
|
let currentTime = CFAbsoluteTimeGetCurrent()
|
||||||
if let lastFrameTime = strongSelf.timeLastFrameReceived,
|
if let lastFrameTime = strongSelf.timeLastFrameReceived,
|
||||||
currentTime - lastFrameTime > 0.5 {
|
currentTime - lastFrameTime > 0.5 {
|
||||||
// DispatchQueue.main.async {
|
|
||||||
strongSelf.videoLoadingThrottler.publish(true, includingLatest: true) { isStalled in
|
strongSelf.videoLoadingThrottler.publish(true, includingLatest: true) { isStalled in
|
||||||
strongSelf.videoStalled = isStalled
|
strongSelf.videoStalled = isStalled
|
||||||
strongSelf.onVideoPlaybackChange(!isStalled)
|
strongSelf.onVideoPlaybackChange(!isStalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
} }
|
} }
|
||||||
|
|
||||||
// TODO: use mapToThrottled (?)
|
// TODO: use mapToThrottled (?)
|
||||||
frameInputDisposable = input.start(next: { [weak self] input in
|
frameInputDisposable = input.start(next: { [weak self] input in
|
||||||
guard let strongSelf = self else { return }
|
guard let strongSelf = self else { return }
|
||||||
@ -355,7 +302,6 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
stallTimer = _stallTimer
|
stallTimer = _stallTimer
|
||||||
// RunLoop.main.add(stallTimer!, forMode: .common)
|
|
||||||
|
|
||||||
if let videoBlurView = self.videoRenderingContext.makeView(input: input, blur: true) {
|
if let videoBlurView = self.videoRenderingContext.makeView(input: input, blur: true) {
|
||||||
self.videoBlurView = videoBlurView
|
self.videoBlurView = videoBlurView
|
||||||
@ -373,75 +319,68 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
|
|
||||||
if let videoView = self.videoRenderingContext.makeView(input: input, blur: false, forceSampleBufferDisplayLayer: true) {
|
if let videoView = self.videoRenderingContext.makeView(input: input, blur: false, forceSampleBufferDisplayLayer: true) {
|
||||||
self.videoView = videoView
|
self.videoView = videoView
|
||||||
self/*.insertSubview(videoView, belowSubview: loadingBlurView)*/.addSubview(videoView)
|
self.addSubview(videoView)
|
||||||
videoView.alpha = 0
|
videoView.alpha = 0
|
||||||
UIView.animate(withDuration: 0.3) {
|
UIView.animate(withDuration: 0.3) {
|
||||||
videoView.alpha = 1
|
videoView.alpha = 1
|
||||||
}
|
}
|
||||||
if let sampleBufferVideoView = videoView as? SampleBufferVideoRenderingView {
|
if let sampleBufferVideoView = videoView as? SampleBufferVideoRenderingView {
|
||||||
sampleBufferVideoView.sampleBufferLayer.masksToBounds = true
|
sampleBufferVideoView.sampleBufferLayer.masksToBounds = true
|
||||||
// sampleBufferVideoView.sampleBufferLayer.cornerRadius = 10
|
|
||||||
|
|
||||||
if #available(iOS 13.0, *) {
|
if #available(iOS 13.0, *) {
|
||||||
sampleBufferVideoView.sampleBufferLayer.preventsDisplaySleepDuringVideoPlayback = true
|
sampleBufferVideoView.sampleBufferLayer.preventsDisplaySleepDuringVideoPlayback = true
|
||||||
}
|
}
|
||||||
// if #available(iOSApplicationExtension 15.0, iOS 15.0, *), AVPictureInPictureController.isPictureInPictureSupported() {
|
// if #available(iOSApplicationExtension 15.0, iOS 15.0, *), AVPictureInPictureController.isPictureInPictureSupported() {
|
||||||
final class PlaybackDelegateImpl: NSObject, AVPictureInPictureSampleBufferPlaybackDelegate {
|
final class PlaybackDelegateImpl: NSObject, AVPictureInPictureSampleBufferPlaybackDelegate {
|
||||||
var onTransitionFinished: (() -> Void)?
|
var onTransitionFinished: (() -> Void)?
|
||||||
func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, setPlaying playing: Bool) {
|
func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, setPlaying playing: Bool) {
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func pictureInPictureControllerTimeRangeForPlayback(_ pictureInPictureController: AVPictureInPictureController) -> CMTimeRange {
|
|
||||||
return CMTimeRange(start: .zero, duration: .positiveInfinity)
|
|
||||||
}
|
|
||||||
|
|
||||||
func pictureInPictureControllerIsPlaybackPaused(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, didTransitionToRenderSize newRenderSize: CMVideoDimensions) {
|
|
||||||
onTransitionFinished?()
|
|
||||||
print("pip finished")
|
|
||||||
}
|
|
||||||
|
|
||||||
func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, skipByInterval skipInterval: CMTime, completion completionHandler: @escaping () -> Void) {
|
|
||||||
completionHandler()
|
|
||||||
}
|
|
||||||
|
|
||||||
public func pictureInPictureControllerShouldProhibitBackgroundAudioPlayback(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pictureInPictureControllerTimeRangeForPlayback(_ pictureInPictureController: AVPictureInPictureController) -> CMTimeRange {
|
||||||
|
return CMTimeRange(start: .zero, duration: .positiveInfinity)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pictureInPictureControllerIsPlaybackPaused(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, didTransitionToRenderSize newRenderSize: CMVideoDimensions) {
|
||||||
|
onTransitionFinished?()
|
||||||
|
}
|
||||||
|
|
||||||
|
func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, skipByInterval skipInterval: CMTime, completion completionHandler: @escaping () -> Void) {
|
||||||
|
completionHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureControllerShouldProhibitBackgroundAudioPlayback(_ pictureInPictureController: AVPictureInPictureController) -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
var pictureInPictureController: AVPictureInPictureController? = nil
|
var pictureInPictureController: AVPictureInPictureController? = nil
|
||||||
if #available(iOS 15.0, *) {
|
if #available(iOS 15.0, *) {
|
||||||
pictureInPictureController = AVPictureInPictureController(contentSource: AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: sampleBufferVideoView.sampleBufferLayer, playbackDelegate: {
|
pictureInPictureController = AVPictureInPictureController(contentSource: AVPictureInPictureController.ContentSource(sampleBufferDisplayLayer: sampleBufferVideoView.sampleBufferLayer, playbackDelegate: {
|
||||||
let delegate = PlaybackDelegateImpl()
|
let delegate = PlaybackDelegateImpl()
|
||||||
delegate.onTransitionFinished = { [weak self] in
|
delegate.onTransitionFinished = {
|
||||||
if self?.videoView?.alpha == 0 {
|
|
||||||
// self?.videoView?.alpha = 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return delegate
|
return delegate
|
||||||
}()))
|
}()))
|
||||||
pictureInPictureController?.playerLayer.masksToBounds = false
|
pictureInPictureController?.playerLayer.masksToBounds = false
|
||||||
pictureInPictureController?.playerLayer.cornerRadius = 10
|
pictureInPictureController?.playerLayer.cornerRadius = 10
|
||||||
} else if AVPictureInPictureController.isPictureInPictureSupported() {
|
} else if AVPictureInPictureController.isPictureInPictureSupported() {
|
||||||
// TODO: support PiP for iOS < 15.0
|
|
||||||
// sampleBufferVideoView.sampleBufferLayer
|
|
||||||
pictureInPictureController = AVPictureInPictureController.init(playerLayer: AVPlayerLayer(player: AVPlayer()))
|
pictureInPictureController = AVPictureInPictureController.init(playerLayer: AVPlayerLayer(player: AVPlayer()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pictureInPictureController?.delegate = self
|
pictureInPictureController?.delegate = self
|
||||||
if #available(iOS 14.2, *) {
|
if #available(iOS 14.2, *) {
|
||||||
pictureInPictureController?.canStartPictureInPictureAutomaticallyFromInline = true
|
pictureInPictureController?.canStartPictureInPictureAutomaticallyFromInline = true
|
||||||
}
|
}
|
||||||
if #available(iOS 14.0, *) {
|
if #available(iOS 14.0, *) {
|
||||||
pictureInPictureController?.requiresLinearPlayback = true
|
pictureInPictureController?.requiresLinearPlayback = true
|
||||||
}
|
}
|
||||||
|
|
||||||
self.pictureInPictureController = pictureInPictureController
|
self.pictureInPictureController = pictureInPictureController
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
videoView.setOnOrientationUpdated { [weak state] _, _ in
|
videoView.setOnOrientationUpdated { [weak state] _, _ in
|
||||||
@ -464,7 +403,6 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// fullScreenBackgroundPlaceholder.removeFromSuperview()
|
|
||||||
} else if component.isFullscreen {
|
} else if component.isFullscreen {
|
||||||
if fullScreenBackgroundPlaceholder.superview == nil {
|
if fullScreenBackgroundPlaceholder.superview == nil {
|
||||||
insertSubview(fullScreenBackgroundPlaceholder, at: 0)
|
insertSubview(fullScreenBackgroundPlaceholder, at: 0)
|
||||||
@ -475,12 +413,6 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
}
|
}
|
||||||
fullScreenBackgroundPlaceholder.frame = .init(origin: .zero, size: availableSize)
|
fullScreenBackgroundPlaceholder.frame = .init(origin: .zero, size: availableSize)
|
||||||
|
|
||||||
// sheetView.frame = .init(x: 0, y: sheetTop, width: availableSize.width, height: sheetHeight)
|
|
||||||
// var aspect = videoView.getAspect()
|
|
||||||
// if aspect <= 0.01 {
|
|
||||||
// let aspect = !component.isFullscreen ? 16.0 / 9.0 : // 3.0 / 4.0
|
|
||||||
// }
|
|
||||||
|
|
||||||
let videoInset: CGFloat
|
let videoInset: CGFloat
|
||||||
if !component.isFullscreen {
|
if !component.isFullscreen {
|
||||||
videoInset = 16
|
videoInset = 16
|
||||||
@ -498,9 +430,8 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
let snapshot = videoView.snapshotView(afterScreenUpdates: false) ?? videoView.snapshotView(afterScreenUpdates: true) {
|
let snapshot = videoView.snapshotView(afterScreenUpdates: false) ?? videoView.snapshotView(afterScreenUpdates: true) {
|
||||||
lastFrame[component.call.peerId.id.description] = snapshot// ()!
|
lastFrame[component.call.peerId.id.description] = snapshot// ()!
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
var aspect = videoView.getAspect()
|
var aspect = videoView.getAspect()
|
||||||
// aspect == 1 the first run
|
|
||||||
if component.isFullscreen && self.hadVideo {
|
if component.isFullscreen && self.hadVideo {
|
||||||
if aspect <= 0.01 {
|
if aspect <= 0.01 {
|
||||||
aspect = 16.0 / 9
|
aspect = 16.0 / 9
|
||||||
@ -545,7 +476,6 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
|
|
||||||
if let videoBlurView = self.videoBlurView {
|
if let videoBlurView = self.videoBlurView {
|
||||||
videoBlurView.updateIsEnabled(component.isVisible)
|
videoBlurView.updateIsEnabled(component.isVisible)
|
||||||
// videoBlurView.isHidden = component.isFullscreen
|
|
||||||
if component.isFullscreen {
|
if component.isFullscreen {
|
||||||
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)
|
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)
|
||||||
} else {
|
} else {
|
||||||
@ -564,15 +494,15 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
videoSize = CGSize(width: 16 / 9 * 100.0, height: 100.0).aspectFitted(.init(width: availableSize.width - videoInset * 2, height: availableSize.height))
|
videoSize = CGSize(width: 16 / 9 * 100.0, height: 100.0).aspectFitted(.init(width: availableSize.width - videoInset * 2, height: availableSize.height))
|
||||||
}
|
}
|
||||||
loadingBlurView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - videoSize.width) / 2.0), y: floor((availableSize.height - videoSize.height) / 2.0)), size: videoSize)
|
loadingBlurView.frame = CGRect(origin: CGPoint(x: floor((availableSize.width - videoSize.width) / 2.0), y: floor((availableSize.height - videoSize.height) / 2.0)), size: videoSize)
|
||||||
print("[LBVFrame] \(loadingBlurView.frame)")
|
|
||||||
loadingBlurView.layer.cornerRadius = videoCornerRadius
|
loadingBlurView.layer.cornerRadius = videoCornerRadius
|
||||||
|
|
||||||
placeholderView.frame = loadingBlurView.frame
|
placeholderView.frame = loadingBlurView.frame
|
||||||
placeholderView.layer.cornerRadius = videoCornerRadius
|
placeholderView.layer.cornerRadius = videoCornerRadius
|
||||||
placeholderView.clipsToBounds = true
|
placeholderView.clipsToBounds = true
|
||||||
|
|
||||||
// shimmerOverlayLayer.frame = loadingBlurView.bounds
|
|
||||||
shimmerBorderLayer.frame = loadingBlurView.bounds
|
shimmerBorderLayer.frame = loadingBlurView.bounds
|
||||||
|
|
||||||
let borderMask = CAShapeLayer()
|
let borderMask = CAShapeLayer()
|
||||||
borderMask.path = CGPath(roundedRect: .init(x: 0, y: 0, width: shimmerBorderLayer.bounds.width, height: shimmerBorderLayer.bounds.height), cornerWidth: videoCornerRadius, cornerHeight: videoCornerRadius, transform: nil)
|
borderMask.path = CGPath(roundedRect: .init(x: 0, y: 0, width: shimmerBorderLayer.bounds.width, height: shimmerBorderLayer.bounds.height), cornerWidth: videoCornerRadius, cornerHeight: videoCornerRadius, transform: nil)
|
||||||
borderMask.fillColor = UIColor.white.withAlphaComponent(0.4).cgColor
|
borderMask.fillColor = UIColor.white.withAlphaComponent(0.4).cgColor
|
||||||
@ -581,9 +511,6 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
shimmerBorderLayer.mask = borderMask
|
shimmerBorderLayer.mask = borderMask
|
||||||
shimmerBorderLayer.cornerRadius = videoCornerRadius
|
shimmerBorderLayer.cornerRadius = videoCornerRadius
|
||||||
|
|
||||||
if component.isFullscreen {
|
|
||||||
// loadingBlurView.removeFromSuperview()
|
|
||||||
}
|
|
||||||
if !self.hadVideo {
|
if !self.hadVideo {
|
||||||
|
|
||||||
if self.noSignalTimer == nil {
|
if self.noSignalTimer == nil {
|
||||||
@ -668,9 +595,6 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
presentation.removeFromSuperview()
|
presentation.removeFromSuperview()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
|
|
||||||
// presentation.removeFromSuperlayer()
|
|
||||||
// }
|
|
||||||
UIView.animate(withDuration: 0.1) { [self] in
|
UIView.animate(withDuration: 0.1) { [self] in
|
||||||
videoBlurView?.alpha = 0
|
videoBlurView?.alpha = 0
|
||||||
}
|
}
|
||||||
@ -726,3 +650,24 @@ final class MediaStreamVideoComponent: Component {
|
|||||||
|
|
||||||
// TODO: move to appropriate place
|
// TODO: move to appropriate place
|
||||||
fileprivate var lastFrame: [String: UIView] = [:]
|
fileprivate var lastFrame: [String: UIView] = [:]
|
||||||
|
|
||||||
|
class CustomIntensityVisualEffectView: UIVisualEffectView {
|
||||||
|
private var animator: UIViewPropertyAnimator!
|
||||||
|
|
||||||
|
init(effect: UIVisualEffect, intensity: CGFloat) {
|
||||||
|
super.init(effect: nil)
|
||||||
|
animator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [weak self] in self?.effect = effect }
|
||||||
|
animator.startAnimation()
|
||||||
|
animator.pauseAnimation()
|
||||||
|
animator.fractionComplete = intensity
|
||||||
|
animator.pausesOnCompletion = true
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder aDecoder: NSCoder) {
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
animator.stopAnimation(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user