Video call improvements

This commit is contained in:
Ali 2020-08-21 21:11:29 +01:00
parent bb974d4cc1
commit bead41710b
35 changed files with 2734 additions and 2545 deletions

View File

@ -2446,6 +2446,7 @@ Unused sets are archived when you add more.";
"Call.StatusBar" = "Touch to return to call %@"; "Call.StatusBar" = "Touch to return to call %@";
"Call.ParticipantVersionOutdatedError" = "%@'s app does not support calls. They need to update their app before you can call them."; "Call.ParticipantVersionOutdatedError" = "%@'s app does not support calls. They need to update their app before you can call them.";
"Call.ParticipantVideoVersionOutdatedError" = "%@'s app does not support video calls. They need to update their app before you can call them.";
"Privacy.Calls" = "Voice Calls"; "Privacy.Calls" = "Voice Calls";
@ -2755,6 +2756,7 @@ Unused sets are archived when you add more.";
"Channel.EditAdmin.CannotEdit" = "You cannot edit the rights of this admin."; "Channel.EditAdmin.CannotEdit" = "You cannot edit the rights of this admin.";
"Call.RateCall" = "Rate This Call"; "Call.RateCall" = "Rate This Call";
"Call.ShareStats" = "Share Statistics";
"Settings.ApplyProxyAlert" = "Are you sure you want to enable this proxy?\nServer: %1$@\nPort: %2$@\n\nYou can change your proxy server later it in the Settings (Data and Storage)."; "Settings.ApplyProxyAlert" = "Are you sure you want to enable this proxy?\nServer: %1$@\nPort: %2$@\n\nYou can change your proxy server later it in the Settings (Data and Storage).";
"Settings.ApplyProxyAlertCredentials" = "Are you sure you want to enable this proxy?\nServer: %1$@\nPort: %2$@\nUsername: %3$@\nPassword: %4$@\n\nYou can change your proxy server later it in the Settings (Data and Storage)."; "Settings.ApplyProxyAlertCredentials" = "Are you sure you want to enable this proxy?\nServer: %1$@\nPort: %2$@\nUsername: %3$@\nPassword: %4$@\n\nYou can change your proxy server later it in the Settings (Data and Storage).";
@ -4002,6 +4004,8 @@ Unused sets are archived when you add more.";
"CallFeedback.ReasonSilentLocal" = "I couldn't hear the other side"; "CallFeedback.ReasonSilentLocal" = "I couldn't hear the other side";
"CallFeedback.ReasonSilentRemote" = "The other side couldn't hear me"; "CallFeedback.ReasonSilentRemote" = "The other side couldn't hear me";
"CallFeedback.ReasonDropped" = "Call ended unexpectedly"; "CallFeedback.ReasonDropped" = "Call ended unexpectedly";
"CallFeedback.VideoReasonDistorted" = "Video was distorted";
"CallFeedback.VideoReasonLowQuality" = "Video was pixelated";
"CallFeedback.AddComment" = "Add an optional comment"; "CallFeedback.AddComment" = "Add an optional comment";
"CallFeedback.IncludeLogs" = "Include technical information"; "CallFeedback.IncludeLogs" = "Include technical information";
"CallFeedback.IncludeLogsInfo" = "This won't reveal the contents of your conversation, but will help us fix the issue sooner."; "CallFeedback.IncludeLogsInfo" = "This won't reveal the contents of your conversation, but will help us fix the issue sooner.";

View File

@ -94,19 +94,22 @@ public final class PresentationCallVideoView {
public let setOnFirstFrameReceived: (((Float) -> Void)?) -> Void public let setOnFirstFrameReceived: (((Float) -> Void)?) -> Void
public let getOrientation: () -> Orientation public let getOrientation: () -> Orientation
public let setOnOrientationUpdated: (((Orientation) -> Void)?) -> Void public let getAspect: () -> CGFloat
public let setOnOrientationUpdated: (((Orientation, CGFloat) -> Void)?) -> Void
public let setOnIsMirroredUpdated: (((Bool) -> Void)?) -> Void public let setOnIsMirroredUpdated: (((Bool) -> Void)?) -> Void
public init( public init(
view: UIView, view: UIView,
setOnFirstFrameReceived: @escaping (((Float) -> Void)?) -> Void, setOnFirstFrameReceived: @escaping (((Float) -> Void)?) -> Void,
getOrientation: @escaping () -> Orientation, getOrientation: @escaping () -> Orientation,
setOnOrientationUpdated: @escaping (((Orientation) -> Void)?) -> Void, getAspect: @escaping () -> CGFloat,
setOnOrientationUpdated: @escaping (((Orientation, CGFloat) -> Void)?) -> Void,
setOnIsMirroredUpdated: @escaping (((Bool) -> Void)?) -> Void setOnIsMirroredUpdated: @escaping (((Bool) -> Void)?) -> Void
) { ) {
self.view = view self.view = view
self.setOnFirstFrameReceived = setOnFirstFrameReceived self.setOnFirstFrameReceived = setOnFirstFrameReceived
self.getOrientation = getOrientation self.getOrientation = getOrientation
self.getAspect = getAspect
self.setOnOrientationUpdated = setOnOrientationUpdated self.setOnOrientationUpdated = setOnOrientationUpdated
self.setOnIsMirroredUpdated = setOnIsMirroredUpdated self.setOnIsMirroredUpdated = setOnIsMirroredUpdated
} }
@ -137,6 +140,7 @@ public protocol PresentationCall: class {
func toggleIsMuted() func toggleIsMuted()
func setIsMuted(_ value: Bool) func setIsMuted(_ value: Bool)
func requestVideo() func requestVideo()
func setRequestedVideoAspect(_ aspect: Float)
func disableVideo() func disableVideo()
func setOutgoingVideoIsPaused(_ isPaused: Bool) func setOutgoingVideoIsPaused(_ isPaused: Bool)
func switchVideoCamera() func switchVideoCamera()

View File

@ -945,8 +945,8 @@ public final class Transaction {
self.postbox?.scanMessages(peerId: peerId, namespace: namespace, tag: tag, f) self.postbox?.scanMessages(peerId: peerId, namespace: namespace, tag: tag, f)
} }
public func scanMessageAttributes(peerId: PeerId, namespace: MessageId.Namespace, _ f: (MessageId, [MessageAttribute]) -> Bool) { public func scanMessageAttributes(peerId: PeerId, namespace: MessageId.Namespace, limit: Int, _ f: (MessageId, [MessageAttribute]) -> Bool) {
self.postbox?.scanMessageAttributes(peerId: peerId, namespace: namespace, f) self.postbox?.scanMessageAttributes(peerId: peerId, namespace: namespace, limit: limit, f)
} }
public func invalidateMessageHistoryTagsSummary(peerId: PeerId, namespace: MessageId.Namespace, tagMask: MessageTags) { public func invalidateMessageHistoryTagsSummary(peerId: PeerId, namespace: MessageId.Namespace, tagMask: MessageTags) {
@ -3174,9 +3174,10 @@ public final class Postbox {
} }
} }
fileprivate func scanMessageAttributes(peerId: PeerId, namespace: MessageId.Namespace, _ f: (MessageId, [MessageAttribute]) -> Bool) { fileprivate func scanMessageAttributes(peerId: PeerId, namespace: MessageId.Namespace, limit: Int, _ f: (MessageId, [MessageAttribute]) -> Bool) {
var remainingLimit = limit
var index = MessageIndex.upperBound(peerId: peerId, namespace: namespace) var index = MessageIndex.upperBound(peerId: peerId, namespace: namespace)
while true { while remainingLimit > 0 {
let messages = self.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: nil, from: index, includeFrom: false, to: MessageIndex.lowerBound(peerId: peerId, namespace: namespace), limit: 32) let messages = self.messageHistoryTable.fetch(peerId: peerId, namespace: namespace, tag: nil, from: index, includeFrom: false, to: MessageIndex.lowerBound(peerId: peerId, namespace: namespace), limit: 32)
for message in messages { for message in messages {
let attributes = MessageHistoryTable.renderMessageAttributes(message) let attributes = MessageHistoryTable.renderMessageAttributes(message)
@ -3184,6 +3185,7 @@ public final class Postbox {
break break
} }
} }
remainingLimit -= messages.count
if let last = messages.last { if let last = messages.last {
index = last.index index = last.index
} else { } else {

View File

@ -43,6 +43,7 @@ private enum DebugControllerSection: Int32 {
case logging case logging
case experiments case experiments
case videoExperiments case videoExperiments
case videoExperiments2
case info case info
} }
@ -73,6 +74,8 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case playerEmbedding(Bool) case playerEmbedding(Bool)
case playlistPlayback(Bool) case playlistPlayback(Bool)
case preferredVideoCodec(Int, String, String?, Bool) case preferredVideoCodec(Int, String, String?, Bool)
case disableVideoAspectScaling(Bool)
case enableVoipTcp(Bool)
case hostInfo(PresentationTheme, String) case hostInfo(PresentationTheme, String)
case versionInfo(PresentationTheme) case versionInfo(PresentationTheme)
@ -90,6 +93,8 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.experiments.rawValue return DebugControllerSection.experiments.rawValue
case .preferredVideoCodec: case .preferredVideoCodec:
return DebugControllerSection.videoExperiments.rawValue return DebugControllerSection.videoExperiments.rawValue
case .disableVideoAspectScaling, .enableVoipTcp:
return DebugControllerSection.videoExperiments2.rawValue
case .hostInfo, .versionInfo: case .hostInfo, .versionInfo:
return DebugControllerSection.info.rawValue return DebugControllerSection.info.rawValue
} }
@ -149,10 +154,14 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 25 return 25
case let .preferredVideoCodec(index, _, _, _): case let .preferredVideoCodec(index, _, _, _):
return 26 + index return 26 + index
case .hostInfo: case .disableVideoAspectScaling:
return 100 return 100
case .versionInfo: case .enableVoipTcp:
return 101 return 101
case .hostInfo:
return 102
case .versionInfo:
return 103
} }
} }
@ -580,6 +589,26 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}).start() }).start()
}) })
case let .disableVideoAspectScaling(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Video Cropping Optimization", value: !value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings as? ExperimentalUISettings ?? ExperimentalUISettings.defaultSettings
settings.disableVideoAspectScaling = !value
return settings
})
}).start()
})
case let .enableVoipTcp(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Enable VoIP TCP", value: !value, sectionId: self.section, style: .blocks, updated: { value in
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
var settings = settings as? ExperimentalUISettings ?? ExperimentalUISettings.defaultSettings
settings.enableVoipTcp = value
return settings
})
}).start()
})
case let .hostInfo(theme, string): case let .hostInfo(theme, string):
return ItemListTextItem(presentationData: presentationData, text: .plain(string), sectionId: self.section) return ItemListTextItem(presentationData: presentationData, text: .plain(string), sectionId: self.section)
case let .versionInfo(theme): case let .versionInfo(theme):
@ -637,6 +666,9 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
for i in 0 ..< codecs.count { for i in 0 ..< codecs.count {
entries.append(.preferredVideoCodec(i, codecs[i].0, codecs[i].1, experimentalSettings.preferredVideoCodec == codecs[i].1)) entries.append(.preferredVideoCodec(i, codecs[i].0, codecs[i].1, experimentalSettings.preferredVideoCodec == codecs[i].1))
} }
entries.append(.disableVideoAspectScaling(experimentalSettings.disableVideoAspectScaling))
entries.append(.enableVoipTcp(experimentalSettings.enableVoipTcp))
if let backupHostOverride = networkSettings?.backupHostOverride { if let backupHostOverride = networkSettings?.backupHostOverride {
entries.append(.hostInfo(presentationData.theme, "Host: \(backupHostOverride)")) entries.append(.hostInfo(presentationData.theme, "Host: \(backupHostOverride)"))

View File

@ -24,7 +24,7 @@ protocol CallControllerNodeProtocol: class {
var acceptCall: (() -> Void)? { get set } var acceptCall: (() -> Void)? { get set }
var endCall: (() -> Void)? { get set } var endCall: (() -> Void)? { get set }
var back: (() -> Void)? { get set } var back: (() -> Void)? { get set }
var presentCallRating: ((CallId) -> Void)? { get set } var presentCallRating: ((CallId, Bool) -> Void)? { get set }
var present: ((ViewController) -> Void)? { get set } var present: ((ViewController) -> Void)? { get set }
var callEnded: ((Bool) -> Void)? { get set } var callEnded: ((Bool) -> Void)? { get set }
var dismissedInteractively: (() -> Void)? { get set } var dismissedInteractively: (() -> Void)? { get set }
@ -230,13 +230,13 @@ public final class CallController: ViewController {
let _ = self?.dismiss() let _ = self?.dismiss()
} }
self.controllerNode.presentCallRating = { [weak self] callId in self.controllerNode.presentCallRating = { [weak self] callId, isVideo in
if let strongSelf = self, !strongSelf.presentedCallRating { if let strongSelf = self, !strongSelf.presentedCallRating {
strongSelf.presentedCallRating = true strongSelf.presentedCallRating = true
Queue.mainQueue().after(0.5, { Queue.mainQueue().after(0.5, {
let window = strongSelf.window let window = strongSelf.window
let controller = callRatingController(sharedContext: strongSelf.sharedContext, account: strongSelf.account, callId: callId, userInitiated: false, present: { c, a in let controller = callRatingController(sharedContext: strongSelf.sharedContext, account: strongSelf.account, callId: callId, userInitiated: false, isVideo: isVideo, present: { c, a in
if let window = window { if let window = window {
c.presentationArguments = a c.presentationArguments = a
window.present(c, on: .root, blockInteraction: false, completion: {}) window.present(c, on: .root, blockInteraction: false, completion: {})

View File

@ -42,18 +42,21 @@ private final class CallVideoNode: ASDisplayNode {
private let isFlippedUpdated: (CallVideoNode) -> Void private let isFlippedUpdated: (CallVideoNode) -> Void
private(set) var currentOrientation: PresentationCallVideoView.Orientation 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) { init(videoView: PresentationCallVideoView, disabledText: String?, assumeReadyAfterTimeout: Bool, isReadyUpdated: @escaping () -> Void, orientationUpdated: @escaping () -> Void, isFlippedUpdated: @escaping (CallVideoNode) -> Void) {
self.isReadyUpdated = isReadyUpdated self.isReadyUpdated = isReadyUpdated
self.isFlippedUpdated = isFlippedUpdated self.isFlippedUpdated = isFlippedUpdated
self.videoTransformContainer = ASDisplayNode() self.videoTransformContainer = ASDisplayNode()
self.videoTransformContainer.clipsToBounds = true
self.videoView = videoView self.videoView = videoView
videoView.view.clipsToBounds = true videoView.view.clipsToBounds = true
videoView.view.backgroundColor = .black videoView.view.backgroundColor = .black
self.currentOrientation = videoView.getOrientation() self.currentOrientation = videoView.getOrientation()
self.currentAspect = videoView.getAspect()
self.videoPausedNode = ImmediateTextNode() self.videoPausedNode = ImmediateTextNode()
self.videoPausedNode.alpha = 0.0 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 { Queue.mainQueue().async {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }
if strongSelf.currentOrientation != orientation { if strongSelf.currentOrientation != orientation || strongSelf.currentAspect != aspect {
strongSelf.currentOrientation = orientation strongSelf.currentOrientation = orientation
strongSelf.currentAspect = aspect
orientationUpdated() 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 self.currentCornerRadius = cornerRadius
var rotationAngle: CGFloat var rotationAngle: CGFloat
switch self.currentOrientation { if isOutgoing {
case .rotation0:
rotationAngle = 0.0
case .rotation90:
rotationAngle = -CGFloat.pi / 2.0
case .rotation180:
rotationAngle = -CGFloat.pi
case .rotation270:
rotationAngle = CGFloat.pi / 2.0 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 { } else {
if rotateFrame { switch self.currentOrientation {
originalRotateFrame = false 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 { if rotateFrame {
let frameSize = CGSize(width: size.height, height: size.width).aspectFitted(size) fittingSize = CGSize(width: size.height, height: size.width)
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
}
} else { } else {
videoFrame = CGRect(origin: CGPoint(), size: size) fittingSize = size
if size.width > size.height { }
scale = 1.0
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 { } 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)) 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)) 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: videoFrame.size)
self.videoTransformContainer.bounds = CGRect(origin: CGPoint(), size: size) if transition.isAnimated && !videoFrame.height.isZero, let previousVideoHeight = self.previousVideoHeight, !previousVideoHeight.isZero {
if transition.isAnimated && !videoFrame.height.isZero && !previousVideoFrame.height.isZero { let scaleDifference = previousVideoHeight / rotatedVideoHeight
transition.animateTransformScale(node: self.videoTransformContainer, from: previousVideoFrame.height / size.height, additive: true) if abs(scaleDifference - 1.0) > 0.001 {
transition.animateTransformScale(node: self.videoTransformContainer, from: scaleDifference, additive: true)
}
} }
transition.updatePosition(node: self.videoTransformContainer, position: videoFrame.center) self.previousVideoHeight = rotatedVideoHeight
transition.updateSublayerTransformScale(node: self.videoTransformContainer, scale: scale) transition.updatePosition(node: self.videoTransformContainer, position: CGPoint(x: size.width / 2.0, y: size.height / 2.0))
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)
let localVideoFrame = CGRect(origin: CGPoint(), size: videoFrame.size)
self.videoView.view.bounds = localVideoFrame self.videoView.view.bounds = localVideoFrame
self.videoView.view.center = localVideoFrame.center self.videoView.view.center = localVideoFrame.center
transition.updateTransformRotation(view: self.videoView.view, angle: rotationAngle) transition.updateTransformRotation(view: self.videoView.view, angle: rotationAngle)
@ -392,7 +402,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
var acceptCall: (() -> Void)? var acceptCall: (() -> Void)?
var endCall: (() -> Void)? var endCall: (() -> Void)?
var back: (() -> Void)? var back: (() -> Void)?
var presentCallRating: ((CallId) -> Void)? var presentCallRating: ((CallId, Bool) -> Void)?
var callEnded: ((Bool) -> Void)? var callEnded: ((Bool) -> Void)?
var dismissedInteractively: (() -> Void)? var dismissedInteractively: (() -> Void)?
var present: ((ViewController) -> Void)? var present: ((ViewController) -> Void)?
@ -419,6 +429,8 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
private var deviceOrientation: UIDeviceOrientation = .portrait private var deviceOrientation: UIDeviceOrientation = .portrait
private var orientationDidChangeObserver: NSObjectProtocol? 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) { 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.sharedContext = sharedContext
self.account = account self.account = account
@ -922,11 +934,18 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
case let .error(error): case let .error(error):
let text = self.presentationData.strings.Call_StatusFailed let text = self.presentationData.strings.Call_StatusFailed
switch error { switch error {
case .notSupportedByPeer: case let .notSupportedByPeer(isVideo):
if !self.displayedVersionOutdatedAlert, let peer = self.peer { if !self.displayedVersionOutdatedAlert, let peer = self.peer {
self.displayedVersionOutdatedAlert = true 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: default:
@ -1019,7 +1038,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
if case let .terminated(id, _, reportRating) = callState.state, let callId = id { if case let .terminated(id, _, reportRating) = callState.state, let callId = id {
let presentRating = reportRating || self.forceReportRating let presentRating = reportRating || self.forceReportRating
if presentRating { if presentRating {
self.presentCallRating?(callId) self.presentCallRating?(callId, self.call.isVideo)
} }
self.callEnded?(presentRating) 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) let previewVideoSide = interpolate(from: 350.0, to: 200.0, value: 1.0 - self.pictureInPictureTransitionFraction)
var previewVideoSize = layout.size.aspectFitted(CGSize(width: previewVideoSide, height: previewVideoSide)) var previewVideoSize = layout.size.aspectFitted(CGSize(width: previewVideoSide, height: previewVideoSide))
previewVideoSize = CGSize(width: 30.0, height: 45.0).aspectFitted(previewVideoSize) previewVideoSize = CGSize(width: 30.0, height: 45.0).aspectFitted(previewVideoSize)
if let minimizedVideoNode = minimizedVideoNode { if let minimizedVideoNode = self.minimizedVideoNode {
switch minimizedVideoNode.currentOrientation { switch minimizedVideoNode.currentOrientation {
case .rotation90, .rotation270: case .rotation90, .rotation270:
previewVideoSize = CGSize(width: previewVideoSize.height, height: previewVideoSize.width)
default:
break break
default:
previewVideoSize = CGSize(width: previewVideoSize.height, height: previewVideoSize.width)
} }
} }
let previewVideoY: CGFloat let previewVideoY: CGFloat
@ -1305,6 +1324,13 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
self.validLayout = (layout, navigationBarHeight) 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 var isUIHidden = self.isUIHidden
switch self.callState?.state { switch self.callState?.state {
case .terminated, .terminating: case .terminated, .terminating:
@ -1445,7 +1471,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
expandedVideoTransition.updateFrame(node: expandedVideoNode, frame: fullscreenVideoFrame) 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 { if self.animateRequestedVideoOnce {
self.animateRequestedVideoOnce = false self.animateRequestedVideoOnce = false
@ -1495,7 +1521,7 @@ final class CallControllerNode: ViewControllerTracingNode, CallControllerNodePro
self.animationForExpandedVideoSnapshotView = nil self.animationForExpandedVideoSnapshotView = nil
} }
minimizedVideoTransition.updateFrame(node: minimizedVideoNode, frame: previewVideoFrame) 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 { if transition.isAnimated && didAppear {
minimizedVideoNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5) 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 { if let debugNode = self.debugNode {
transition.updateFrame(node: debugNode, frame: CGRect(origin: CGPoint(), size: layout.size)) 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() { @objc func keyPressed() {

View File

@ -12,6 +12,9 @@ import OverlayStatusController
import AccountContext import AccountContext
private enum CallFeedbackReason: Int32, CaseIterable { private enum CallFeedbackReason: Int32, CaseIterable {
case videoDistorted
case videoLowQuality
case echo case echo
case noise case noise
case interruption case interruption
@ -36,6 +39,19 @@ private enum CallFeedbackReason: Int32, CaseIterable {
return "silent_remote" return "silent_remote"
case .dropped: case .dropped:
return "dropped" return "dropped"
case .videoDistorted:
return "distorted_video"
case .videoLowQuality:
return "pixelated_video"
}
}
var isVideoRelated: Bool {
switch self {
case .videoDistorted, .videoLowQuality:
return true
default:
return false
} }
} }
@ -55,6 +71,10 @@ private enum CallFeedbackReason: Int32, CaseIterable {
return strings.CallFeedback_ReasonSilentRemote return strings.CallFeedback_ReasonSilentRemote
case .dropped: case .dropped:
return strings.CallFeedback_ReasonDropped return strings.CallFeedback_ReasonDropped
case .videoDistorted:
return strings.CallFeedback_VideoReasonDistorted
case .videoLowQuality:
return strings.CallFeedback_VideoReasonLowQuality
} }
} }
} }
@ -214,11 +234,22 @@ private struct CallFeedbackState: Equatable {
} }
} }
private func callFeedbackControllerEntries(theme: PresentationTheme, strings: PresentationStrings, state: CallFeedbackState) -> [CallFeedbackControllerEntry] { private func callFeedbackControllerEntries(theme: PresentationTheme, strings: PresentationStrings, state: CallFeedbackState, isVideo: Bool) -> [CallFeedbackControllerEntry] {
var entries: [CallFeedbackControllerEntry] = [] var entries: [CallFeedbackControllerEntry] = []
entries.append(.reasonsHeader(theme, strings.CallFeedback_WhatWentWrong)) entries.append(.reasonsHeader(theme, strings.CallFeedback_WhatWentWrong))
if isVideo {
for reason in CallFeedbackReason.allCases {
if !reason.isVideoRelated {
continue
}
entries.append(.reason(theme, reason, CallFeedbackReason.localizedString(for: reason, strings: strings), state.reasons.contains(reason)))
}
}
for reason in CallFeedbackReason.allCases { for reason in CallFeedbackReason.allCases {
if reason.isVideoRelated {
continue
}
entries.append(.reason(theme, reason, CallFeedbackReason.localizedString(for: reason, strings: strings), state.reasons.contains(reason))) entries.append(.reason(theme, reason, CallFeedbackReason.localizedString(for: reason, strings: strings), state.reasons.contains(reason)))
} }
@ -230,7 +261,7 @@ private func callFeedbackControllerEntries(theme: PresentationTheme, strings: Pr
return entries return entries
} }
public func callFeedbackController(sharedContext: SharedAccountContext, account: Account, callId: CallId, rating: Int, userInitiated: Bool) -> ViewController { public func callFeedbackController(sharedContext: SharedAccountContext, account: Account, callId: CallId, rating: Int, userInitiated: Bool, isVideo: Bool) -> ViewController {
let initialState = CallFeedbackState() let initialState = CallFeedbackState()
let statePromise = ValuePromise(initialState, ignoreRepeated: true) let statePromise = ValuePromise(initialState, ignoreRepeated: true)
let stateValue = Atomic(value: initialState) let stateValue = Atomic(value: initialState)
@ -290,7 +321,7 @@ public func callFeedbackController(sharedContext: SharedAccountContext, account:
}) })
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.CallFeedback_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.CallFeedback_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back))
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: callFeedbackControllerEntries(theme: presentationData.theme, strings: presentationData.strings, state: state), style: .blocks, animateChanges: false) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: callFeedbackControllerEntries(theme: presentationData.theme, strings: presentationData.strings, state: state, isVideo: isVideo), style: .blocks, animateChanges: false)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} }

View File

@ -246,7 +246,7 @@ func rateCallAndSendLogs(account: Account, callId: CallId, starsCount: Int, comm
let rate = rateCall(account: account, callId: callId, starsCount: Int32(starsCount), comment: comment, userInitiated: userInitiated) let rate = rateCall(account: account, callId: callId, starsCount: Int32(starsCount), comment: comment, userInitiated: userInitiated)
if includeLogs { if includeLogs {
let id = arc4random64() let id = arc4random64()
let name = "\(callId.id)_\(callId.accessHash).log" let name = "\(callId.id)_\(callId.accessHash).log.json"
let path = callLogsPath(account: account) + "/" + name let path = callLogsPath(account: account) + "/" + name
let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: id), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: nil, attributes: [.FileName(fileName: name)]) let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: LocalFileReferenceMediaResource(localFilePath: path, randomId: id), previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: nil, attributes: [.FileName(fileName: name)])
let message = EnqueueMessage.message(text: comment, attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil) let message = EnqueueMessage.message(text: comment, attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil)
@ -266,7 +266,7 @@ func rateCallAndSendLogs(account: Account, callId: CallId, starsCount: Int, comm
} }
} }
public func callRatingController(sharedContext: SharedAccountContext, account: Account, callId: CallId, userInitiated: Bool, present: @escaping (ViewController, Any) -> Void, push: @escaping (ViewController) -> Void) -> AlertController { public func callRatingController(sharedContext: SharedAccountContext, account: Account, callId: CallId, userInitiated: Bool, isVideo: Bool, present: @escaping (ViewController, Any) -> Void, push: @escaping (ViewController) -> Void) -> AlertController {
let presentationData = sharedContext.currentPresentationData.with { $0 } let presentationData = sharedContext.currentPresentationData.with { $0 }
let theme = presentationData.theme let theme = presentationData.theme
let strings = presentationData.strings let strings = presentationData.strings
@ -282,7 +282,7 @@ public func callRatingController(sharedContext: SharedAccountContext, account: A
}, apply: { rating in }, apply: { rating in
dismissImpl?(true) dismissImpl?(true)
if rating < 4 { if rating < 4 {
push(callFeedbackController(sharedContext: sharedContext, account: account, callId: callId, rating: rating, userInitiated: userInitiated)) push(callFeedbackController(sharedContext: sharedContext, account: account, callId: callId, rating: rating, userInitiated: userInitiated, isVideo: isVideo))
} else { } else {
let _ = rateCallAndSendLogs(account: account, callId: callId, starsCount: rating, comment: "", userInitiated: userInitiated, includeLogs: false).start() let _ = rateCallAndSendLogs(account: account, callId: callId, starsCount: rating, comment: "", userInitiated: userInitiated, includeLogs: false).start()
} }

View File

@ -62,7 +62,7 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
var endCall: (() -> Void)? var endCall: (() -> Void)?
var setIsVideoPaused: ((Bool) -> Void)? var setIsVideoPaused: ((Bool) -> Void)?
var back: (() -> Void)? var back: (() -> Void)?
var presentCallRating: ((CallId) -> Void)? var presentCallRating: ((CallId, Bool) -> Void)?
var callEnded: ((Bool) -> Void)? var callEnded: ((Bool) -> Void)?
var dismissedInteractively: (() -> Void)? var dismissedInteractively: (() -> Void)?
var present: ((ViewController) -> Void)? var present: ((ViewController) -> Void)?
@ -307,7 +307,7 @@ final class LegacyCallControllerNode: ASDisplayNode, CallControllerNodeProtocol
if case let .terminated(id, _, reportRating) = callState.state, let callId = id { if case let .terminated(id, _, reportRating) = callState.state, let callId = id {
let presentRating = reportRating || self.forceReportRating let presentRating = reportRating || self.forceReportRating
if presentRating { if presentRating {
self.presentCallRating?(callId) self.presentCallRating?(callId, false)
} }
self.callEnded?(presentRating) self.callEnded?(presentRating)
} }

View File

@ -169,6 +169,7 @@ public final class PresentationCallImpl: PresentationCall {
public let isOutgoing: Bool public let isOutgoing: Bool
public var isVideo: Bool public var isVideo: Bool
public var isVideoPossible: Bool public var isVideoPossible: Bool
private let enableStunMarking: Bool
public let preferredVideoCodec: String? public let preferredVideoCodec: String?
public let peer: Peer? public let peer: Peer?
@ -184,6 +185,7 @@ public final class PresentationCallImpl: PresentationCall {
private var callContextState: OngoingCallContextState? private var callContextState: OngoingCallContextState?
private var ongoingContext: OngoingCallContext? private var ongoingContext: OngoingCallContext?
private var ongoingContextStateDisposable: Disposable? private var ongoingContextStateDisposable: Disposable?
private var requestedVideoAspect: Float?
private var reception: Int32? private var reception: Int32?
private var receptionDisposable: Disposable? private var receptionDisposable: Disposable?
private var reportedIncomingCall = false private var reportedIncomingCall = false
@ -265,6 +267,7 @@ public final class PresentationCallImpl: PresentationCall {
updatedNetworkType: Signal<NetworkType, NoError>, updatedNetworkType: Signal<NetworkType, NoError>,
startWithVideo: Bool, startWithVideo: Bool,
isVideoPossible: Bool, isVideoPossible: Bool,
enableStunMarking: Bool,
preferredVideoCodec: String? preferredVideoCodec: String?
) { ) {
self.account = account self.account = account
@ -292,6 +295,7 @@ public final class PresentationCallImpl: PresentationCall {
self.isOutgoing = isOutgoing self.isOutgoing = isOutgoing
self.isVideo = initialState?.type == .video self.isVideo = initialState?.type == .video
self.isVideoPossible = isVideoPossible self.isVideoPossible = isVideoPossible
self.enableStunMarking = enableStunMarking
self.preferredVideoCodec = preferredVideoCodec self.preferredVideoCodec = preferredVideoCodec
self.peer = peer self.peer = peer
self.isVideo = startWithVideo self.isVideo = startWithVideo
@ -606,9 +610,12 @@ public final class PresentationCallImpl: PresentationCall {
if let _ = audioSessionControl, !wasActive || previousControl == nil { if let _ = audioSessionControl, !wasActive || previousControl == nil {
let logName = "\(id.id)_\(id.accessHash)" let logName = "\(id.id)_\(id.accessHash)"
let ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer, initialNetworkType: self.currentNetworkType, updatedNetworkType: self.updatedNetworkType, serializedData: self.serializedData, dataSaving: dataSaving, derivedState: self.derivedState, key: key, isOutgoing: sessionState.isOutgoing, video: self.videoCapturer, connections: connections, maxLayer: maxLayer, version: version, allowP2P: allowsP2P, audioSessionActive: self.audioSessionActive.get(), logName: logName, preferredVideoCodec: self.preferredVideoCodec) let ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer, initialNetworkType: self.currentNetworkType, updatedNetworkType: self.updatedNetworkType, serializedData: self.serializedData, dataSaving: dataSaving, derivedState: self.derivedState, key: key, isOutgoing: sessionState.isOutgoing, video: self.videoCapturer, connections: connections, maxLayer: maxLayer, version: version, allowP2P: allowsP2P, enableStunMarking: self.enableStunMarking, audioSessionActive: self.audioSessionActive.get(), logName: logName, preferredVideoCodec: self.preferredVideoCodec)
self.ongoingContext = ongoingContext self.ongoingContext = ongoingContext
ongoingContext.setIsMuted(self.isMutedValue) ongoingContext.setIsMuted(self.isMutedValue)
if let requestedVideoAspect = self.requestedVideoAspect {
ongoingContext.setRequestedVideoAspect(requestedVideoAspect)
}
self.debugInfoValue.set(ongoingContext.debugInfo()) self.debugInfoValue.set(ongoingContext.debugInfo())
@ -848,6 +855,11 @@ public final class PresentationCallImpl: PresentationCall {
} }
} }
public func setRequestedVideoAspect(_ aspect: Float) {
self.requestedVideoAspect = aspect
self.ongoingContext?.setRequestedVideoAspect(aspect)
}
public func disableVideo() { public func disableVideo() {
if let _ = self.videoCapturer { if let _ = self.videoCapturer {
self.videoCapturer = nil self.videoCapturer = nil
@ -909,8 +921,15 @@ public final class PresentationCallImpl: PresentationCall {
return .rotation0 return .rotation0
} }
}, },
getAspect: { [weak view] in
if let view = view {
return view.getAspect()
} else {
return 0.0
}
},
setOnOrientationUpdated: { f in setOnOrientationUpdated: { f in
setOnOrientationUpdated { value in setOnOrientationUpdated { value, aspect in
let mappedValue: PresentationCallVideoView.Orientation let mappedValue: PresentationCallVideoView.Orientation
switch value { switch value {
case .rotation0: case .rotation0:
@ -922,7 +941,7 @@ public final class PresentationCallImpl: PresentationCall {
case .rotation270: case .rotation270:
mappedValue = .rotation270 mappedValue = .rotation270
} }
f?(mappedValue) f?(mappedValue, aspect)
} }
}, },
setOnIsMirroredUpdated: { f in setOnIsMirroredUpdated: { f in
@ -971,8 +990,15 @@ public final class PresentationCallImpl: PresentationCall {
return .rotation0 return .rotation0
} }
}, },
getAspect: { [weak view] in
if let view = view {
return view.getAspect()
} else {
return 0.0
}
},
setOnOrientationUpdated: { f in setOnOrientationUpdated: { f in
setOnOrientationUpdated { value in setOnOrientationUpdated { value, aspect in
let mappedValue: PresentationCallVideoView.Orientation let mappedValue: PresentationCallVideoView.Orientation
switch value { switch value {
case .rotation0: case .rotation0:
@ -984,7 +1010,7 @@ public final class PresentationCallImpl: PresentationCall {
case .rotation270: case .rotation270:
mappedValue = .rotation270 mappedValue = .rotation270
} }
f?(mappedValue) f?(mappedValue, aspect)
} }
}, },
setOnIsMirroredUpdated: { f in setOnIsMirroredUpdated: { f in

View File

@ -17,45 +17,14 @@ private func callKitIntegrationIfEnabled(_ integration: CallKitIntegration?, set
return enabled ? integration : nil return enabled ? integration : nil
} }
private func auxiliaryServers(appConfiguration: AppConfiguration) -> [CallAuxiliaryServer] { private func shouldEnableStunMarking(appConfiguration: AppConfiguration) -> Bool {
guard let data = appConfiguration.data else { guard let data = appConfiguration.data else {
return [] return true
} }
guard let servers = data["rtc_servers"] as? [[String: Any]] else { guard let enableStunMarking = data["voip_enable_stun_marking"] as? Bool else {
return [] return true
} }
var result: [CallAuxiliaryServer] = [] return enableStunMarking
for server in servers {
guard let host = server["host"] as? String else {
continue
}
guard let portString = server["port"] as? String else {
continue
}
guard let username = server["username"] as? String else {
continue
}
guard let password = server["password"] as? String else {
continue
}
guard let port = Int(portString) else {
continue
}
result.append(CallAuxiliaryServer(
host: host,
port: port,
connection: .stun
))
result.append(CallAuxiliaryServer(
host: host,
port: port,
connection: .turn(
username: username,
password: password
)
))
}
return result
} }
private enum CurrentCall { private enum CurrentCall {
@ -309,11 +278,12 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
isOutgoing: false, isOutgoing: false,
peer: firstState.1, peer: firstState.1,
proxyServer: strongSelf.proxyServer, proxyServer: strongSelf.proxyServer,
auxiliaryServers: auxiliaryServers(appConfiguration: appConfiguration), auxiliaryServers: [],
currentNetworkType: firstState.4, currentNetworkType: firstState.4,
updatedNetworkType: firstState.0.networkType, updatedNetworkType: firstState.0.networkType,
startWithVideo: firstState.2.isVideo, startWithVideo: firstState.2.isVideo,
isVideoPossible: firstState.2.isVideoPossible, isVideoPossible: firstState.2.isVideoPossible,
enableStunMarking: shouldEnableStunMarking(appConfiguration: appConfiguration),
preferredVideoCodec: experimentalSettings.preferredVideoCodec preferredVideoCodec: experimentalSettings.preferredVideoCodec
) )
strongSelf.updateCurrentCall(call) strongSelf.updateCurrentCall(call)
@ -551,11 +521,12 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
isOutgoing: true, isOutgoing: true,
peer: nil, peer: nil,
proxyServer: strongSelf.proxyServer, proxyServer: strongSelf.proxyServer,
auxiliaryServers: auxiliaryServers(appConfiguration: appConfiguration), auxiliaryServers: [],
currentNetworkType: currentNetworkType, currentNetworkType: currentNetworkType,
updatedNetworkType: account.networkType, updatedNetworkType: account.networkType,
startWithVideo: isVideo, startWithVideo: isVideo,
isVideoPossible: isVideoPossible, isVideoPossible: isVideoPossible,
enableStunMarking: shouldEnableStunMarking(appConfiguration: appConfiguration),
preferredVideoCodec: experimentalSettings.preferredVideoCodec preferredVideoCodec: experimentalSettings.preferredVideoCodec
) )
strongSelf.updateCurrentCall(call) strongSelf.updateCurrentCall(call)

View File

@ -2878,10 +2878,10 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
} }
// could be the reason for unbounded slowdown, needs investigation // could be the reason for unbounded slowdown, needs investigation
/*for (peerIdAndNamespace, pts) in clearHolesFromPreviousStateForChannelMessagesWithPts { for (peerIdAndNamespace, pts) in clearHolesFromPreviousStateForChannelMessagesWithPts {
var upperMessageId: Int32? var upperMessageId: Int32?
var lowerMessageId: Int32? var lowerMessageId: Int32?
transaction.scanMessageAttributes(peerId: peerIdAndNamespace.peerId, namespace: peerIdAndNamespace.namespace, { id, attributes in transaction.scanMessageAttributes(peerId: peerIdAndNamespace.peerId, namespace: peerIdAndNamespace.namespace, limit: 200, { id, attributes in
for attribute in attributes { for attribute in attributes {
if let attribute = attribute as? ChannelMessageStateVersionAttribute { if let attribute = attribute as? ChannelMessageStateVersionAttribute {
if attribute.pts >= pts { if attribute.pts >= pts {
@ -2906,7 +2906,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP
transaction.removeHole(peerId: peerIdAndNamespace.peerId, namespace: peerIdAndNamespace.namespace, space: .everywhere, range: lowerMessageId ... upperMessageId) transaction.removeHole(peerId: peerIdAndNamespace.peerId, namespace: peerIdAndNamespace.namespace, space: .everywhere, range: lowerMessageId ... upperMessageId)
} }
} }
}*/ }
if !peerActivityTimestamps.isEmpty { if !peerActivityTimestamps.isEmpty {
updatePeerPresenceLastActivities(transaction: transaction, accountPeerId: accountPeerId, activities: peerActivityTimestamps) updatePeerPresenceLastActivities(transaction: transaction, accountPeerId: accountPeerId, activities: peerActivityTimestamps)

View File

@ -11,44 +11,9 @@ private let minLayer: Int32 = 65
public enum CallSessionError: Equatable { public enum CallSessionError: Equatable {
case generic case generic
case privacyRestricted case privacyRestricted
case notSupportedByPeer case notSupportedByPeer(isVideo: Bool)
case serverProvided(String) case serverProvided(text: String)
case disconnected case disconnected
public static func ==(lhs: CallSessionError, rhs: CallSessionError) -> Bool {
switch lhs {
case .generic:
if case .generic = rhs {
return true
} else {
return false
}
case .privacyRestricted:
if case .privacyRestricted = rhs {
return true
} else {
return false
}
case .notSupportedByPeer:
if case .notSupportedByPeer = rhs {
return true
} else {
return false
}
case let .serverProvided(text):
if case .serverProvided(text) = rhs {
return true
} else {
return false
}
case .disconnected:
if case .disconnected = rhs {
return true
} else {
return false
}
}
}
} }
public enum CallSessionEndedType { public enum CallSessionEndedType {
@ -1204,12 +1169,12 @@ private func requestCallSession(postbox: Postbox, network: Network, peerId: Peer
|> `catch` { error -> Signal<RequestCallSessionResult, NoError> in |> `catch` { error -> Signal<RequestCallSessionResult, NoError> in
switch error.errorDescription { switch error.errorDescription {
case "PARTICIPANT_VERSION_OUTDATED": case "PARTICIPANT_VERSION_OUTDATED":
return .single(.failed(.notSupportedByPeer)) return .single(.failed(.notSupportedByPeer(isVideo: isVideo)))
case "USER_PRIVACY_RESTRICTED": case "USER_PRIVACY_RESTRICTED":
return .single(.failed(.privacyRestricted)) return .single(.failed(.privacyRestricted))
default: default:
if error.errorCode == 406 { if error.errorCode == 406 {
return .single(.failed(.serverProvided(error.errorDescription))) return .single(.failed(.serverProvided(text: error.errorDescription)))
} else { } else {
return .single(.failed(.generic)) return .single(.failed(.generic))
} }

View File

@ -1710,9 +1710,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
} }
}, completed: {}) }, completed: {})
} }
}, rateCall: { [weak self] message, callId in }, rateCall: { [weak self] message, callId, isVideo in
if let strongSelf = self { if let strongSelf = self {
let controller = callRatingController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, callId: callId, userInitiated: true, present: { [weak self] c, a in let controller = callRatingController(sharedContext: strongSelf.context.sharedContext, account: strongSelf.context.account, callId: callId, userInitiated: true, isVideo: isVideo, present: { [weak self] c, a in
if let strongSelf = self { if let strongSelf = self {
strongSelf.present(c, in: .window(.root), with: a) strongSelf.present(c, in: .window(.root), with: a)
} }

View File

@ -99,7 +99,7 @@ public final class ChatControllerInteraction {
let navigateToFirstDateMessage: (Int32) -> Void let navigateToFirstDateMessage: (Int32) -> Void
let requestRedeliveryOfFailedMessages: (MessageId) -> Void let requestRedeliveryOfFailedMessages: (MessageId) -> Void
let addContact: (String) -> Void let addContact: (String) -> Void
let rateCall: (Message, CallId) -> Void let rateCall: (Message, CallId, Bool) -> Void
let requestSelectMessagePollOptions: (MessageId, [Data]) -> Void let requestSelectMessagePollOptions: (MessageId, [Data]) -> Void
let requestOpenMessagePollResults: (MessageId, MediaId) -> Void let requestOpenMessagePollResults: (MessageId, MediaId) -> Void
let openAppStorePage: () -> Void let openAppStorePage: () -> Void
@ -138,7 +138,7 @@ public final class ChatControllerInteraction {
var searchTextHighightState: (String, [MessageIndex])? var searchTextHighightState: (String, [MessageIndex])?
var seenOneTimeAnimatedMedia = Set<MessageId>() var seenOneTimeAnimatedMedia = Set<MessageId>()
init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, tapMessage: ((Message) -> Void)?, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId, Bool) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, NSAttributedString, TextSelectionAction) -> Void, updateMessageLike: @escaping (MessageId, Bool) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, dismissReplyMarkupMessage: @escaping (Message) -> Void, openMessagePollResults: @escaping (MessageId, Data) -> Void, openPollCreation: @escaping (Bool?) -> Void, displayPollSolution: @escaping (TelegramMediaPollResults.Solution, ASDisplayNode) -> Void, displayPsa: @escaping (String, ASDisplayNode) -> Void, displayDiceTooltip: @escaping (TelegramMediaDice) -> Void, animateDiceSuccess: @escaping () -> Void, greetingStickerNode: @escaping () -> (ASDisplayNode, ASDisplayNode, ASDisplayNode, () -> Void)?, openPeerContextMenu: @escaping (Peer, ASDisplayNode, CGRect, ContextGesture?) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) { init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, UIGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, tapMessage: ((Message) -> Void)?, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId, Bool) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> ChatControllerInteractionSwipeAction, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId, Bool) -> Void, requestSelectMessagePollOptions: @escaping (MessageId, [Data]) -> Void, requestOpenMessagePollResults: @escaping (MessageId, MediaId) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, NSAttributedString, TextSelectionAction) -> Void, updateMessageLike: @escaping (MessageId, Bool) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, dismissReplyMarkupMessage: @escaping (Message) -> Void, openMessagePollResults: @escaping (MessageId, Data) -> Void, openPollCreation: @escaping (Bool?) -> Void, displayPollSolution: @escaping (TelegramMediaPollResults.Solution, ASDisplayNode) -> Void, displayPsa: @escaping (String, ASDisplayNode) -> Void, displayDiceTooltip: @escaping (TelegramMediaDice) -> Void, animateDiceSuccess: @escaping () -> Void, greetingStickerNode: @escaping () -> (ASDisplayNode, ASDisplayNode, ASDisplayNode, () -> Void)?, openPeerContextMenu: @escaping (Peer, ASDisplayNode, CGRect, ContextGesture?) -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) {
self.openMessage = openMessage self.openMessage = openMessage
self.openPeer = openPeer self.openPeer = openPeer
self.openPeerMention = openPeerMention self.openPeerMention = openPeerMention
@ -228,7 +228,7 @@ public final class ChatControllerInteraction {
}, navigateToFirstDateMessage: { _ in }, navigateToFirstDateMessage: { _ in
}, requestRedeliveryOfFailedMessages: { _ in }, requestRedeliveryOfFailedMessages: { _ in
}, addContact: { _ in }, addContact: { _ in
}, rateCall: { _, _ in }, rateCall: { _, _, _ in
}, requestSelectMessagePollOptions: { _, _ in }, requestSelectMessagePollOptions: { _, _ in
}, requestOpenMessagePollResults: { _, _ in }, requestOpenMessagePollResults: { _, _ in
}, openAppStorePage: { }, openAppStorePage: {

View File

@ -1484,7 +1484,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
strongSelf._buttonKeyboardMessage.set(.single(transition.keyboardButtonsMessage)) strongSelf._buttonKeyboardMessage.set(.single(transition.keyboardButtonsMessage))
} }
/*if transition.animateIn || animateIn { if transition.animateIn || animateIn {
let heightNorm = strongSelf.bounds.height - strongSelf.insets.top let heightNorm = strongSelf.bounds.height - strongSelf.insets.top
strongSelf.forEachVisibleItemNode { itemNode in strongSelf.forEachVisibleItemNode { itemNode in
if let itemNode = itemNode as? ChatMessageItemView { if let itemNode = itemNode as? ChatMessageItemView {
@ -1502,7 +1502,7 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode {
itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay) itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, delay: delay)
itemNode.layer.animateScale(from: 0.9, to: 1.0, duration: 0.4, delay: delay, timingFunction: kCAMediaTimingFunctionSpring) itemNode.layer.animateScale(from: 0.9, to: 1.0, duration: 0.4, delay: delay, timingFunction: kCAMediaTimingFunctionSpring)
} }
}*/ }
if let scrolledToIndex = transition.scrolledToIndex { if let scrolledToIndex = transition.scrolledToIndex {
if let strongSelf = self { if let strongSelf = self {

View File

@ -405,20 +405,22 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
if data.messageActions.options.contains(.rateCall) { if data.messageActions.options.contains(.rateCall) {
var callId: CallId? var callId: CallId?
var isVideo: Bool = false
for media in message.media { for media in message.media {
if let action = media as? TelegramMediaAction, case let .phoneCall(id, discardReason, _, _) = action.action { if let action = media as? TelegramMediaAction, case let .phoneCall(id, discardReason, _, isVideoValue) = action.action {
isVideo = isVideoValue
if discardReason != .busy && discardReason != .missed { if discardReason != .busy && discardReason != .missed {
if let logName = callLogNameForId(id: id, account: context.account) { if let logName = callLogNameForId(id: id, account: context.account) {
let logsPath = callLogsPath(account: context.account) let logsPath = callLogsPath(account: context.account)
let logPath = logsPath + "/" + logName let logPath = logsPath + "/" + logName
let start = logName.index(logName.startIndex, offsetBy: "\(id)".count + 1) let start = logName.index(logName.startIndex, offsetBy: "\(id)".count + 1)
let end = logName.index(logName.endIndex, offsetBy: -4) let end = logName.index(logName.endIndex, offsetBy: -4 - 5)
let accessHash = logName[start..<end] let accessHash = logName[start..<end]
if let accessHash = Int64(accessHash) { if let accessHash = Int64(accessHash) {
callId = CallId(id: id, accessHash: accessHash) callId = CallId(id: id, accessHash: accessHash)
} }
actions.append(.action(ContextMenuActionItem(text: "Share Statistics", icon: { theme in actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Call_ShareStats, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Forward"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in }, action: { _, f in
f(.dismissWithoutContent) f(.dismissWithoutContent)
@ -446,7 +448,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState:
actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Call_RateCall, icon: { theme in actions.append(.action(ContextMenuActionItem(text: chatPresentationInterfaceState.strings.Call_RateCall, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Rate"), color: theme.actionSheet.primaryTextColor)
}, action: { _, f in }, action: { _, f in
let _ = controllerInteraction.rateCall(message, callId) let _ = controllerInteraction.rateCall(message, callId, isVideo)
f(.dismissWithoutContent) f(.dismissWithoutContent)
}))) })))
} }

View File

@ -868,7 +868,7 @@ final class ChatMessageInteractiveFileNode: ASDisplayNode {
statusNode.backgroundNodeColor = backgroundNodeColor statusNode.backgroundNodeColor = backgroundNodeColor
} }
if state != .none && isVoice && self.playbackAudioLevelView == nil { if state != .none && isVoice && self.playbackAudioLevelView == nil && false {
let blobFrame = progressFrame.insetBy(dx: -12.0, dy: -12.0) let blobFrame = progressFrame.insetBy(dx: -12.0, dy: -12.0)
let playbackAudioLevelView = VoiceBlobView( let playbackAudioLevelView = VoiceBlobView(
frame: blobFrame, frame: blobFrame,

View File

@ -425,7 +425,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
}, navigateToFirstDateMessage: { _ in }, navigateToFirstDateMessage: { _ in
}, requestRedeliveryOfFailedMessages: { _ in }, requestRedeliveryOfFailedMessages: { _ in
}, addContact: { _ in }, addContact: { _ in
}, rateCall: { _, _ in }, rateCall: { _, _, _ in
}, requestSelectMessagePollOptions: { _, _ in }, requestSelectMessagePollOptions: { _, _ in
}, requestOpenMessagePollResults: { _, _ in }, requestOpenMessagePollResults: { _, _ in
}, openAppStorePage: { [weak self] in }, openAppStorePage: { [weak self] in

View File

@ -121,7 +121,7 @@ private final class DrawingStickersScreenNode: ViewControllerTracingNode {
}, navigateToFirstDateMessage: { _ in }, navigateToFirstDateMessage: { _ in
}, requestRedeliveryOfFailedMessages: { _ in }, requestRedeliveryOfFailedMessages: { _ in
}, addContact: { _ in }, addContact: { _ in
}, rateCall: { _, _ in }, rateCall: { _, _, _ in
}, requestSelectMessagePollOptions: { _, _ in }, requestSelectMessagePollOptions: { _, _ in
}, requestOpenMessagePollResults: { _, _ in }, requestOpenMessagePollResults: { _, _ in
}, openAppStorePage: { }, openAppStorePage: {

View File

@ -108,7 +108,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu
}, navigateToFirstDateMessage: { _ in }, navigateToFirstDateMessage: { _ in
}, requestRedeliveryOfFailedMessages: { _ in }, requestRedeliveryOfFailedMessages: { _ in
}, addContact: { _ in }, addContact: { _ in
}, rateCall: { _, _ in }, rateCall: { _, _, _ in
}, requestSelectMessagePollOptions: { _, _ in }, requestSelectMessagePollOptions: { _, _ in
}, requestOpenMessagePollResults: { _, _ in }, requestOpenMessagePollResults: { _, _ in
}, openAppStorePage: { }, openAppStorePage: {

View File

@ -1930,7 +1930,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
}, navigateToFirstDateMessage: { _ in }, navigateToFirstDateMessage: { _ in
}, requestRedeliveryOfFailedMessages: { _ in }, requestRedeliveryOfFailedMessages: { _ in
}, addContact: { _ in }, addContact: { _ in
}, rateCall: { _, _ in }, rateCall: { _, _, _ in
}, requestSelectMessagePollOptions: { _, _ in }, requestSelectMessagePollOptions: { _, _ in
}, requestOpenMessagePollResults: { _, _ in }, requestOpenMessagePollResults: { _, _ in
}, openAppStorePage: { }, openAppStorePage: {

View File

@ -413,7 +413,7 @@ public class PeerMediaCollectionController: TelegramBaseController {
}, navigateToFirstDateMessage: { _ in }, navigateToFirstDateMessage: { _ in
}, requestRedeliveryOfFailedMessages: { _ in }, requestRedeliveryOfFailedMessages: { _ in
}, addContact: { _ in }, addContact: { _ in
}, rateCall: { _, _ in }, rateCall: { _, _, _ in
}, requestSelectMessagePollOptions: { _, _ in }, requestSelectMessagePollOptions: { _, _ in
}, requestOpenMessagePollResults: { _, _ in }, requestOpenMessagePollResults: { _, _ in
}, openAppStorePage: { }, openAppStorePage: {

View File

@ -1171,7 +1171,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
}, navigateToFirstDateMessage: { _ in }, navigateToFirstDateMessage: { _ in
}, requestRedeliveryOfFailedMessages: { _ in }, requestRedeliveryOfFailedMessages: { _ in
}, addContact: { _ in }, addContact: { _ in
}, rateCall: { _, _ in }, rateCall: { _, _, _ in
}, requestSelectMessagePollOptions: { _, _ in }, requestSelectMessagePollOptions: { _, _ in
}, requestOpenMessagePollResults: { _, _ in }, requestOpenMessagePollResults: { _, _ in
}, openAppStorePage: { }, openAppStorePage: {

View File

@ -12,6 +12,8 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
public var playerEmbedding: Bool public var playerEmbedding: Bool
public var playlistPlayback: Bool public var playlistPlayback: Bool
public var preferredVideoCodec: String? public var preferredVideoCodec: String?
public var disableVideoAspectScaling: Bool
public var enableVoipTcp: Bool
public static var defaultSettings: ExperimentalUISettings { public static var defaultSettings: ExperimentalUISettings {
return ExperimentalUISettings( return ExperimentalUISettings(
@ -23,7 +25,9 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
foldersTabAtBottom: false, foldersTabAtBottom: false,
playerEmbedding: false, playerEmbedding: false,
playlistPlayback: false, playlistPlayback: false,
preferredVideoCodec: nil preferredVideoCodec: nil,
disableVideoAspectScaling: false,
enableVoipTcp: false
) )
} }
@ -36,7 +40,9 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
foldersTabAtBottom: Bool, foldersTabAtBottom: Bool,
playerEmbedding: Bool, playerEmbedding: Bool,
playlistPlayback: Bool, playlistPlayback: Bool,
preferredVideoCodec: String? preferredVideoCodec: String?,
disableVideoAspectScaling: Bool,
enableVoipTcp: Bool
) { ) {
self.keepChatNavigationStack = keepChatNavigationStack self.keepChatNavigationStack = keepChatNavigationStack
self.skipReadHistory = skipReadHistory self.skipReadHistory = skipReadHistory
@ -47,6 +53,8 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
self.playerEmbedding = playerEmbedding self.playerEmbedding = playerEmbedding
self.playlistPlayback = playlistPlayback self.playlistPlayback = playlistPlayback
self.preferredVideoCodec = preferredVideoCodec self.preferredVideoCodec = preferredVideoCodec
self.disableVideoAspectScaling = disableVideoAspectScaling
self.enableVoipTcp = enableVoipTcp
} }
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
@ -59,6 +67,8 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
self.playerEmbedding = decoder.decodeInt32ForKey("playerEmbedding", orElse: 0) != 0 self.playerEmbedding = decoder.decodeInt32ForKey("playerEmbedding", orElse: 0) != 0
self.playlistPlayback = decoder.decodeInt32ForKey("playlistPlayback", orElse: 0) != 0 self.playlistPlayback = decoder.decodeInt32ForKey("playlistPlayback", orElse: 0) != 0
self.preferredVideoCodec = decoder.decodeOptionalStringForKey("preferredVideoCodec") self.preferredVideoCodec = decoder.decodeOptionalStringForKey("preferredVideoCodec")
self.disableVideoAspectScaling = decoder.decodeInt32ForKey("disableVideoAspectScaling", orElse: 0) != 0
self.enableVoipTcp = decoder.decodeInt32ForKey("enableVoipTcp", orElse: 0) != 0
} }
public func encode(_ encoder: PostboxEncoder) { public func encode(_ encoder: PostboxEncoder) {
@ -73,6 +83,8 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry {
if let preferredVideoCodec = self.preferredVideoCodec { if let preferredVideoCodec = self.preferredVideoCodec {
encoder.encodeString(preferredVideoCodec, forKey: "preferredVideoCodec") encoder.encodeString(preferredVideoCodec, forKey: "preferredVideoCodec")
} }
encoder.encodeInt32(self.disableVideoAspectScaling ? 1 : 0, forKey: "disableVideoAspectScaling")
encoder.encodeInt32(self.enableVoipTcp ? 1 : 0, forKey: "enableVoipTcp")
} }
public func isEqual(to: PreferencesEntry) -> Bool { public func isEqual(to: PreferencesEntry) -> Bool {

View File

@ -34,8 +34,6 @@ private func callConnectionDescriptionsWebrtc(_ connection: CallSessionConnectio
} }
} }
private let callLogsLimit = 20
public func callLogNameForId(id: Int64, account: Account) -> String? { public func callLogNameForId(id: Int64, account: Account) -> String? {
let path = callLogsPath(account: account) let path = callLogsPath(account: account)
let namePrefix = "\(id)_" let namePrefix = "\(id)_"
@ -63,26 +61,25 @@ private func cleanupCallLogs(account: Account) {
try? fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) try? fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
} }
var oldest: (URL, Date)? = nil var oldest: [(URL, Date)] = []
var count = 0 var count = 0
if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: path), includingPropertiesForKeys: [.contentModificationDateKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) { if let enumerator = FileManager.default.enumerator(at: URL(fileURLWithPath: path), includingPropertiesForKeys: [.contentModificationDateKey], options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants], errorHandler: nil) {
for url in enumerator { for url in enumerator {
if let url = url as? URL { if let url = url as? URL {
if let date = (try? url.resourceValues(forKeys: Set([.contentModificationDateKey])))?.contentModificationDate { if let date = (try? url.resourceValues(forKeys: Set([.contentModificationDateKey])))?.contentModificationDate {
if let currentOldest = oldest { oldest.append((url, date))
if date < currentOldest.1 {
oldest = (url, date)
}
} else {
oldest = (url, date)
}
count += 1 count += 1
} }
} }
} }
} }
if count > callLogsLimit, let oldest = oldest { let callLogsLimit = 40
try? fileManager.removeItem(atPath: oldest.0.path) if count > callLogsLimit {
oldest.sort(by: { $0.1 > $1.1 })
while oldest.count > callLogsLimit {
try? fileManager.removeItem(atPath: oldest[oldest.count - 1].0.path)
oldest.removeLast()
}
} }
} }
@ -271,6 +268,7 @@ private protocol OngoingCallThreadLocalContextProtocol: class {
func nativeSetIsMuted(_ value: Bool) func nativeSetIsMuted(_ value: Bool)
func nativeSetIsLowBatteryLevel(_ value: Bool) func nativeSetIsLowBatteryLevel(_ value: Bool)
func nativeRequestVideo(_ capturer: OngoingCallVideoCapturer) func nativeRequestVideo(_ capturer: OngoingCallVideoCapturer)
func nativeSetRequestedVideoAspect(_ aspect: Float)
func nativeDisableVideo() func nativeDisableVideo()
func nativeStop(_ completion: @escaping (String?, Int64, Int64, Int64, Int64) -> Void) func nativeStop(_ completion: @escaping (String?, Int64, Int64, Int64, Int64) -> Void)
func nativeBeginTermination() func nativeBeginTermination()
@ -309,6 +307,9 @@ extension OngoingCallThreadLocalContext: OngoingCallThreadLocalContextProtocol {
func nativeRequestVideo(_ capturer: OngoingCallVideoCapturer) { func nativeRequestVideo(_ capturer: OngoingCallVideoCapturer) {
} }
func nativeSetRequestedVideoAspect(_ aspect: Float) {
}
func nativeDisableVideo() { func nativeDisableVideo() {
} }
@ -347,10 +348,24 @@ public final class OngoingCallVideoCapturer {
setOnFirstFrameReceived: { [weak view] f in setOnFirstFrameReceived: { [weak view] f in
view?.setOnFirstFrameReceived(f) view?.setOnFirstFrameReceived(f)
}, },
getOrientation: { getOrientation: { [weak view] in
return .rotation0 if let view = view {
return OngoingCallVideoOrientation(view.orientation)
} else {
return .rotation0
}
}, },
setOnOrientationUpdated: { _ in getAspect: { [weak view] in
if let view = view {
return view.aspect
} else {
return 0.0
}
},
setOnOrientationUpdated: { [weak view] f in
view?.setOnOrientationUpdated { value, aspect in
f?(OngoingCallVideoOrientation(value), aspect)
}
}, },
setOnIsMirroredUpdated: { [weak view] f in setOnIsMirroredUpdated: { [weak view] f in
view?.setOnIsMirroredUpdated(f) view?.setOnIsMirroredUpdated(f)
@ -392,6 +407,10 @@ extension OngoingCallThreadLocalContextWebrtc: OngoingCallThreadLocalContextProt
self.requestVideo(capturer.impl) self.requestVideo(capturer.impl)
} }
func nativeSetRequestedVideoAspect(_ aspect: Float) {
self.setRequestedVideoAspect(aspect)
}
func nativeDisableVideo() { func nativeDisableVideo() {
self.disableVideo() self.disableVideo()
} }
@ -456,11 +475,11 @@ private extension OngoingCallVideoOrientation {
case .orientation0: case .orientation0:
self = .rotation0 self = .rotation0
case .orientation90: case .orientation90:
self = .rotation270 self = .rotation90
case .orientation180: case .orientation180:
self = .rotation180 self = .rotation180
case .orientation270: case .orientation270:
self = .rotation90 self = .rotation270
@unknown default: @unknown default:
self = .rotation0 self = .rotation0
} }
@ -471,19 +490,22 @@ public final class OngoingCallContextPresentationCallVideoView {
public let view: UIView public let view: UIView
public let setOnFirstFrameReceived: (((Float) -> Void)?) -> Void public let setOnFirstFrameReceived: (((Float) -> Void)?) -> Void
public let getOrientation: () -> OngoingCallVideoOrientation public let getOrientation: () -> OngoingCallVideoOrientation
public let setOnOrientationUpdated: (((OngoingCallVideoOrientation) -> Void)?) -> Void public let getAspect: () -> CGFloat
public let setOnOrientationUpdated: (((OngoingCallVideoOrientation, CGFloat) -> Void)?) -> Void
public let setOnIsMirroredUpdated: (((Bool) -> Void)?) -> Void public let setOnIsMirroredUpdated: (((Bool) -> Void)?) -> Void
public init( public init(
view: UIView, view: UIView,
setOnFirstFrameReceived: @escaping (((Float) -> Void)?) -> Void, setOnFirstFrameReceived: @escaping (((Float) -> Void)?) -> Void,
getOrientation: @escaping () -> OngoingCallVideoOrientation, getOrientation: @escaping () -> OngoingCallVideoOrientation,
setOnOrientationUpdated: @escaping (((OngoingCallVideoOrientation) -> Void)?) -> Void, getAspect: @escaping () -> CGFloat,
setOnOrientationUpdated: @escaping (((OngoingCallVideoOrientation, CGFloat) -> Void)?) -> Void,
setOnIsMirroredUpdated: @escaping (((Bool) -> Void)?) -> Void setOnIsMirroredUpdated: @escaping (((Bool) -> Void)?) -> Void
) { ) {
self.view = view self.view = view
self.setOnFirstFrameReceived = setOnFirstFrameReceived self.setOnFirstFrameReceived = setOnFirstFrameReceived
self.getOrientation = getOrientation self.getOrientation = getOrientation
self.getAspect = getAspect
self.setOnOrientationUpdated = setOnOrientationUpdated self.setOnOrientationUpdated = setOnOrientationUpdated
self.setOnIsMirroredUpdated = setOnIsMirroredUpdated self.setOnIsMirroredUpdated = setOnIsMirroredUpdated
} }
@ -541,6 +563,9 @@ public final class OngoingCallContext {
return OngoingCallThreadLocalContext.maxLayer() return OngoingCallThreadLocalContext.maxLayer()
} }
private let tempLogFile: TempBoxFile
private let tempStatsLogFile: TempBoxFile
public static func versions(includeExperimental: Bool, includeReference: Bool) -> [(version: String, supportsVideo: Bool)] { public static func versions(includeExperimental: Bool, includeReference: Bool) -> [(version: String, supportsVideo: Bool)] {
var result: [(version: String, supportsVideo: Bool)] = [(OngoingCallThreadLocalContext.version(), false)] var result: [(version: String, supportsVideo: Bool)] = [(OngoingCallThreadLocalContext.version(), false)]
if includeExperimental { if includeExperimental {
@ -551,7 +576,7 @@ public final class OngoingCallContext {
return result return result
} }
public init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, proxyServer: ProxyServerSettings?, initialNetworkType: NetworkType, updatedNetworkType: Signal<NetworkType, NoError>, serializedData: String?, dataSaving: VoiceCallDataSaving, derivedState: VoipDerivedState, key: Data, isOutgoing: Bool, video: OngoingCallVideoCapturer?, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, allowP2P: Bool, audioSessionActive: Signal<Bool, NoError>, logName: String, preferredVideoCodec: String?) { public init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, proxyServer: ProxyServerSettings?, initialNetworkType: NetworkType, updatedNetworkType: Signal<NetworkType, NoError>, serializedData: String?, dataSaving: VoiceCallDataSaving, derivedState: VoipDerivedState, key: Data, isOutgoing: Bool, video: OngoingCallVideoCapturer?, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, allowP2P: Bool, enableStunMarking: Bool, audioSessionActive: Signal<Bool, NoError>, logName: String, preferredVideoCodec: String?) {
let _ = setupLogs let _ = setupLogs
OngoingCallThreadLocalContext.applyServerConfig(serializedData) OngoingCallThreadLocalContext.applyServerConfig(serializedData)
@ -560,6 +585,11 @@ public final class OngoingCallContext {
self.callSessionManager = callSessionManager self.callSessionManager = callSessionManager
self.logPath = logName.isEmpty ? "" : callLogsPath(account: self.account) + "/" + logName + ".log" self.logPath = logName.isEmpty ? "" : callLogsPath(account: self.account) + "/" + logName + ".log"
let logPath = self.logPath let logPath = self.logPath
self.tempLogFile = TempBox.shared.tempFile(fileName: "CallLog.txt")
let tempLogPath = self.tempLogFile.path
self.tempStatsLogFile = TempBox.shared.tempFile(fileName: "CallStats.json")
let tempStatsLogPath = self.tempStatsLogFile.path
let queue = self.queue let queue = self.queue
@ -581,11 +611,6 @@ public final class OngoingCallContext {
} }
} }
let screenSize = UIScreen.main.bounds.size
let portraitSize = CGSize(width: min(screenSize.width, screenSize.height), height: max(screenSize.width, screenSize.height))
let preferredAspectRatio = portraitSize.width / portraitSize.height
let unfilteredConnections = [connections.primary] + connections.alternatives let unfilteredConnections = [connections.primary] + connections.alternatives
var processedConnections: [CallSessionConnection] = [] var processedConnections: [CallSessionConnection] = []
var filteredConnections: [OngoingCallConnectionDescriptionWebrtc] = [] var filteredConnections: [OngoingCallConnectionDescriptionWebrtc] = []
@ -597,9 +622,9 @@ public final class OngoingCallContext {
filteredConnections.append(contentsOf: callConnectionDescriptionsWebrtc(connection)) filteredConnections.append(contentsOf: callConnectionDescriptionsWebrtc(connection))
} }
let context = OngoingCallThreadLocalContextWebrtc(version: version, queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, connections: filteredConnections, maxLayer: maxLayer, allowP2P: allowP2P, logPath: logPath, sendSignalingData: { [weak callSessionManager] data in let context = OngoingCallThreadLocalContextWebrtc(version: version, queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, connections: filteredConnections, maxLayer: maxLayer, allowP2P: allowP2P, enableStunMarking: enableStunMarking, logPath: tempLogPath, statsLogPath: tempStatsLogPath, sendSignalingData: { [weak callSessionManager] data in
callSessionManager?.sendSignalingData(internalId: internalId, data: data) callSessionManager?.sendSignalingData(internalId: internalId, data: data)
}, videoCapturer: video?.impl, preferredAspectRatio: Float(preferredAspectRatio), preferredVideoCodec: preferredVideoCodec) }, videoCapturer: video?.impl, preferredVideoCodec: preferredVideoCodec)
strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context)) strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context))
context.stateChanged = { [weak callSessionManager] state, videoState, remoteVideoState, remoteAudioState, remoteBatteryLevel, _ in context.stateChanged = { [weak callSessionManager] state, videoState, remoteVideoState, remoteAudioState, remoteBatteryLevel, _ in
@ -748,10 +773,15 @@ public final class OngoingCallContext {
public func stop(callId: CallId? = nil, sendDebugLogs: Bool = false, debugLogValue: Promise<String?>) { public func stop(callId: CallId? = nil, sendDebugLogs: Bool = false, debugLogValue: Promise<String?>) {
let account = self.account let account = self.account
let logPath = self.logPath let logPath = self.logPath
var statsLogPath = ""
if !logPath.isEmpty {
statsLogPath = logPath + ".json"
}
let tempLogPath = self.tempLogFile.path
let tempStatsLogPath = self.tempStatsLogFile.path
self.withContextThenDeallocate { context in self.withContextThenDeallocate { context in
context.nativeStop { debugLog, bytesSentWifi, bytesReceivedWifi, bytesSentMobile, bytesReceivedMobile in context.nativeStop { debugLog, bytesSentWifi, bytesReceivedWifi, bytesSentMobile, bytesReceivedMobile in
debugLogValue.set(.single(debugLog))
let delta = NetworkUsageStatsConnectionsEntry( let delta = NetworkUsageStatsConnectionsEntry(
cellular: NetworkUsageStatsDirectionsEntry( cellular: NetworkUsageStatsDirectionsEntry(
incoming: bytesReceivedMobile, incoming: bytesReceivedMobile,
@ -761,17 +791,22 @@ public final class OngoingCallContext {
outgoing: bytesSentWifi)) outgoing: bytesSentWifi))
updateAccountNetworkUsageStats(account: self.account, category: .call, delta: delta) updateAccountNetworkUsageStats(account: self.account, category: .call, delta: delta)
if !logPath.isEmpty, let debugLog = debugLog { if !logPath.isEmpty {
let logsPath = callLogsPath(account: account) let logsPath = callLogsPath(account: account)
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil) let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
if let data = debugLog.data(using: .utf8) { let _ = try? FileManager.default.moveItem(atPath: tempLogPath, toPath: logPath)
let _ = try? data.write(to: URL(fileURLWithPath: logPath))
}
} }
if let callId = callId, let debugLog = debugLog { if !statsLogPath.isEmpty {
let logsPath = callLogsPath(account: account)
let _ = try? FileManager.default.createDirectory(atPath: logsPath, withIntermediateDirectories: true, attributes: nil)
let _ = try? FileManager.default.moveItem(atPath: tempStatsLogPath, toPath: statsLogPath)
}
if let callId = callId, !statsLogPath.isEmpty, let data = try? Data(contentsOf: URL(fileURLWithPath: statsLogPath)), let dataString = String(data: data, encoding: .utf8) {
debugLogValue.set(.single(dataString))
if sendDebugLogs { if sendDebugLogs {
let _ = saveCallDebugLog(network: self.account.network, callId: callId, log: debugLog).start() let _ = saveCallDebugLog(network: self.account.network, callId: callId, log: dataString).start()
} }
} }
} }
@ -800,6 +835,12 @@ public final class OngoingCallContext {
} }
} }
public func setRequestedVideoAspect(_ aspect: Float) {
self.withContext { context in
context.nativeSetRequestedVideoAspect(aspect)
}
}
public func disableVideo() { public func disableVideo() {
self.withContext { context in self.withContext { context in
context.nativeDisableVideo() context.nativeDisableVideo()
@ -837,9 +878,16 @@ public final class OngoingCallContext {
return .rotation0 return .rotation0
} }
}, },
getAspect: { [weak view] in
if let view = view {
return view.aspect
} else {
return 0.0
}
},
setOnOrientationUpdated: { [weak view] f in setOnOrientationUpdated: { [weak view] f in
view?.setOnOrientationUpdated { value in view?.setOnOrientationUpdated { value, aspect in
f?(OngoingCallVideoOrientation(value)) f?(OngoingCallVideoOrientation(value), aspect)
} }
}, },
setOnIsMirroredUpdated: { [weak view] f in setOnIsMirroredUpdated: { [weak view] f in

View File

@ -95,11 +95,12 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
@protocol OngoingCallThreadLocalContextWebrtcVideoView <NSObject> @protocol OngoingCallThreadLocalContextWebrtcVideoView <NSObject>
@property (nonatomic, readonly) OngoingCallVideoOrientationWebrtc orientation; @property (nonatomic, readonly) OngoingCallVideoOrientationWebrtc orientation;
@property (nonatomic, readonly) CGFloat aspect;
- (void)setOnFirstFrameReceived:(void (^ _Nullable)(float))onFirstFrameReceived; - (void)setOnFirstFrameReceived:(void (^ _Nullable)(float))onFirstFrameReceived;
- (void)setOnOrientationUpdated:(void (^ _Nullable)(OngoingCallVideoOrientationWebrtc))onOrientationUpdated; - (void)setOnOrientationUpdated:(void (^ _Nullable)(OngoingCallVideoOrientationWebrtc, CGFloat))onOrientationUpdated;
- (void)setOnIsMirroredUpdated:(void (^ _Nullable)(bool))onIsMirroredUpdated; - (void)setOnIsMirroredUpdated:(void (^ _Nullable)(bool))onIsMirroredUpdated;
#ifdef WEBRTC_MAC #if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS)
- (void)setVideoContentMode:(CALayerContentsGravity _Nonnull )mode; - (void)setVideoContentMode:(CALayerContentsGravity _Nonnull )mode;
#endif #endif
@end @end
@ -125,7 +126,7 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
@property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc, OngoingCallVideoStateWebrtc, OngoingCallRemoteVideoStateWebrtc, OngoingCallRemoteAudioStateWebrtc, OngoingCallRemoteBatteryLevelWebrtc, float); @property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc, OngoingCallVideoStateWebrtc, OngoingCallRemoteVideoStateWebrtc, OngoingCallRemoteAudioStateWebrtc, OngoingCallRemoteBatteryLevelWebrtc, float);
@property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t); @property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t);
- (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing connections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)connections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredAspectRatio:(float)preferredAspectRatio preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec; - (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing connections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)connections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P enableStunMarking:(BOOL)enableStunMarking logPath:(NSString * _Nonnull)logPath statsLogPath:(NSString * _Nonnull)statsLogPath sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec;
- (void)beginTermination; - (void)beginTermination;
- (void)stop:(void (^_Nullable)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile))completion; - (void)stop:(void (^_Nullable)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile))completion;
@ -141,6 +142,7 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
- (void)setNetworkType:(OngoingCallNetworkTypeWebrtc)networkType; - (void)setNetworkType:(OngoingCallNetworkTypeWebrtc)networkType;
- (void)makeIncomingVideoView:(void (^_Nonnull)(UIView<OngoingCallThreadLocalContextWebrtcVideoView> * _Nullable))completion; - (void)makeIncomingVideoView:(void (^_Nonnull)(UIView<OngoingCallThreadLocalContextWebrtcVideoView> * _Nullable))completion;
- (void)requestVideo:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer; - (void)requestVideo:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer;
- (void)setRequestedVideoAspect:(float)aspect;
- (void)disableVideo; - (void)disableVideo;
- (void)addSignalingData:(NSData * _Nonnull)data; - (void)addSignalingData:(NSData * _Nonnull)data;

View File

@ -49,12 +49,14 @@
@protocol OngoingCallThreadLocalContextWebrtcVideoViewImpl <NSObject> @protocol OngoingCallThreadLocalContextWebrtcVideoViewImpl <NSObject>
@property (nonatomic, readwrite) OngoingCallVideoOrientationWebrtc orientation; @property (nonatomic, readwrite) OngoingCallVideoOrientationWebrtc orientation;
@property (nonatomic, readonly) CGFloat aspect;
@end @end
@interface VideoMetalView (VideoViewImpl) <OngoingCallThreadLocalContextWebrtcVideoView, OngoingCallThreadLocalContextWebrtcVideoViewImpl> @interface VideoMetalView (VideoViewImpl) <OngoingCallThreadLocalContextWebrtcVideoView, OngoingCallThreadLocalContextWebrtcVideoViewImpl>
@property (nonatomic, readwrite) OngoingCallVideoOrientationWebrtc orientation; @property (nonatomic, readwrite) OngoingCallVideoOrientationWebrtc orientation;
@property (nonatomic, readonly) CGFloat aspect;
@end @end
@ -64,14 +66,18 @@
return (OngoingCallVideoOrientationWebrtc)self.internalOrientation; return (OngoingCallVideoOrientationWebrtc)self.internalOrientation;
} }
- (CGFloat)aspect {
return self.internalAspect;
}
- (void)setOrientation:(OngoingCallVideoOrientationWebrtc)orientation { - (void)setOrientation:(OngoingCallVideoOrientationWebrtc)orientation {
[self setInternalOrientation:(int)orientation]; [self setInternalOrientation:(int)orientation];
} }
- (void)setOnOrientationUpdated:(void (^ _Nullable)(OngoingCallVideoOrientationWebrtc))onOrientationUpdated { - (void)setOnOrientationUpdated:(void (^ _Nullable)(OngoingCallVideoOrientationWebrtc, CGFloat))onOrientationUpdated {
if (onOrientationUpdated) { if (onOrientationUpdated) {
[self internalSetOnOrientationUpdated:^(int value) { [self internalSetOnOrientationUpdated:^(int value, CGFloat aspect) {
onOrientationUpdated((OngoingCallVideoOrientationWebrtc)value); onOrientationUpdated((OngoingCallVideoOrientationWebrtc)value, aspect);
}]; }];
} else { } else {
[self internalSetOnOrientationUpdated:nil]; [self internalSetOnOrientationUpdated:nil];
@ -93,6 +99,7 @@
@interface GLVideoView (VideoViewImpl) <OngoingCallThreadLocalContextWebrtcVideoView, OngoingCallThreadLocalContextWebrtcVideoViewImpl> @interface GLVideoView (VideoViewImpl) <OngoingCallThreadLocalContextWebrtcVideoView, OngoingCallThreadLocalContextWebrtcVideoViewImpl>
@property (nonatomic, readwrite) OngoingCallVideoOrientationWebrtc orientation; @property (nonatomic, readwrite) OngoingCallVideoOrientationWebrtc orientation;
@property (nonatomic, readonly) CGFloat aspect;
@end @end
@ -102,14 +109,18 @@
return (OngoingCallVideoOrientationWebrtc)self.internalOrientation; return (OngoingCallVideoOrientationWebrtc)self.internalOrientation;
} }
- (CGFloat)aspect {
return self.internalAspect;
}
- (void)setOrientation:(OngoingCallVideoOrientationWebrtc)orientation { - (void)setOrientation:(OngoingCallVideoOrientationWebrtc)orientation {
[self setInternalOrientation:(int)orientation]; [self setInternalOrientation:(int)orientation];
} }
- (void)setOnOrientationUpdated:(void (^ _Nullable)(OngoingCallVideoOrientationWebrtc))onOrientationUpdated { - (void)setOnOrientationUpdated:(void (^ _Nullable)(OngoingCallVideoOrientationWebrtc, CGFloat))onOrientationUpdated {
if (onOrientationUpdated) { if (onOrientationUpdated) {
[self internalSetOnOrientationUpdated:^(int value) { [self internalSetOnOrientationUpdated:^(int value, CGFloat aspect) {
onOrientationUpdated((OngoingCallVideoOrientationWebrtc)value); onOrientationUpdated((OngoingCallVideoOrientationWebrtc)value, aspect);
}]; }];
} else { } else {
[self internalSetOnOrientationUpdated:nil]; [self internalSetOnOrientationUpdated:nil];
@ -207,6 +218,7 @@
NSTimeInterval _callPacketTimeout; NSTimeInterval _callPacketTimeout;
std::unique_ptr<tgcalls::Instance> _tgVoip; std::unique_ptr<tgcalls::Instance> _tgVoip;
bool _didStop;
OngoingCallStateWebrtc _state; OngoingCallStateWebrtc _state;
OngoingCallVideoStateWebrtc _videoState; OngoingCallVideoStateWebrtc _videoState;
@ -224,7 +236,6 @@
void (^_sendSignalingData)(NSData *); void (^_sendSignalingData)(NSData *);
float _remotePreferredAspectRatio; float _remotePreferredAspectRatio;
} }
- (void)controllerStateChanged:(tgcalls::State)state; - (void)controllerStateChanged:(tgcalls::State)state;
@ -312,7 +323,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
} }
} }
- (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing connections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)connections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredAspectRatio:(float)preferredAspectRatio preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec { - (instancetype _Nonnull)initWithVersion:(NSString * _Nonnull)version queue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing connections:(NSArray<OngoingCallConnectionDescriptionWebrtc *> * _Nonnull)connections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P enableStunMarking:(BOOL)enableStunMarking logPath:(NSString * _Nonnull)logPath statsLogPath:(NSString * _Nonnull)statsLogPath sendSignalingData:(void (^)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer preferredVideoCodec:(NSString * _Nullable)preferredVideoCodec {
self = [super init]; self = [super init];
if (self != nil) { if (self != nil) {
_version = version; _version = version;
@ -387,13 +398,14 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
.receiveTimeout = _callPacketTimeout, .receiveTimeout = _callPacketTimeout,
.dataSaving = callControllerDataSavingForType(dataSaving), .dataSaving = callControllerDataSavingForType(dataSaving),
.enableP2P = (bool)allowP2P, .enableP2P = (bool)allowP2P,
.enableStunMarking = (bool)enableStunMarking,
.enableAEC = false, .enableAEC = false,
.enableNS = true, .enableNS = true,
.enableAGC = true, .enableAGC = true,
.enableCallUpgrade = false, .enableCallUpgrade = false,
.logPath = "", //logPath.length == 0 ? "" : std::string(logPath.UTF8String), .logPath = logPath.length == 0 ? "" : std::string(logPath.UTF8String),
.statsLogPath = statsLogPath.length == 0 ? "" : std::string(statsLogPath.UTF8String),
.maxApiLayer = [OngoingCallThreadLocalContextWebrtc maxLayer], .maxApiLayer = [OngoingCallThreadLocalContextWebrtc maxLayer],
.preferredAspectRatio = preferredAspectRatio,
.enableHighBitrateVideo = true, .enableHighBitrateVideo = true,
.preferredVideoCodecs = preferredVideoCodecs, .preferredVideoCodecs = preferredVideoCodecs,
.protocolVersion = [OngoingCallThreadLocalContextWebrtc protocolVersionFromLibraryVersion:version] .protocolVersion = [OngoingCallThreadLocalContextWebrtc protocolVersionFromLibraryVersion:version]
@ -543,13 +555,10 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
- (void)beginTermination { - (void)beginTermination {
} }
- (void)stopWithTerminationResult:(OngoingCallThreadLocalContextWebrtcTerminationResult *)terminationResult completion:(void (^)(NSString *, int64_t, int64_t, int64_t, int64_t))completion { + (void)stopWithTerminationResult:(OngoingCallThreadLocalContextWebrtcTerminationResult *)terminationResult completion:(void (^)(NSString *, int64_t, int64_t, int64_t, int64_t))completion {
_tgVoip.reset();
if (completion) { if (completion) {
if (terminationResult) { if (terminationResult) {
NSString *debugLog = [NSString stringWithUTF8String:terminationResult.finalState.debugLog.c_str()]; NSString *debugLog = [NSString stringWithUTF8String:terminationResult.finalState.debugLog.c_str()];
_lastDerivedState = [[NSData alloc] initWithBytes:terminationResult.finalState.persistentState.value.data() length:terminationResult.finalState.persistentState.value.size()];
if (completion) { if (completion) {
completion(debugLog, terminationResult.finalState.trafficStats.bytesSentWifi, terminationResult.finalState.trafficStats.bytesReceivedWifi, terminationResult.finalState.trafficStats.bytesSentMobile, terminationResult.finalState.trafficStats.bytesReceivedMobile); completion(debugLog, terminationResult.finalState.trafficStats.bytesSentWifi, terminationResult.finalState.trafficStats.bytesReceivedWifi, terminationResult.finalState.trafficStats.bytesSentMobile, terminationResult.finalState.trafficStats.bytesReceivedMobile);
@ -567,24 +576,28 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
return; return;
} }
if (completion == nil) { if (completion == nil) {
_tgVoip->stop([](tgcalls::FinalState finalState) { if (!_didStop) {
}); _tgVoip->stop([](tgcalls::FinalState finalState) {
});
}
_tgVoip.reset(); _tgVoip.reset();
return; return;
} }
__weak OngoingCallThreadLocalContextWebrtc *weakSelf = self; __weak OngoingCallThreadLocalContextWebrtc *weakSelf = self;
id<OngoingCallThreadLocalContextQueueWebrtc> queue = _queue; id<OngoingCallThreadLocalContextQueueWebrtc> queue = _queue;
_didStop = true;
_tgVoip->stop([weakSelf, queue, completion = [completion copy]](tgcalls::FinalState finalState) { _tgVoip->stop([weakSelf, queue, completion = [completion copy]](tgcalls::FinalState finalState) {
[queue dispatch:^{ [queue dispatch:^{
__strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf; __strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf;
if (!strongSelf) { if (strongSelf) {
strongSelf->_tgVoip.reset();
return; return;
} }
OngoingCallThreadLocalContextWebrtcTerminationResult *terminationResult = [[OngoingCallThreadLocalContextWebrtcTerminationResult alloc] initWithFinalState:finalState]; OngoingCallThreadLocalContextWebrtcTerminationResult *terminationResult = [[OngoingCallThreadLocalContextWebrtcTerminationResult alloc] initWithFinalState:finalState];
[strongSelf stopWithTerminationResult:terminationResult completion:completion]; [OngoingCallThreadLocalContextWebrtc stopWithTerminationResult:terminationResult completion:completion];
}]; }];
}); });
} }
@ -694,7 +707,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
if ([VideoMetalView isSupported]) { if ([VideoMetalView isSupported]) {
VideoMetalView *remoteRenderer = [[VideoMetalView alloc] initWithFrame:CGRectZero]; VideoMetalView *remoteRenderer = [[VideoMetalView alloc] initWithFrame:CGRectZero];
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
remoteRenderer.videoContentMode = UIViewContentModeScaleAspectFill; remoteRenderer.videoContentMode = UIViewContentModeScaleToFill;
#else #else
remoteRenderer.videoContentMode = UIViewContentModeScaleAspect; remoteRenderer.videoContentMode = UIViewContentModeScaleAspect;
#endif #endif
@ -737,6 +750,12 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL;
} }
} }
- (void)setRequestedVideoAspect:(float)aspect {
if (_tgVoip) {
_tgVoip->setRequestedVideoAspect(aspect);
}
}
- (void)disableVideo { - (void)disableVideo {
if (_tgVoip) { if (_tgVoip) {
_videoCapturer = nil; _videoCapturer = nil;

@ -1 +1 @@
Subproject commit d8b106c94e5a5e20020ad862d835e921d4f102d9 Subproject commit 34c37a8e332ab1cd645bfd90af9a2a4900866e3e

View File

@ -1,3 +1,5 @@
use_gn_build = True
webrtc_libs = [ webrtc_libs = [
"libwebrtc.a", "libwebrtc.a",
] ]
@ -70,7 +72,7 @@ genrule(
) )
cc_library( cc_library(
name = "webrtc_lib_gn", name = "webrtc_lib_gn" if not use_gn_build else "webrtc_lib",
srcs = [":" + x for x in webrtc_libs], srcs = [":" + x for x in webrtc_libs],
visibility = ["//visibility:public"], visibility = ["//visibility:public"],
) )
@ -2182,7 +2184,7 @@ objc_library(
) )
objc_library( objc_library(
name = "webrtc_lib", name = "webrtc_lib_custom" if use_gn_build else "webrtc_lib",
enable_modules = True, enable_modules = True,
module_name = "webrtc", module_name = "webrtc",
srcs = combined_sources, srcs = combined_sources,