Tweaking offsets and landscape mode

This commit is contained in:
Ilya Yelagov
2022-12-07 22:34:55 +04:00
parent c28d2bc888
commit bfa7de8e27
4 changed files with 98 additions and 106 deletions

View File

@@ -50,7 +50,7 @@ public final class AnimatedCountView: UIView {
self.foregroundGradientLayer.frame = CGRect(origin: .zero, size: bounds.size).insetBy(dx: -60, dy: -60)
self.maskingView.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 - 8 : 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 - 10, width: subtitleLabel.intrinsicContentSize.width + 20, height: 20)
}
func update(countString: String, subtitle: String) {

View File

@@ -969,7 +969,7 @@ public final class _MediaStreamComponent: CombinedComponent {
let moreAnimationTag = GenericComponentViewTag()
return { context in
var forceFullScreenInLandscape: Bool { false }
var forceFullScreenInLandscape: Bool { true }
let environment = context.environment[ViewControllerComponentContainer.Environment.self].value
if environment.isVisible {
} else {
@@ -1000,24 +1000,36 @@ public final class _MediaStreamComponent: CombinedComponent {
state.updated(transition: .easeInOut(duration: 3))
deactivatePictureInPicture.invoke(Void())
}
var isFullscreen = state.isFullscreen
let isFullscreen: Bool // = state.isFullscreen
let isLandscape = context.availableSize.width > context.availableSize.height
// if let videoSize = context.state.videoSize {
// Always fullscreen in landscape
// TODO: support landscape sheet (wrap in scrollview, video size same as portrait)
if forceFullScreenInLandscape && /*videoSize.width > videoSize.height &&*/ isLandscape && !isFullscreen {
state.isFullscreen = true
isFullscreen = true
} else if let videoSize = context.state.videoSize, videoSize.width > videoSize.height && !isLandscape && isFullscreen {
state.isFullscreen = false
isFullscreen = false
}
// }
if forceFullScreenInLandscape && /*videoSize.width > videoSize.height &&*/ isLandscape && !state.isFullscreen {
state.isFullscreen = true
isFullscreen = true
} else if let videoSize = context.state.videoSize, videoSize.width > videoSize.height && !isLandscape && state.isFullscreen {
state.isFullscreen = false
isFullscreen = false
} else {
isFullscreen = state.isFullscreen
}
// }
let videoInset: CGFloat
if !isFullscreen {
videoInset = 16
} else {
videoInset = 0
}
let videoHeight: CGFloat = context.availableSize.width / 16 * 9
let videoHeight: CGFloat = forceFullScreenInLandscape
? (context.availableSize.width - videoInset * 2) / 16 * 9
: context.state.videoSize?.height ?? (context.availableSize.width - videoInset * 2) / 16 * 9
let bottomPadding = 40 + environment.safeInsets.bottom
let sheetHeight: CGFloat = isFullscreen ? context.availableSize.height : (44 + videoHeight + 40 + 69 + 16 + 32 + 70 + bottomPadding)
let sheetHeight: CGFloat = isFullscreen
? context.availableSize.height
: (44 + videoHeight + 40 + 69 + 16 + 32 + 70 + bottomPadding)
let isFullyDragged = context.availableSize.height - sheetHeight + state.dismissOffset - context.view.safeAreaInsets.top < 30
var dragOffset = context.state.dismissOffset
@@ -1054,6 +1066,10 @@ public final class _MediaStreamComponent: CombinedComponent {
},
onVideoSizeRetrieved: { [weak state] size in
state?.videoSize = size
},
onVideoPlaybackLiveChange: { [weak state] isLive in
state?.videoStalled = !isLive
state?.updated()
}
),
availableSize: context.availableSize,
@@ -1538,7 +1554,8 @@ public final class _MediaStreamComponent: CombinedComponent {
participantsCount: context.state.originInfo?.memberCount ?? 0, // Int.random(in: 0...999998)// [0, 5, 15, 16, 95, 100, 16042, 942539].randomElement()!
//
isFullyExtended: isFullyDragged,
deviceCornerRadius: deviceCornerRadius ?? 0
deviceCornerRadius: deviceCornerRadius ?? 0,
videoHeight: videoHeight
),
availableSize: context.availableSize,
transition: context.transition
@@ -1559,7 +1576,7 @@ public final class _MediaStreamComponent: CombinedComponent {
if isFullscreen {
videoPos = context.availableSize.height / 2 + dragOffset
} else {
videoPos = sheetPosition - sheetHeight / 2 + videoHeight / 2 + 50
videoPos = sheetPosition - sheetHeight / 2 + videoHeight / 2 + 50 + 12
}
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))
@@ -1623,7 +1640,8 @@ public final class _MediaStreamComponent: CombinedComponent {
bottomPadding: 12,
participantsCount: -1, // context.state.originInfo?.memberCount ?? 0
isFullyExtended: isFullyDragged,
deviceCornerRadius: deviceCornerRadius ?? 0
deviceCornerRadius: deviceCornerRadius ?? 0,
videoHeight: videoHeight
),
availableSize: context.availableSize,
transition: context.transition

View File

@@ -80,6 +80,7 @@ final class _MediaStreamVideoComponent: Component {
let onVideoSizeRetrieved: (CGSize) -> Void
let videoLoading: Bool
let callPeer: Peer?
let onVideoPlaybackLiveChange: (Bool) -> Void
init(
call: PresentationGroupCallImpl,
@@ -95,7 +96,8 @@ final class _MediaStreamVideoComponent: Component {
deactivatePictureInPicture: ActionSlot<Void>,
bringBackControllerForPictureInPictureDeactivation: @escaping (@escaping () -> Void) -> Void,
pictureInPictureClosed: @escaping () -> Void,
onVideoSizeRetrieved: @escaping (CGSize) -> Void
onVideoSizeRetrieved: @escaping (CGSize) -> Void,
onVideoPlaybackLiveChange: @escaping (Bool) -> Void
) {
self.call = call
self.hasVideo = hasVideo
@@ -107,6 +109,7 @@ final class _MediaStreamVideoComponent: Component {
self.deactivatePictureInPicture = deactivatePictureInPicture
self.bringBackControllerForPictureInPictureDeactivation = bringBackControllerForPictureInPictureDeactivation
self.pictureInPictureClosed = pictureInPictureClosed
self.onVideoPlaybackLiveChange = onVideoPlaybackLiveChange
self.callPeer = callPeer
self.peerImage = peerImage
@@ -220,6 +223,8 @@ final class _MediaStreamVideoComponent: Component {
}
}
}
var onVideoPlaybackChange: ((Bool) -> Void) = { _ in }
private var frameInputDisposable: Disposable?
private func updateVideoStalled(isStalled: Bool) {
@@ -268,15 +273,15 @@ final class _MediaStreamVideoComponent: Component {
shimmerOverlayView.compositingFilter = "softLightBlendMode"
shimmer.testUpdate(background: .clear, foreground: .white.withAlphaComponent(0.4))
}
loadingBlurView.layer.cornerRadius = 10
// loadingBlurView.layer.cornerRadius = 10
shimmerOverlayLayer.opacity = 0.6
shimmerBorderLayer.cornerRadius = 10
let cornerRadius = loadingBlurView.layer.cornerRadius
shimmerBorderLayer.cornerRadius = cornerRadius // TODO: check isFullScreeen
shimmerBorderLayer.masksToBounds = true
shimmerBorderLayer.compositingFilter = "softLightBlendMode"
shimmerBorderLayer.frame = loadingBlurView.bounds
let borderMask = CAShapeLayer()
borderMask.path = CGPath(roundedRect: .init(x: 0, y: 0, width: shimmerBorderLayer.bounds.width, height: shimmerBorderLayer.bounds.height), cornerWidth: 10, cornerHeight: 10, transform: nil)
borderMask.path = CGPath(roundedRect: .init(x: 0, y: 0, width: shimmerBorderLayer.bounds.width, height: shimmerBorderLayer.bounds.height), cornerWidth: cornerRadius, cornerHeight: cornerRadius, transform: nil)
borderMask.fillColor = UIColor.white.withAlphaComponent(0.4).cgColor
borderMask.strokeColor = UIColor.white.withAlphaComponent(0.8).cgColor
borderMask.lineWidth = 2
@@ -353,12 +358,17 @@ final class _MediaStreamVideoComponent: Component {
var avatarDisposable: Disposable?
var didBeginLoadingAvatar = false
// let avatarPlaceholderView = UIImageView()
var timeLastFrameReceived: CFAbsoluteTime?
var isFullscreen: Bool = false
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
self.onVideoPlaybackChange = component.onVideoPlaybackLiveChange
self.isFullscreen = component.isFullscreen
if let peer = component.callPeer, !didBeginLoadingAvatar {
didBeginLoadingAvatar = true
@@ -373,96 +383,46 @@ final class _MediaStreamVideoComponent: 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 {
// Accounting for delay in first frame received
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [self] in
UIView.transition(with: self.loadingBlurView, duration: 0.2, animations: {
self.loadingBlurView.alpha = 0
}, completion: { _ in
self.loadingBlurView.removeFromSuperview()
})
placeholderView.removeFromSuperview()
}
}*/
}
if component.hasVideo, self.videoView == nil {
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)")
// print("Timer emitting \(timer)")
let currentTime = CFAbsoluteTimeGetCurrent()
if let lastFrameTime = strongSelf.timeLastFrameReceived,
currentTime - lastFrameTime > 0.5 {
DispatchQueue.main.async {
strongSelf.videoStalled = true
strongSelf.onVideoPlaybackChange(false)
}
}
}
} }
// TODO: use mapToThrottled (?)
frameInputDisposable = input.start(next: { [weak self] input in
guard let strongSelf = self else { return }
print("input")
strongSelf.stallTimer?.invalidate()
// print("input")
// strongSelf.stallTimer?.invalidate()
// TODO: optimize with throttle
strongSelf.timeLastFrameReceived = CFAbsoluteTimeGetCurrent()
DispatchQueue.main.async {
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.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
strongSelf.onVideoPlaybackChange(true)
}
})
stallTimer = _stallTimer
// RunLoop.main.add(stallTimer!, forMode: .common)
// RunLoop.main.add(stallTimer!, forMode: .common)
if let videoBlurView = self.videoRenderingContext.makeView(input: input, blur: true) {
self.videoBlurView = videoBlurView
@@ -487,7 +447,7 @@ final class _MediaStreamVideoComponent: Component {
}
if let sampleBufferVideoView = videoView as? SampleBufferVideoRenderingView {
sampleBufferVideoView.sampleBufferLayer.masksToBounds = true
sampleBufferVideoView.sampleBufferLayer.cornerRadius = 20
// sampleBufferVideoView.sampleBufferLayer.cornerRadius = 10
if #available(iOS 13.0, *) {
sampleBufferVideoView.sampleBufferLayer.preventsDisplaySleepDuringVideoPlayback = true
@@ -532,7 +492,7 @@ final class _MediaStreamVideoComponent: Component {
return delegate
}()))
pictureInPictureController?.playerLayer.masksToBounds = false
pictureInPictureController?.playerLayer.cornerRadius = 30
pictureInPictureController?.playerLayer.cornerRadius = 10
} else if AVPictureInPictureController.isPictureInPictureSupported() {
// TODO: support PiP for iOS < 15.0
// sampleBufferVideoView.sampleBufferLayer
@@ -577,9 +537,11 @@ final class _MediaStreamVideoComponent: Component {
fullScreenBackgroundPlaceholder.removeFromSuperview()
} else if component.isFullscreen {
if fullScreenBackgroundPlaceholder.superview == nil {
// insertSubview(fullScreenBackgroundPlaceholder, at: 0)
insertSubview(fullScreenBackgroundPlaceholder, at: 0)
}
fullScreenBackgroundPlaceholder.frame = self.bounds
} else {
fullScreenBackgroundPlaceholder.removeFromSuperview()
}
// sheetView.frame = .init(x: 0, y: sheetTop, width: availableSize.width, height: sheetHeight)
@@ -595,6 +557,8 @@ final class _MediaStreamVideoComponent: Component {
videoInset = 0
}
let videoSize: CGSize
let videoCornerRadius: CGFloat = component.isFullscreen ? 0 : 10
if let videoView = self.videoView {
// TODO: REMOVE FROM HERE and move to call end (or at least to background)
// if let presentation = videoView.snapshotView(afterScreenUpdates: false) {
@@ -608,13 +572,17 @@ final class _MediaStreamVideoComponent: Component {
// saveAspect(aspect)
if component.isFullscreen {
if aspect <= 0.01 {
aspect = 3.0 / 4.0
aspect = 16.0 / 9 // 3.0 / 4.0
}
} else {
aspect = 16.0 / 9
}
let videoSize = CGSize(width: aspect * 100.0, height: 100.0).aspectFitted(.init(width: availableSize.width - videoInset * 2, height: availableSize.height))
if component.isFullscreen {
videoSize = CGSize(width: aspect * 100.0, height: 100.0).aspectFitted(.init(width: availableSize.width - videoInset * 2, height: availableSize.height))
} else {
videoSize = CGSize(width: aspect * 100.0, height: 100.0).aspectFitted(.init(width: min(availableSize.width, availableSize.height) - videoInset * 2, height: max(availableSize.height, availableSize.width)))
}
let blurredVideoSize = videoSize.aspectFilled(availableSize)
component.onVideoSizeRetrieved(videoSize)
@@ -635,7 +603,7 @@ final class _MediaStreamVideoComponent: Component {
videoView.updateIsEnabled(isVideoVisible)
videoView.clipsToBounds = true
videoView.layer.cornerRadius = component.isFullscreen ? 0 : 10
videoView.layer.cornerRadius = videoCornerRadius
// var aspect = videoView.getAspect()
// if aspect <= 0.01 {
// TODO: remove debug
@@ -664,21 +632,21 @@ final class _MediaStreamVideoComponent: Component {
self.maskGradientLayer.frame = videoBlurView.bounds
}
} else {
videoSize = CGSize(width: 16 / 9 * 100.0, height: 100.0).aspectFitted(.init(width: availableSize.width - videoInset * 2, height: availableSize.height))
}
let 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.layer.cornerRadius = 10
loadingBlurView.layer.cornerRadius = videoCornerRadius
placeholderView.frame = loadingBlurView.frame
placeholderView.layer.cornerRadius = 10
placeholderView.layer.cornerRadius = videoCornerRadius
placeholderView.clipsToBounds = true
// avatarPlaceholderView.frame = placeholderView.bounds
shimmerOverlayLayer.frame = loadingBlurView.bounds
shimmerBorderLayer.frame = loadingBlurView.bounds
// shimmerBorderLayer.mask?.frame = loadingBlurView.bounds
if component.isFullscreen {
loadingBlurView.removeFromSuperview()
// loadingBlurView.removeFromSuperview()
}
if !self.hadVideo {
// TODO: hide fullscreen button without video

View File

@@ -21,6 +21,7 @@ final class StreamSheetComponent: CombinedComponent {
let bottomPadding: CGFloat
let isFullyExtended: Bool
let deviceCornerRadius: CGFloat
let videoHeight: CGFloat
init(
// color: UIColor,
@@ -32,7 +33,8 @@ final class StreamSheetComponent: CombinedComponent {
bottomPadding: CGFloat,
participantsCount: Int,
isFullyExtended: Bool,
deviceCornerRadius: CGFloat
deviceCornerRadius: CGFloat,
videoHeight: CGFloat
) {
// self.leftItem = leftItem
self.topComponent = topComponent
@@ -45,6 +47,7 @@ final class StreamSheetComponent: CombinedComponent {
self.participantsCount = participantsCount
self.isFullyExtended = isFullyExtended
self.deviceCornerRadius = deviceCornerRadius
self.videoHeight = videoHeight
}
static func ==(lhs: StreamSheetComponent, rhs: StreamSheetComponent) -> Bool {
@@ -75,6 +78,9 @@ final class StreamSheetComponent: CombinedComponent {
if lhs.isFullyExtended != rhs.isFullyExtended {
return false
}
if lhs.videoHeight != rhs.videoHeight {
return false
}
return true
}
//
@@ -95,7 +101,7 @@ final class StreamSheetComponent: CombinedComponent {
override func draw(_ rect: CGRect) {
super.draw(rect)
// Debug interactive area
// guard let context = UIGraphicsGetCurrentContext() else { return }
// context.setFillColor(UIColor.red.cgColor)
// overlayComponentsFrames.forEach { frame in
@@ -193,7 +199,7 @@ final class StreamSheetComponent: CombinedComponent {
)
(context.view as? StreamSheetComponent.View)?.overlayComponentsFrames.append(.init(x: 0, y: topOffset, width: topItem.size.width, height: topItem.size.height))
}
let videoHeight = (availableWidth - 32) / 16 * 9
let videoHeight = context.component.videoHeight // ?? (min(availableWidth, context.availableSize.height) - 32) / 16 * 9
let sheetHeight = context.component.sheetHeight
let animatedParticipantsVisible = context.component.participantsCount != -1
if true {