Throttling playback status change

This commit is contained in:
Ilya Yelagov 2022-12-08 00:07:59 +04:00
parent bfa7de8e27
commit 17ef4ae35b
2 changed files with 153 additions and 110 deletions

View File

@ -801,7 +801,8 @@ public final class _MediaStreamComponent: CombinedComponent {
strongSelf.updated(transition: .immediate)
})
self.networkStateDisposable = (call.account.networkState |> deliverOnMainQueue).start(next: { [weak self] state in
// 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:
@ -828,7 +829,7 @@ public final class _MediaStreamComponent: CombinedComponent {
if prev != self?.videoStalled {
self?.updated(transition: .immediate)
}
})
})*/
let callPeer = call.accountContext.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: call.peerId))
@ -956,6 +957,7 @@ public final class _MediaStreamComponent: CombinedComponent {
public static var body: Body {
let background = Child(Rectangle.self)
let dismissTapComponent = Child(Rectangle.self)
let video = Child(MediaStreamVideoComponent.self)
// let navigationBar = Child(NavigationBarComponent.self)
// let toolbar = Child(ToolbarComponent.self)
@ -1037,6 +1039,14 @@ public final class _MediaStreamComponent: CombinedComponent {
dragOffset = max(context.state.dismissOffset, sheetHeight - context.availableSize.height + context.view.safeAreaInsets.top)// sheetHeight - UIScreen.main.bounds.height
}
let dismissTapAreaHeight = isFullscreen ? 0 : (context.availableSize.height - sheetHeight + dragOffset)
let dismissTapComponent = dismissTapComponent.update(
component: Rectangle(color: .red.withAlphaComponent(0)),
availableSize: CGSize(width: context.availableSize.width, height: dismissTapAreaHeight),
transition: context.transition
)
let video = video.update(
component: MediaStreamVideoComponent(
call: context.component.call,
@ -1044,7 +1054,7 @@ public final class _MediaStreamComponent: CombinedComponent {
isVisible: environment.isVisible && context.state.isVisibleInHierarchy,
isAdmin: context.state.canManageCall,
peerTitle: context.state.peerTitle,
// TODO: find out how to get image
// TODO: remove // find out how to get image
peerImage: nil,
isFullscreen: isFullscreen,
videoLoading: context.state.videoStalled,
@ -1068,8 +1078,12 @@ public final class _MediaStreamComponent: CombinedComponent {
state?.videoSize = size
},
onVideoPlaybackLiveChange: { [weak state] isLive in
state?.videoStalled = !isLive
state?.updated()
guard let state else { return }
let wasLive = !state.videoStalled
if isLive != wasLive {
state.videoStalled = !isLive
state.updated()
}
}
),
availableSize: context.availableSize,
@ -1381,15 +1395,8 @@ public final class _MediaStreamComponent: CombinedComponent {
}
let availableSize = context.availableSize
let safeAreaTop = context.view.safeAreaInsets.top
context.add(background
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
.gesture(.tap { [weak state] in
guard let state = state else {
return
}
state.toggleDisplayUI()
})
.gesture(.pan { [weak state] panState in
let onPanGesture: ((Gesture.PanGestureState) -> Void) = { [weak state] panState in
guard let state = state else {
return
}
@ -1434,11 +1441,31 @@ public final class _MediaStreamComponent: CombinedComponent {
}
}
}
}
context.add(background
.position(CGPoint(x: context.availableSize.width / 2.0, y: context.availableSize.height / 2.0))
.gesture(.tap { [weak state] in
guard let state = state, state.isFullscreen else {
return
}
state.toggleDisplayUI()
})
.gesture(.pan { panState in
onPanGesture(panState)
})
)
// var bottomComponent: AnyComponent<Empty>?
// var fullScreenToolbarComponent: AnyComponent<Empty>?
context.add(dismissTapComponent
.position(CGPoint(x: context.availableSize.width / 2, y: dismissTapAreaHeight / 2))
.gesture(.tap {
_ = call.leave(terminateIfPossible: false)
})
.gesture(.pan(onPanGesture))
)
if !isFullscreen {
let bottomComponent = AnyComponent(ButtonsRowComponent(
bottomInset: environment.safeInsets.bottom,

View File

@ -238,10 +238,10 @@ final class _MediaStreamVideoComponent: Component {
// 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 frameView = lastFrame[component.call.peerId.id.description] {
placeholderView.subviews.forEach { $0.removeFromSuperview() }
placeholderView.addSubview(frame)
frame.frame = placeholderView.bounds
placeholderView.addSubview(frameView)
frameView.frame = placeholderView.bounds
// placeholderView.backgroundColor = .green
} else {
// placeholderView.addSubview(avatarPlaceholderView)
@ -252,8 +252,12 @@ final class _MediaStreamVideoComponent: Component {
if !hadVideo && placeholderView.superview == nil {
addSubview(placeholderView)
}
let needsFadeInAnimation = hadVideo
if loadingBlurView.superview == nil {
addSubview(loadingBlurView)
if needsFadeInAnimation {
let anim = CABasicAnimation(keyPath: "opacity")
anim.duration = 0.5
anim.fromValue = 0
@ -262,20 +266,22 @@ final class _MediaStreamVideoComponent: Component {
anim.isRemovedOnCompletion = false
loadingBlurView.layer.add(anim, forKey: "opacity")
}
}
if shimmerBorderLayer.superlayer == nil {
// loadingBlurView.contentView.layer.addSublayer(shimmerOverlayLayer)
loadingBlurView.contentView.layer.addSublayer(shimmerBorderLayer)
}
loadingBlurView.clipsToBounds = true
if shimmerOverlayLayer.mask == nil {
shimmer = .init()
shimmer.layer = shimmerOverlayLayer
shimmerOverlayView.compositingFilter = "softLightBlendMode"
shimmer.testUpdate(background: .clear, foreground: .white.withAlphaComponent(0.4))
}
// if shimmerOverlayLayer.mask == nil {
// 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
let cornerRadius = loadingBlurView.layer.cornerRadius
// shimmerOverlayLayer.opacity = 0.6
shimmerBorderLayer.cornerRadius = cornerRadius // TODO: check isFullScreeen
shimmerBorderLayer.masksToBounds = true
shimmerBorderLayer.compositingFilter = "softLightBlendMode"
@ -295,14 +301,14 @@ final class _MediaStreamVideoComponent: Component {
// testBorder.frame = shimmerBorderLayer.bounds
// let borderMask = CALayer()
// shimmerBorderLayer.removeAllAnimations()
// if shimmerBorderLayer.mask == nil {
// if shimmerBorderLayer.mask == nil {
borderShimmer = .init()
shimmerBorderLayer.mask = borderMask
borderShimmer.layer = shimmerBorderLayer
shimmerBorderLayer.backgroundColor = UIColor.clear.cgColor
// shimmerBorderLayer.backgroundColor = UIColor.green.withAlphaComponent(0.4).cgColor
// shimmerBorderLayer.backgroundColor = UIColor.green.withAlphaComponent(0.4).cgColor
borderShimmer.testUpdate(background: .clear, foreground: .white)
// }
// }
loadingBlurView.alpha = 1
} else {
if hadVideo {
@ -313,32 +319,32 @@ final class _MediaStreamVideoComponent: Component {
anim.toValue = 0
anim.fillMode = .forwards
anim.isRemovedOnCompletion = false
anim.completion = { [self] _ in
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [self] in
loadingBlurView.removeFromSuperview()
// loadingBlurView = .init(effect: UIBlurEffect(style: .light), intensity: 0.4)
placeholderView.removeFromSuperview()
anim.completion = { [weak self] _ in
guard self?.videoStalled == false else { return }
self?.loadingBlurView.removeFromSuperview()
self?.placeholderView.removeFromSuperview()
}
loadingBlurView.layer.add(anim, forKey: "opacity")
} else {
// Accounting for delay in first frame received
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [self] in
guard !self.videoStalled else { return }
DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
guard self?.videoStalled == false else { return }
// TODO: animate blur intesity with UIPropertyAnimator
loadingBlurView.layer.removeAllAnimations()
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 = { _ in
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.removeFromSuperview()
self?.placeholderView.removeFromSuperview()
}
loadingBlurView.layer.add(anim, forKey: "opacity")
self?.loadingBlurView.layer.add(anim, forKey: "opacity")
// UIView.transition(with: self, duration: 0.2, animations: {
//// self.loadingBlurView.animator.fractionComplete = 0
//// self.loadingBlurView.effect = nil
@ -361,6 +367,7 @@ final class _MediaStreamVideoComponent: Component {
var timeLastFrameReceived: CFAbsoluteTime?
var isFullscreen: Bool = false
let videoLoadingThrottler = Throttler<Bool>(duration: 1, queue: .main)
func update(component: _MediaStreamVideoComponent, availableSize: CGSize, state: State, transition: Transition) -> CGSize {
self.state = state
@ -381,7 +388,7 @@ final class _MediaStreamVideoComponent: Component {
})
}
if component.videoLoading || self.videoStalled {
if !component.hasVideo || component.videoLoading || self.videoStalled {
updateVideoStalled(isStalled: true)
} else {
updateVideoStalled(isStalled: false)
@ -396,10 +403,13 @@ final class _MediaStreamVideoComponent: Component {
let currentTime = CFAbsoluteTimeGetCurrent()
if let lastFrameTime = strongSelf.timeLastFrameReceived,
currentTime - lastFrameTime > 0.5 {
DispatchQueue.main.async {
strongSelf.videoStalled = true
strongSelf.onVideoPlaybackChange(false)
// DispatchQueue.main.async {
strongSelf.videoLoadingThrottler.publish(true, includingLatest: true) { isStalled in
strongSelf.videoStalled = isStalled
strongSelf.onVideoPlaybackChange(!isStalled)
}
// }
}
} }
// TODO: use mapToThrottled (?)
@ -409,7 +419,7 @@ final class _MediaStreamVideoComponent: Component {
// strongSelf.stallTimer?.invalidate()
// TODO: optimize with throttle
strongSelf.timeLastFrameReceived = CFAbsoluteTimeGetCurrent()
DispatchQueue.main.async {
// DispatchQueue.main.async {
// strongSelf.stallTimer = _stallTimer
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
// print(strongSelf.videoStalled)
@ -417,9 +427,13 @@ final class _MediaStreamVideoComponent: Component {
// strongSelf.stallTimer?.fire()
// }
// RunLoop.main.add(strongSelf.stallTimer!, forMode: .common)
strongSelf.videoStalled = false
strongSelf.onVideoPlaybackChange(true)
strongSelf.videoLoadingThrottler.publish(false, includingLatest: true) { isStalled in
strongSelf.videoStalled = isStalled
strongSelf.onVideoPlaybackChange(!isStalled)
}
// strongSelf.videoStalled = false
// strongSelf.onVideoPlaybackChange(true)
// }
})
stallTimer = _stallTimer
// RunLoop.main.add(stallTimer!, forMode: .common)
@ -743,22 +757,24 @@ final class _MediaStreamVideoComponent: Component {
func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
// Fading to make
let presentation = self.videoView!.snapshotView(afterScreenUpdates: false)!
if let presentation = self.videoView!.snapshotView(afterScreenUpdates: false) {
self.addSubview(presentation)
presentation.frame = self.videoView!.frame
lastFrame[self.component!.call.peerId.id.description] = presentation
// let image = UIGraphicsImageRenderer(size: presentation.bounds.size).image { context in
// presentation.render(in: context.cgContext)
// }
// print(image)
// let image = UIGraphicsImageRenderer(size: presentation.bounds.size).image { context in
// presentation.render(in: context.cgContext)
// }
// print(image)
self.videoView?.alpha = 0
// self.videoView?.alpha = 0.5
// presentation.animateAlpha(from: 1, to: 0, duration: 0.1, completion: { _ in presentation.removeFromSuperlayer() })
// self.videoView?.alpha = 0.5
// presentation.animateAlpha(from: 1, to: 0, duration: 0.1, completion: { _ in presentation.removeFromSuperlayer() })
UIView.animate(withDuration: 0.1, animations: {
presentation.alpha = 0
}, completion: { _ in
presentation.removeFromSuperview()
})
}
// DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
// presentation.removeFromSuperlayer()
// }