mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 22:25:57 +00:00
Video call improvements
This commit is contained in:
@@ -42,18 +42,21 @@ private final class CallVideoNode: ASDisplayNode {
|
||||
private let isFlippedUpdated: (CallVideoNode) -> Void
|
||||
|
||||
private(set) var currentOrientation: PresentationCallVideoView.Orientation
|
||||
private(set) var currentAspect: CGFloat = 0.0
|
||||
|
||||
private var previousVideoHeight: CGFloat?
|
||||
|
||||
init(videoView: PresentationCallVideoView, disabledText: String?, assumeReadyAfterTimeout: Bool, isReadyUpdated: @escaping () -> Void, orientationUpdated: @escaping () -> Void, isFlippedUpdated: @escaping (CallVideoNode) -> Void) {
|
||||
self.isReadyUpdated = isReadyUpdated
|
||||
self.isFlippedUpdated = isFlippedUpdated
|
||||
|
||||
self.videoTransformContainer = ASDisplayNode()
|
||||
self.videoTransformContainer.clipsToBounds = true
|
||||
self.videoView = videoView
|
||||
videoView.view.clipsToBounds = true
|
||||
videoView.view.backgroundColor = .black
|
||||
|
||||
self.currentOrientation = videoView.getOrientation()
|
||||
self.currentAspect = videoView.getAspect()
|
||||
|
||||
self.videoPausedNode = ImmediateTextNode()
|
||||
self.videoPausedNode.alpha = 0.0
|
||||
@@ -89,13 +92,14 @@ private final class CallVideoNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
self.videoView.setOnOrientationUpdated { [weak self] orientation in
|
||||
self.videoView.setOnOrientationUpdated { [weak self] orientation, aspect in
|
||||
Queue.mainQueue().async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
if strongSelf.currentOrientation != orientation {
|
||||
if strongSelf.currentOrientation != orientation || strongSelf.currentAspect != aspect {
|
||||
strongSelf.currentOrientation = orientation
|
||||
strongSelf.currentAspect = aspect
|
||||
orientationUpdated()
|
||||
}
|
||||
}
|
||||
@@ -164,85 +168,91 @@ private final class CallVideoNode: ASDisplayNode {
|
||||
})
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, cornerRadius: CGFloat, deviceOrientation: UIDeviceOrientation, transition: ContainedViewLayoutTransition) {
|
||||
func updateLayout(size: CGSize, cornerRadius: CGFloat, isOutgoing: Bool, deviceOrientation: UIDeviceOrientation, isCompactLayout: Bool, transition: ContainedViewLayoutTransition) {
|
||||
self.currentCornerRadius = cornerRadius
|
||||
|
||||
var rotationAngle: CGFloat
|
||||
switch self.currentOrientation {
|
||||
case .rotation0:
|
||||
rotationAngle = 0.0
|
||||
case .rotation90:
|
||||
rotationAngle = -CGFloat.pi / 2.0
|
||||
case .rotation180:
|
||||
rotationAngle = -CGFloat.pi
|
||||
case .rotation270:
|
||||
if isOutgoing {
|
||||
rotationAngle = CGFloat.pi / 2.0
|
||||
}
|
||||
|
||||
var additionalAngle: CGFloat = 0.0
|
||||
switch deviceOrientation {
|
||||
case .portrait:
|
||||
additionalAngle = 0.0
|
||||
case .landscapeLeft:
|
||||
additionalAngle = CGFloat.pi / 2.0
|
||||
case .landscapeRight:
|
||||
additionalAngle = -CGFloat.pi / 2.0
|
||||
case .portraitUpsideDown:
|
||||
rotationAngle = -CGFloat.pi
|
||||
default:
|
||||
additionalAngle = 0.0
|
||||
}
|
||||
rotationAngle += additionalAngle
|
||||
if abs(rotationAngle - (-CGFloat.pi)) < 1.0 {
|
||||
rotationAngle = -CGFloat.pi + 0.001
|
||||
}
|
||||
|
||||
var rotateFrame = abs(rotationAngle.remainder(dividingBy: CGFloat.pi)) > 1.0
|
||||
|
||||
var originalRotateFrame = rotateFrame
|
||||
if size.width > size.height {
|
||||
rotateFrame = !rotateFrame
|
||||
if rotateFrame {
|
||||
originalRotateFrame = true
|
||||
}
|
||||
} else {
|
||||
if rotateFrame {
|
||||
originalRotateFrame = false
|
||||
switch self.currentOrientation {
|
||||
case .rotation0:
|
||||
rotationAngle = 0.0
|
||||
case .rotation90:
|
||||
rotationAngle = CGFloat.pi / 2.0
|
||||
case .rotation180:
|
||||
rotationAngle = CGFloat.pi
|
||||
case .rotation270:
|
||||
rotationAngle = -CGFloat.pi / 2.0
|
||||
}
|
||||
|
||||
var additionalAngle: CGFloat = 0.0
|
||||
switch deviceOrientation {
|
||||
case .portrait:
|
||||
additionalAngle = 0.0
|
||||
case .landscapeLeft:
|
||||
additionalAngle = CGFloat.pi / 2.0
|
||||
case .landscapeRight:
|
||||
additionalAngle = -CGFloat.pi / 2.0
|
||||
case .portraitUpsideDown:
|
||||
rotationAngle = CGFloat.pi
|
||||
default:
|
||||
additionalAngle = 0.0
|
||||
}
|
||||
rotationAngle += additionalAngle
|
||||
if abs(rotationAngle - (-CGFloat.pi)) < 1.0 {
|
||||
rotationAngle = -CGFloat.pi + 0.001
|
||||
}
|
||||
}
|
||||
let videoFrame: CGRect
|
||||
let scale: CGFloat
|
||||
|
||||
let rotateFrame = abs(rotationAngle.remainder(dividingBy: CGFloat.pi)) > 1.0
|
||||
let fittingSize: CGSize
|
||||
if rotateFrame {
|
||||
let frameSize = CGSize(width: size.height, height: size.width).aspectFitted(size)
|
||||
videoFrame = CGRect(origin: CGPoint(x: floor((size.width - frameSize.width) / 2.0), y: floor((size.height - frameSize.height) / 2.0)), size: frameSize)
|
||||
if size.width > size.height {
|
||||
scale = frameSize.height / size.width
|
||||
} else {
|
||||
scale = frameSize.width / size.height
|
||||
}
|
||||
fittingSize = CGSize(width: size.height, height: size.width)
|
||||
} else {
|
||||
videoFrame = CGRect(origin: CGPoint(), size: size)
|
||||
if size.width > size.height {
|
||||
scale = 1.0
|
||||
fittingSize = size
|
||||
}
|
||||
|
||||
let unboundVideoSize = CGSize(width: self.currentAspect * 10000.0, height: 10000.0)
|
||||
|
||||
var fittedVideoSize = unboundVideoSize.fitted(fittingSize)
|
||||
if fittedVideoSize.width < fittingSize.width || fittedVideoSize.height < fittingSize.height {
|
||||
let isVideoPortrait = unboundVideoSize.width < unboundVideoSize.height
|
||||
let isFittingSizePortrait = fittingSize.width < fittingSize.height
|
||||
|
||||
if isCompactLayout && isVideoPortrait == isFittingSizePortrait {
|
||||
fittedVideoSize = unboundVideoSize.aspectFilled(fittingSize)
|
||||
} else {
|
||||
scale = 1.0
|
||||
let maxFittingEdgeDistance: CGFloat
|
||||
if isCompactLayout {
|
||||
maxFittingEdgeDistance = 200.0
|
||||
} else {
|
||||
maxFittingEdgeDistance = 400.0
|
||||
}
|
||||
if fittedVideoSize.width > fittingSize.width - maxFittingEdgeDistance && fittedVideoSize.height > fittingSize.height - maxFittingEdgeDistance {
|
||||
fittedVideoSize = unboundVideoSize.aspectFilled(fittingSize)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let rotatedVideoHeight: CGFloat = max(fittedVideoSize.height, fittedVideoSize.width)
|
||||
|
||||
let videoFrame: CGRect = CGRect(origin: CGPoint(), size: fittedVideoSize)
|
||||
|
||||
let videoPausedSize = self.videoPausedNode.updateLayout(CGSize(width: size.width - 16.0, height: 100.0))
|
||||
transition.updateFrame(node: self.videoPausedNode, frame: CGRect(origin: CGPoint(x: floor((size.width - videoPausedSize.width) / 2.0), y: floor((size.height - videoPausedSize.height) / 2.0)), size: videoPausedSize))
|
||||
|
||||
let previousVideoFrame = self.videoTransformContainer.frame
|
||||
self.videoTransformContainer.bounds = CGRect(origin: CGPoint(), size: size)
|
||||
if transition.isAnimated && !videoFrame.height.isZero && !previousVideoFrame.height.isZero {
|
||||
transition.animateTransformScale(node: self.videoTransformContainer, from: previousVideoFrame.height / size.height, additive: true)
|
||||
self.videoTransformContainer.bounds = CGRect(origin: CGPoint(), size: videoFrame.size)
|
||||
if transition.isAnimated && !videoFrame.height.isZero, let previousVideoHeight = self.previousVideoHeight, !previousVideoHeight.isZero {
|
||||
let scaleDifference = previousVideoHeight / rotatedVideoHeight
|
||||
if abs(scaleDifference - 1.0) > 0.001 {
|
||||
transition.animateTransformScale(node: self.videoTransformContainer, from: scaleDifference, additive: true)
|
||||
}
|
||||
}
|
||||
transition.updatePosition(node: self.videoTransformContainer, position: videoFrame.center)
|
||||
transition.updateSublayerTransformScale(node: self.videoTransformContainer, scale: scale)
|
||||
|
||||
let localVideoSize = originalRotateFrame ? CGSize(width: size.height, height: size.width) : size
|
||||
let localVideoFrame = CGRect(origin: CGPoint(x: floor((size.width - localVideoSize.width) / 2.0), y: floor((size.height - localVideoSize.height) / 2.0)), size: localVideoSize)
|
||||
self.previousVideoHeight = rotatedVideoHeight
|
||||
transition.updatePosition(node: self.videoTransformContainer, position: CGPoint(x: size.width / 2.0, y: size.height / 2.0))
|
||||
|
||||
let localVideoFrame = CGRect(origin: CGPoint(), size: videoFrame.size)
|
||||
self.videoView.view.bounds = localVideoFrame
|
||||
self.videoView.view.center = localVideoFrame.center
|
||||
transition.updateTransformRotation(view: self.videoView.view, angle: rotationAngle)
|
||||
@@ -392,7 +402,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
|
||||
var acceptCall: (() -> Void)?
|
||||
var endCall: (() -> Void)?
|
||||
var back: (() -> Void)?
|
||||
var presentCallRating: ((CallId) -> Void)?
|
||||
var presentCallRating: ((CallId, Bool) -> Void)?
|
||||
var callEnded: ((Bool) -> Void)?
|
||||
var dismissedInteractively: (() -> Void)?
|
||||
var present: ((ViewController) -> Void)?
|
||||
@@ -419,6 +429,8 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
|
||||
private var deviceOrientation: UIDeviceOrientation = .portrait
|
||||
private var orientationDidChangeObserver: NSObjectProtocol?
|
||||
|
||||
private var currentRequestedAspect: CGFloat?
|
||||
|
||||
init(sharedContext: SharedAccountContext, account: Account, presentationData: PresentationData, statusBar: StatusBar, debugInfo: Signal<(String, String), NoError>, shouldStayHiddenUntilConnection: Bool = false, easyDebugAccess: Bool, call: PresentationCall) {
|
||||
self.sharedContext = sharedContext
|
||||
self.account = account
|
||||
@@ -922,11 +934,18 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
|
||||
case let .error(error):
|
||||
let text = self.presentationData.strings.Call_StatusFailed
|
||||
switch error {
|
||||
case .notSupportedByPeer:
|
||||
case let .notSupportedByPeer(isVideo):
|
||||
if !self.displayedVersionOutdatedAlert, let peer = self.peer {
|
||||
self.displayedVersionOutdatedAlert = true
|
||||
|
||||
self.present?(textAlertController(sharedContext: self.sharedContext, title: nil, text: self.presentationData.strings.Call_ParticipantVersionOutdatedError(peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)).0, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {
|
||||
let text: String
|
||||
if isVideo {
|
||||
text = self.presentationData.strings.Call_ParticipantVideoVersionOutdatedError(peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)).0
|
||||
} else {
|
||||
text = self.presentationData.strings.Call_ParticipantVersionOutdatedError(peer.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)).0
|
||||
}
|
||||
|
||||
self.present?(textAlertController(sharedContext: self.sharedContext, title: nil, text: text, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {
|
||||
})]))
|
||||
}
|
||||
default:
|
||||
@@ -1019,7 +1038,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
|
||||
if case let .terminated(id, _, reportRating) = callState.state, let callId = id {
|
||||
let presentRating = reportRating || self.forceReportRating
|
||||
if presentRating {
|
||||
self.presentCallRating?(callId)
|
||||
self.presentCallRating?(callId, self.call.isVideo)
|
||||
}
|
||||
self.callEnded?(presentRating)
|
||||
}
|
||||
@@ -1253,12 +1272,12 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
|
||||
let previewVideoSide = interpolate(from: 350.0, to: 200.0, value: 1.0 - self.pictureInPictureTransitionFraction)
|
||||
var previewVideoSize = layout.size.aspectFitted(CGSize(width: previewVideoSide, height: previewVideoSide))
|
||||
previewVideoSize = CGSize(width: 30.0, height: 45.0).aspectFitted(previewVideoSize)
|
||||
if let minimizedVideoNode = minimizedVideoNode {
|
||||
if let minimizedVideoNode = self.minimizedVideoNode {
|
||||
switch minimizedVideoNode.currentOrientation {
|
||||
case .rotation90, .rotation270:
|
||||
previewVideoSize = CGSize(width: previewVideoSize.height, height: previewVideoSize.width)
|
||||
default:
|
||||
break
|
||||
default:
|
||||
previewVideoSize = CGSize(width: previewVideoSize.height, height: previewVideoSize.width)
|
||||
}
|
||||
}
|
||||
let previewVideoY: CGFloat
|
||||
@@ -1305,6 +1324,13 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = (layout, navigationBarHeight)
|
||||
|
||||
var mappedDeviceOrientation = self.deviceOrientation
|
||||
var isCompactLayout = true
|
||||
if case .regular = layout.metrics.widthClass, case .regular = layout.metrics.heightClass {
|
||||
mappedDeviceOrientation = .portrait
|
||||
isCompactLayout = false
|
||||
}
|
||||
|
||||
var isUIHidden = self.isUIHidden
|
||||
switch self.callState?.state {
|
||||
case .terminated, .terminating:
|
||||
@@ -1445,7 +1471,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
|
||||
expandedVideoTransition.updateFrame(node: expandedVideoNode, frame: fullscreenVideoFrame)
|
||||
}
|
||||
|
||||
expandedVideoNode.updateLayout(size: expandedVideoNode.frame.size, cornerRadius: 0.0, deviceOrientation: self.deviceOrientation, transition: expandedVideoTransition)
|
||||
expandedVideoNode.updateLayout(size: expandedVideoNode.frame.size, cornerRadius: 0.0, isOutgoing: expandedVideoNode === self.outgoingVideoNodeValue, deviceOrientation: mappedDeviceOrientation, isCompactLayout: isCompactLayout, transition: expandedVideoTransition)
|
||||
|
||||
if self.animateRequestedVideoOnce {
|
||||
self.animateRequestedVideoOnce = false
|
||||
@@ -1495,7 +1521,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
|
||||
self.animationForExpandedVideoSnapshotView = nil
|
||||
}
|
||||
minimizedVideoTransition.updateFrame(node: minimizedVideoNode, frame: previewVideoFrame)
|
||||
minimizedVideoNode.updateLayout(size: previewVideoFrame.size, cornerRadius: interpolate(from: 14.0, to: 24.0, value: self.pictureInPictureTransitionFraction), deviceOrientation: .portrait, transition: minimizedVideoTransition)
|
||||
minimizedVideoNode.updateLayout(size: previewVideoFrame.size, cornerRadius: interpolate(from: 14.0, to: 24.0, value: self.pictureInPictureTransitionFraction), isOutgoing: minimizedVideoNode === self.outgoingVideoNodeValue, deviceOrientation: .portrait, isCompactLayout: false, transition: minimizedVideoTransition)
|
||||
if transition.isAnimated && didAppear {
|
||||
minimizedVideoNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5)
|
||||
}
|
||||
@@ -1511,6 +1537,43 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
|
||||
if let debugNode = self.debugNode {
|
||||
transition.updateFrame(node: debugNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
}
|
||||
|
||||
let requestedAspect: CGFloat
|
||||
if case .compact = layout.metrics.widthClass, case .compact = layout.metrics.heightClass {
|
||||
var isIncomingVideoRotated = false
|
||||
var rotationCount = 0
|
||||
|
||||
switch mappedDeviceOrientation {
|
||||
case .portrait:
|
||||
break
|
||||
case .landscapeLeft:
|
||||
rotationCount += 1
|
||||
case .landscapeRight:
|
||||
rotationCount += 1
|
||||
case .portraitUpsideDown:
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if rotationCount % 2 != 0 {
|
||||
isIncomingVideoRotated = true
|
||||
}
|
||||
|
||||
if !isIncomingVideoRotated {
|
||||
requestedAspect = layout.size.width / layout.size.height
|
||||
} else {
|
||||
requestedAspect = 0.0
|
||||
}
|
||||
} else {
|
||||
requestedAspect = 0.0
|
||||
}
|
||||
if self.currentRequestedAspect != requestedAspect {
|
||||
self.currentRequestedAspect = requestedAspect
|
||||
if !self.sharedContext.immediateExperimentalUISettings.disableVideoAspectScaling {
|
||||
self.call.setRequestedVideoAspect(Float(requestedAspect))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func keyPressed() {
|
||||
|
||||
Reference in New Issue
Block a user