mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-11-07 17:30:12 +00:00
Observing video input to detect stall
This commit is contained in:
parent
a74098cd09
commit
c65ad5ad97
@ -16,6 +16,7 @@ public final class AnimatedCountView: UIView {
|
|||||||
private let foregroundView = UIView()
|
private let foregroundView = UIView()
|
||||||
private let foregroundGradientLayer = CAGradientLayer()
|
private let foregroundGradientLayer = CAGradientLayer()
|
||||||
private let maskingView = UIView()
|
private let maskingView = UIView()
|
||||||
|
private var scaleFactor: CGFloat { 0.7 }
|
||||||
|
|
||||||
override init(frame: CGRect = .zero) {
|
override init(frame: CGRect = .zero) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
@ -49,7 +50,7 @@ public final class AnimatedCountView: UIView {
|
|||||||
self.foregroundGradientLayer.frame = CGRect(origin: .zero, size: bounds.size).insetBy(dx: -60, dy: -60)
|
self.foregroundGradientLayer.frame = CGRect(origin: .zero, size: bounds.size).insetBy(dx: -60, dy: -60)
|
||||||
self.maskingView.frame = CGRect(origin: .zero, size: bounds.size)
|
self.maskingView.frame = CGRect(origin: .zero, size: bounds.size)
|
||||||
countLabel.frame = CGRect(origin: .zero, size: bounds.size)
|
countLabel.frame = CGRect(origin: .zero, size: bounds.size)
|
||||||
subtitleLabel.frame = .init(x: bounds.midX - subtitleLabel.intrinsicContentSize.width / 2 - 10, y: subtitleLabel.text == "No viewers" ? bounds.midY - 10 : bounds.height - 6, width: subtitleLabel.intrinsicContentSize.width + 20, height: 20)
|
subtitleLabel.frame = .init(x: bounds.midX - subtitleLabel.intrinsicContentSize.width / 2 - 10, y: subtitleLabel.text == "No viewers" ? bounds.midY - 8 : bounds.height - 6, width: subtitleLabel.intrinsicContentSize.width + 20, height: 20)
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(countString: String, subtitle: String) {
|
func update(countString: String, subtitle: String) {
|
||||||
@ -64,8 +65,9 @@ public final class AnimatedCountView: UIView {
|
|||||||
// self.countLabel.attributedText = NSAttributedString(string: text, attributes: [.font: UIFont.systemFont(ofSize: 60, weight: .semibold)])
|
// self.countLabel.attributedText = NSAttributedString(string: text, attributes: [.font: UIFont.systemFont(ofSize: 60, weight: .semibold)])
|
||||||
// } else {
|
// } else {
|
||||||
// self.countLabel.attributedText = NSAttributedString(string: text, attributes: [.font: UIFont.systemFont(ofSize: 54, weight: .semibold)])
|
// self.countLabel.attributedText = NSAttributedString(string: text, attributes: [.font: UIFont.systemFont(ofSize: 54, weight: .semibold)])
|
||||||
// }
|
//
|
||||||
self.countLabel.attributedText = NSAttributedString(string: text, font: Font.with(size: 60.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor: .white)
|
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, attributes: [.font: UIFont.systemFont(ofSize: 60, weight: .semibold)])
|
// 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))
|
// var timerSize = self.timerNode.updateLayout(CGSize(width: size.width + 100.0, height: size.height))
|
||||||
// if timerSize.width > size.width - 32.0 {
|
// if timerSize.width > size.width - 32.0 {
|
||||||
@ -176,6 +178,14 @@ class AnimatedCountLabel: UILabel {
|
|||||||
private var chars = [AnimatedCharLayer]()
|
private var chars = [AnimatedCharLayer]()
|
||||||
private let containerView = UIView()
|
private let containerView = UIView()
|
||||||
|
|
||||||
|
var itemWidth: CGFloat { 36 * fontSize / 60 }
|
||||||
|
var commaWidthForSpacing: CGFloat { 8 * fontSize / 60 }
|
||||||
|
var commaFrameWidth: CGFloat { 36 * fontSize / 60 }
|
||||||
|
var interItemSpacing: CGFloat { 0 * fontSize / 60 }
|
||||||
|
var didBegin = false
|
||||||
|
var fontSize: CGFloat = 60
|
||||||
|
var scaleFactor: CGFloat { 1 }
|
||||||
|
|
||||||
override init(frame: CGRect = .zero) {
|
override init(frame: CGRect = .zero) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
containerView.clipsToBounds = false
|
containerView.clipsToBounds = false
|
||||||
@ -186,11 +196,6 @@ class AnimatedCountLabel: UILabel {
|
|||||||
required init?(coder: NSCoder) {
|
required init?(coder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
var itemWidth: CGFloat { 36 }
|
|
||||||
var commaWidthForSpacing: CGFloat { 8 }
|
|
||||||
var commaFrameWidth: CGFloat { 36 }
|
|
||||||
var interItemSpacing: CGFloat { 0 }
|
|
||||||
var didBegin = false
|
|
||||||
|
|
||||||
private func offsetForChar(at index: Int, within characters: [NSAttributedString]? = nil) -> CGFloat {
|
private func offsetForChar(at index: Int, within characters: [NSAttributedString]? = nil) -> CGFloat {
|
||||||
if let characters {
|
if let characters {
|
||||||
@ -201,7 +206,7 @@ class AnimatedCountLabel: UILabel {
|
|||||||
return $0 + itemWidth + interItemSpacing
|
return $0 + itemWidth + interItemSpacing
|
||||||
}
|
}
|
||||||
if characters.count > index && characters[index].string == "," {
|
if characters.count > index && characters[index].string == "," {
|
||||||
offset -= 4
|
offset -= commaWidthForSpacing / 2 // 4
|
||||||
}
|
}
|
||||||
return offset
|
return offset
|
||||||
} else {
|
} else {
|
||||||
@ -212,7 +217,7 @@ class AnimatedCountLabel: UILabel {
|
|||||||
return $0 + itemWidth + interItemSpacing
|
return $0 + itemWidth + interItemSpacing
|
||||||
}
|
}
|
||||||
if self.chars.count > index && self.chars[index].attributedText?.string == "," {
|
if self.chars.count > index && self.chars[index].attributedText?.string == "," {
|
||||||
offset -= 4
|
offset -= commaWidthForSpacing / 2
|
||||||
}
|
}
|
||||||
return offset
|
return offset
|
||||||
}
|
}
|
||||||
@ -226,8 +231,7 @@ class AnimatedCountLabel: UILabel {
|
|||||||
}
|
}
|
||||||
return $0 + itemWidth + interItemSpacing
|
return $0 + itemWidth + interItemSpacing
|
||||||
}*/ - 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, y: 0, width: countWidth, 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.size.width = char.attributedText?.string == "," ? commaFrameWidth : itemWidth
|
||||||
@ -362,16 +366,16 @@ class AnimatedCountLabel: UILabel {
|
|||||||
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/*, delay: initialDuration * Double(changeIndex)*/) { [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 > self.bounds.width {
|
if countWidth * scaleFactor > self.bounds.width {
|
||||||
let scale = countWidth / self.bounds.width
|
let scale = (self.bounds.width - 32) / (countWidth * scaleFactor)
|
||||||
self.transform = .init(scaleX: scale, y: scale)
|
containerView.transform = .init(scaleX: scale, y: scale)
|
||||||
} else {
|
} else {
|
||||||
self.transform = .identity
|
containerView.transform = .init(scaleX: scaleFactor, y: scaleFactor)
|
||||||
}
|
}
|
||||||
// containerView.backgroundColor = .red.withAlphaComponent(0.3)
|
// containerView.backgroundColor = .red.withAlphaComponent(0.3)
|
||||||
}
|
}
|
||||||
} else if countWidth > 0 {
|
} else if countWidth > 0 {
|
||||||
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 * scaleFactor, y: 0, width: countWidth * scaleFactor, height: self.bounds.height)
|
||||||
didBegin = true
|
didBegin = true
|
||||||
}
|
}
|
||||||
// self.backgroundColor = .green.withAlphaComponent(0.2)
|
// self.backgroundColor = .green.withAlphaComponent(0.2)
|
||||||
@ -451,7 +455,7 @@ class AnimatedCountLabel: UILabel {
|
|||||||
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// CFTimeInterval(DispatchTime.now().uptimeNanoseconds / 1000000000)// CACurrentMediaTime()
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + beginTime) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + beginTime) { [self] in
|
||||||
let currentTime = CFTimeInterval(DispatchTime.now().uptimeNanoseconds / 1000000000)
|
let currentTime = CFTimeInterval(DispatchTime.now().uptimeNanoseconds / 1000000000)
|
||||||
let beginTime: CFTimeInterval = 0
|
let beginTime: CFTimeInterval = 0
|
||||||
print("[DIFF-in] \(currentTime - beginTimeOffset)")
|
print("[DIFF-in] \(currentTime - beginTimeOffset)")
|
||||||
@ -479,7 +483,7 @@ class AnimatedCountLabel: UILabel {
|
|||||||
|
|
||||||
let animation = CAKeyframeAnimation()
|
let animation = CAKeyframeAnimation()
|
||||||
animation.keyPath = "position.y"
|
animation.keyPath = "position.y"
|
||||||
animation.values = [20, -6, 0]
|
animation.values = [20 * fontSize / 60, -6 * fontSize / 60, 0]
|
||||||
animation.keyTimes = [0, 0.64, 1]
|
animation.keyTimes = [0, 0.64, 1]
|
||||||
animation.timingFunction = CAMediaTimingFunction.init(name: .easeInEaseOut)
|
animation.timingFunction = CAMediaTimingFunction.init(name: .easeInEaseOut)
|
||||||
animation.duration = duration / 0.64
|
animation.duration = duration / 0.64
|
||||||
|
|||||||
@ -967,6 +967,7 @@ public final class _MediaStreamComponent: CombinedComponent {
|
|||||||
let moreAnimationTag = GenericComponentViewTag()
|
let moreAnimationTag = GenericComponentViewTag()
|
||||||
|
|
||||||
return { context in
|
return { context in
|
||||||
|
var forceFullScreenInLandscape: Bool { false }
|
||||||
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
|
||||||
if environment.isVisible {
|
if environment.isVisible {
|
||||||
} else {
|
} else {
|
||||||
@ -1000,16 +1001,17 @@ public final class _MediaStreamComponent: CombinedComponent {
|
|||||||
var isFullscreen = state.isFullscreen
|
var isFullscreen = state.isFullscreen
|
||||||
let isLandscape = context.availableSize.width > context.availableSize.height
|
let isLandscape = context.availableSize.width > context.availableSize.height
|
||||||
|
|
||||||
if let videoSize = context.state.videoSize {
|
// if let videoSize = context.state.videoSize {
|
||||||
// Always fullscreen in landscape
|
// Always fullscreen in landscape
|
||||||
if /*videoSize.width > videoSize.height &&*/ isLandscape && !isFullscreen {
|
// TODO: support landscape sheet (wrap in scrollview, video size same as portrait)
|
||||||
|
if forceFullScreenInLandscape && /*videoSize.width > videoSize.height &&*/ isLandscape && !isFullscreen {
|
||||||
state.isFullscreen = true
|
state.isFullscreen = true
|
||||||
isFullscreen = true
|
isFullscreen = true
|
||||||
} else if videoSize.width > videoSize.height && !isLandscape && isFullscreen {
|
} else if let videoSize = context.state.videoSize, videoSize.width > videoSize.height && !isLandscape && isFullscreen {
|
||||||
state.isFullscreen = false
|
state.isFullscreen = false
|
||||||
isFullscreen = false
|
isFullscreen = false
|
||||||
}
|
}
|
||||||
}
|
// }
|
||||||
|
|
||||||
let videoHeight: CGFloat = context.availableSize.width / 16 * 9
|
let videoHeight: CGFloat = context.availableSize.width / 16 * 9
|
||||||
let bottomPadding = 40 + environment.safeInsets.bottom
|
let bottomPadding = 40 + environment.safeInsets.bottom
|
||||||
|
|||||||
@ -9,15 +9,11 @@ import Display
|
|||||||
import ShimmerEffect
|
import ShimmerEffect
|
||||||
|
|
||||||
import TelegramCore
|
import TelegramCore
|
||||||
|
import SwiftSignalKit
|
||||||
|
|
||||||
typealias MediaStreamVideoComponent = _MediaStreamVideoComponent
|
typealias MediaStreamVideoComponent = _MediaStreamVideoComponent
|
||||||
|
|
||||||
class CustomIntensityVisualEffectView: UIVisualEffectView {
|
class CustomIntensityVisualEffectView: UIVisualEffectView {
|
||||||
|
|
||||||
/// Create visual effect view with given effect and its intensity
|
|
||||||
///
|
|
||||||
/// - Parameters:
|
|
||||||
/// - effect: visual effect, eg UIBlurEffect(style: .dark)
|
|
||||||
/// - intensity: custom intensity from 0.0 (no effect) to 1.0 (full effect) using linear scale
|
|
||||||
init(effect: UIVisualEffect, intensity: CGFloat) {
|
init(effect: UIVisualEffect, intensity: CGFloat) {
|
||||||
super.init(effect: nil)
|
super.init(effect: nil)
|
||||||
animator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [unowned self] in self.effect = effect }
|
animator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [unowned self] in self.effect = effect }
|
||||||
@ -27,10 +23,27 @@ class CustomIntensityVisualEffectView: UIVisualEffectView {
|
|||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError()
|
fatalError()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Private
|
var animator: UIViewPropertyAnimator!
|
||||||
private var animator: UIViewPropertyAnimator!
|
|
||||||
|
private var displayLink: CADisplayLink?
|
||||||
|
|
||||||
|
func setIntensity(_ intensity: CGFloat, animated: Bool) {
|
||||||
|
self.displayLink?.invalidate()
|
||||||
|
let displaylink = CADisplayLink(
|
||||||
|
target: self,
|
||||||
|
selector: #selector(displayLinkStep)
|
||||||
|
)
|
||||||
|
self.displayLink = displaylink
|
||||||
|
displaylink.add(
|
||||||
|
to: .current,
|
||||||
|
forMode: RunLoop.Mode.default
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func displayLinkStep() {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class _MediaStreamVideoComponent: Component {
|
final class _MediaStreamVideoComponent: Component {
|
||||||
@ -135,7 +148,7 @@ final class _MediaStreamVideoComponent: Component {
|
|||||||
|
|
||||||
private var requestedExpansion: Bool = false
|
private var requestedExpansion: Bool = false
|
||||||
|
|
||||||
private var noSignalTimer: Timer?
|
private var noSignalTimer: Foundation.Timer?
|
||||||
private var noSignalTimeout: Bool = false
|
private var noSignalTimeout: Bool = false
|
||||||
|
|
||||||
private weak var state: State?
|
private weak var state: State?
|
||||||
@ -176,12 +189,27 @@ final class _MediaStreamVideoComponent: Component {
|
|||||||
let shimmerBorderLayer = CALayer()
|
let shimmerBorderLayer = CALayer()
|
||||||
let placeholderView = UIImageView()
|
let placeholderView = UIImageView()
|
||||||
|
|
||||||
func update(component: _MediaStreamVideoComponent, availableSize: CGSize, state: State, transition: Transition) -> CGSize {
|
var videoStalled = false {
|
||||||
self.state = state
|
didSet {
|
||||||
// placeholderView.alpha = 0.7
|
if videoStalled != oldValue {
|
||||||
// placeholderView.image = lastFrame[component.call.peerId.id.description]
|
self.updateVideoStalled(isStalled: self.videoStalled)
|
||||||
|
// state?.updated()
|
||||||
if component.videoLoading {
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private var frameInputDisposable: Disposable?
|
||||||
|
|
||||||
|
private func updateVideoStalled(isStalled: Bool) {
|
||||||
|
if isStalled {
|
||||||
|
guard let component = self.component else { return }
|
||||||
|
// let effect = UIBlurEffect(style: .light)
|
||||||
|
// let intensity: CGFloat = 0.4
|
||||||
|
// self.loadingBlurView.effect = nil
|
||||||
|
// self.loadingBlurView.animator.stopAnimation(true)
|
||||||
|
// self.loadingBlurView.animator = UIViewPropertyAnimator(duration: 1, curve: .linear) { [unowned loadingBlurView] in loadingBlurView.effect = effect }
|
||||||
|
// self.loadingBlurView.animator.fractionComplete = intensity
|
||||||
|
// self.loadingBlurView.animator.fractionComplete = 0.4
|
||||||
|
// self.loadingBlurView.effect = UIBlurEffect(style: .light)
|
||||||
if let frame = lastFrame[component.call.peerId.id.description] {
|
if let frame = lastFrame[component.call.peerId.id.description] {
|
||||||
placeholderView.subviews.forEach { $0.removeFromSuperview() }
|
placeholderView.subviews.forEach { $0.removeFromSuperview() }
|
||||||
placeholderView.addSubview(frame)
|
placeholderView.addSubview(frame)
|
||||||
@ -220,8 +248,8 @@ final class _MediaStreamVideoComponent: Component {
|
|||||||
borderMask.strokeColor = UIColor.white.cgColor
|
borderMask.strokeColor = UIColor.white.cgColor
|
||||||
borderMask.lineWidth = 4
|
borderMask.lineWidth = 4
|
||||||
// let borderMask = CALayer()
|
// let borderMask = CALayer()
|
||||||
shimmerBorderLayer.mask = borderMask
|
|
||||||
borderShimmer = .init()
|
borderShimmer = .init()
|
||||||
|
shimmerBorderLayer.mask = borderMask
|
||||||
borderShimmer.layer = shimmerBorderLayer
|
borderShimmer.layer = shimmerBorderLayer
|
||||||
borderShimmer.testUpdate(background: .clear, foreground: .white)
|
borderShimmer.testUpdate(background: .clear, foreground: .white)
|
||||||
loadingBlurView.alpha = 1
|
loadingBlurView.alpha = 1
|
||||||
@ -229,6 +257,81 @@ final class _MediaStreamVideoComponent: Component {
|
|||||||
if hadVideo {
|
if hadVideo {
|
||||||
self.loadingBlurView.removeFromSuperview()
|
self.loadingBlurView.removeFromSuperview()
|
||||||
placeholderView.removeFromSuperview()
|
placeholderView.removeFromSuperview()
|
||||||
|
} else {
|
||||||
|
// Accounting for delay in first frame received
|
||||||
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [self] in
|
||||||
|
guard !self.videoStalled else { return }
|
||||||
|
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.removeFromSuperview()
|
||||||
|
})
|
||||||
|
placeholderView.removeFromSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var stallTimer: Foundation.Timer?
|
||||||
|
let fullScreenBackgroundPlaceholder = UIVisualEffectView(effect: UIBlurEffect(style: .regular))
|
||||||
|
func update(component: _MediaStreamVideoComponent, availableSize: CGSize, state: State, transition: Transition) -> CGSize {
|
||||||
|
self.state = state
|
||||||
|
// placeholderView.alpha = 0.7
|
||||||
|
// placeholderView.image = lastFrame[component.call.peerId.id.description]
|
||||||
|
self.component = component
|
||||||
|
|
||||||
|
if component.videoLoading || self.videoStalled {
|
||||||
|
updateVideoStalled(isStalled: true)
|
||||||
|
/*if let frame = lastFrame[component.call.peerId.id.description] {
|
||||||
|
placeholderView.subviews.forEach { $0.removeFromSuperview() }
|
||||||
|
placeholderView.addSubview(frame)
|
||||||
|
frame.frame = placeholderView.bounds
|
||||||
|
// placeholderView.backgroundColor = .green
|
||||||
|
} else {
|
||||||
|
// placeholderView.subviews.forEach { $0.removeFromSuperview() }
|
||||||
|
// placeholderView.backgroundColor = .red
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hadVideo && placeholderView.superview == nil {
|
||||||
|
addSubview(placeholderView)
|
||||||
|
}
|
||||||
|
if loadingBlurView.superview == nil {
|
||||||
|
addSubview(loadingBlurView)
|
||||||
|
}
|
||||||
|
if shimmerOverlayLayer.superlayer == nil {
|
||||||
|
loadingBlurView.layer.addSublayer(shimmerOverlayLayer)
|
||||||
|
loadingBlurView.layer.addSublayer(shimmerBorderLayer)
|
||||||
|
}
|
||||||
|
loadingBlurView.clipsToBounds = true
|
||||||
|
shimmer = .init()
|
||||||
|
shimmer.layer = shimmerOverlayLayer
|
||||||
|
shimmerOverlayView.compositingFilter = "softLightBlendMode"
|
||||||
|
shimmer.testUpdate(background: .clear, foreground: .white.withAlphaComponent(0.4))
|
||||||
|
loadingBlurView.layer.cornerRadius = 10
|
||||||
|
shimmerOverlayLayer.opacity = 0.6
|
||||||
|
|
||||||
|
shimmerBorderLayer.cornerRadius = 10
|
||||||
|
shimmerBorderLayer.masksToBounds = true
|
||||||
|
shimmerBorderLayer.compositingFilter = "softLightBlendMode"
|
||||||
|
|
||||||
|
let borderMask = CAShapeLayer()
|
||||||
|
borderMask.path = CGPath(roundedRect: .init(x: 0, y: 0, width: loadingBlurView.bounds.width, height: loadingBlurView.bounds.height), cornerWidth: 10, cornerHeight: 10, transform: nil)
|
||||||
|
borderMask.fillColor = UIColor.clear.cgColor
|
||||||
|
borderMask.strokeColor = UIColor.white.cgColor
|
||||||
|
borderMask.lineWidth = 4
|
||||||
|
// let borderMask = CALayer()
|
||||||
|
borderShimmer = .init()
|
||||||
|
shimmerBorderLayer.mask = borderMask
|
||||||
|
borderShimmer.layer = shimmerBorderLayer
|
||||||
|
borderShimmer.testUpdate(background: .clear, foreground: .white)
|
||||||
|
loadingBlurView.alpha = 1*/
|
||||||
|
} else {
|
||||||
|
updateVideoStalled(isStalled: false)
|
||||||
|
/*if hadVideo {
|
||||||
|
self.loadingBlurView.removeFromSuperview()
|
||||||
|
placeholderView.removeFromSuperview()
|
||||||
} else {
|
} else {
|
||||||
// Accounting for delay in first frame received
|
// Accounting for delay in first frame received
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [self] in
|
||||||
@ -239,11 +342,39 @@ final class _MediaStreamVideoComponent: Component {
|
|||||||
})
|
})
|
||||||
placeholderView.removeFromSuperview()
|
placeholderView.removeFromSuperview()
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
if component.hasVideo, self.videoView == nil {
|
if component.hasVideo, self.videoView == nil {
|
||||||
if let input = component.call.video(endpointId: "unified") {
|
if let input = component.call.video(endpointId: "unified") {
|
||||||
|
var _stallTimer: Foundation.Timer { Foundation.Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] timer in
|
||||||
|
guard let strongSelf = self else { return timer.invalidate() }
|
||||||
|
print("Timer emitting \(timer)")
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
strongSelf.videoStalled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO: use mapToThrottled (?)
|
||||||
|
frameInputDisposable = input.start(next: { [weak self] input in
|
||||||
|
guard let strongSelf = self else { return }
|
||||||
|
print("input")
|
||||||
|
// TODO: optimize with throttle
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
strongSelf.stallTimer?.invalidate()
|
||||||
|
strongSelf.stallTimer = _stallTimer
|
||||||
|
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
|
// print(strongSelf.videoStalled)
|
||||||
|
// if strongSelf.videoStalled {
|
||||||
|
// strongSelf.stallTimer?.fire()
|
||||||
|
// }
|
||||||
|
RunLoop.main.add(strongSelf.stallTimer!, forMode: .common)
|
||||||
|
strongSelf.videoStalled = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
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
|
||||||
self.insertSubview(videoBlurView, belowSubview: self.blurTintView)
|
self.insertSubview(videoBlurView, belowSubview: self.blurTintView)
|
||||||
@ -354,6 +485,12 @@ final class _MediaStreamVideoComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fullScreenBackgroundPlaceholder.removeFromSuperview()
|
||||||
|
} else if component.isFullscreen {
|
||||||
|
if fullScreenBackgroundPlaceholder.superview == nil {
|
||||||
|
insertSubview(fullScreenBackgroundPlaceholder, at: 0)
|
||||||
|
}
|
||||||
|
fullScreenBackgroundPlaceholder.frame = self.bounds
|
||||||
}
|
}
|
||||||
|
|
||||||
// sheetView.frame = .init(x: 0, y: sheetTop, width: availableSize.width, height: sheetHeight)
|
// sheetView.frame = .init(x: 0, y: sheetTop, width: availableSize.width, height: sheetHeight)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user