mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Call improvements
This commit is contained in:
parent
a38e2e41ed
commit
d33e03b64c
@ -25,6 +25,7 @@ swift_library(
|
|||||||
"//submodules/GZip:GZip",
|
"//submodules/GZip:GZip",
|
||||||
"//third-party/ZipArchive:ZipArchive",
|
"//third-party/ZipArchive:ZipArchive",
|
||||||
"//submodules/InAppPurchaseManager:InAppPurchaseManager",
|
"//submodules/InAppPurchaseManager:InAppPurchaseManager",
|
||||||
|
"//submodules/TelegramVoip",
|
||||||
],
|
],
|
||||||
visibility = [
|
visibility = [
|
||||||
"//visibility:public",
|
"//visibility:public",
|
||||||
|
@ -16,6 +16,7 @@ import AppBundle
|
|||||||
import ZipArchive
|
import ZipArchive
|
||||||
import WebKit
|
import WebKit
|
||||||
import InAppPurchaseManager
|
import InAppPurchaseManager
|
||||||
|
import TelegramVoip
|
||||||
|
|
||||||
@objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate {
|
@objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate {
|
||||||
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||||
@ -104,8 +105,8 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
case disableReloginTokens(Bool)
|
case disableReloginTokens(Bool)
|
||||||
case disableCallV2(Bool)
|
case disableCallV2(Bool)
|
||||||
case experimentalCallMute(Bool)
|
case experimentalCallMute(Bool)
|
||||||
case liveStreamV2(Bool)
|
case autoBenchmarkReflectors(Bool)
|
||||||
case dynamicStreaming(Bool)
|
case benchmarkReflectors
|
||||||
case enableLocalTranslation(Bool)
|
case enableLocalTranslation(Bool)
|
||||||
case preferredVideoCodec(Int, String, String?, Bool)
|
case preferredVideoCodec(Int, String, String?, Bool)
|
||||||
case disableVideoAspectScaling(Bool)
|
case disableVideoAspectScaling(Bool)
|
||||||
@ -131,7 +132,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return DebugControllerSection.web.rawValue
|
return DebugControllerSection.web.rawValue
|
||||||
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
|
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
|
||||||
return DebugControllerSection.experiments.rawValue
|
return DebugControllerSection.experiments.rawValue
|
||||||
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .disableCallV2, .experimentalCallMute, .liveStreamV2, .dynamicStreaming, .enableLocalTranslation:
|
case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .storiesExperiment, .storiesJpegExperiment, .playlistPlayback, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .disableCallV2, .experimentalCallMute, .autoBenchmarkReflectors, .benchmarkReflectors, .enableLocalTranslation:
|
||||||
return DebugControllerSection.experiments.rawValue
|
return DebugControllerSection.experiments.rawValue
|
||||||
case .logTranslationRecognition, .resetTranslationStates:
|
case .logTranslationRecognition, .resetTranslationStates:
|
||||||
return DebugControllerSection.translation.rawValue
|
return DebugControllerSection.translation.rawValue
|
||||||
@ -248,9 +249,9 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
return 51
|
return 51
|
||||||
case .experimentalCallMute:
|
case .experimentalCallMute:
|
||||||
return 52
|
return 52
|
||||||
case .liveStreamV2:
|
case .autoBenchmarkReflectors:
|
||||||
return 53
|
return 53
|
||||||
case .dynamicStreaming:
|
case .benchmarkReflectors:
|
||||||
return 54
|
return 54
|
||||||
case .enableLocalTranslation:
|
case .enableLocalTranslation:
|
||||||
return 55
|
return 55
|
||||||
@ -1344,25 +1345,70 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
|||||||
})
|
})
|
||||||
}).start()
|
}).start()
|
||||||
})
|
})
|
||||||
case let .liveStreamV2(value):
|
case let .autoBenchmarkReflectors(value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: "Live Stream V2", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, title: "Auto-Benchmark Reflectors [Restart]", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
|
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
|
||||||
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
|
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
|
||||||
settings.liveStreamV2 = value
|
settings.autoBenchmarkReflectors = value
|
||||||
return PreferencesEntry(settings)
|
return PreferencesEntry(settings)
|
||||||
})
|
})
|
||||||
}).start()
|
}).start()
|
||||||
})
|
})
|
||||||
case let .dynamicStreaming(value):
|
case .benchmarkReflectors:
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: "Dynamic Streaming", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListActionItem(presentationData: presentationData, title: "Benchmark Reflectors", kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: {
|
||||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
guard let context = arguments.context else {
|
||||||
transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in
|
return
|
||||||
var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings
|
}
|
||||||
settings.dynamicStreaming = value
|
|
||||||
return PreferencesEntry(settings)
|
var signal: Signal<ReflectorBenchmark.Results, NoError> = Signal { subscriber in
|
||||||
|
var reflectorBenchmark: ReflectorBenchmark? = ReflectorBenchmark(address: "91.108.13.35", port: 599)
|
||||||
|
reflectorBenchmark?.start(completion: { results in
|
||||||
|
subscriber.putNext(results)
|
||||||
|
subscriber.putCompletion()
|
||||||
})
|
})
|
||||||
}).start()
|
|
||||||
|
return ActionDisposable {
|
||||||
|
reflectorBenchmark = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> runOn(.mainQueue())
|
||||||
|
|
||||||
|
var cancelImpl: (() -> Void)?
|
||||||
|
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||||
|
let progressSignal = Signal<Never, NoError> { subscriber in
|
||||||
|
let controller = OverlayStatusController(theme: presentationData.theme, type: .loading(cancelled: {
|
||||||
|
cancelImpl?()
|
||||||
|
}))
|
||||||
|
arguments.presentController(controller, nil)
|
||||||
|
return ActionDisposable { [weak controller] in
|
||||||
|
Queue.mainQueue().async() {
|
||||||
|
controller?.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> runOn(Queue.mainQueue())
|
||||||
|
|> delay(0.15, queue: Queue.mainQueue())
|
||||||
|
let progressDisposable = progressSignal.start()
|
||||||
|
|
||||||
|
let reindexDisposable = MetaDisposable()
|
||||||
|
|
||||||
|
signal = signal
|
||||||
|
|> afterDisposed {
|
||||||
|
Queue.mainQueue().async {
|
||||||
|
progressDisposable.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cancelImpl = {
|
||||||
|
reindexDisposable.set(nil)
|
||||||
|
}
|
||||||
|
reindexDisposable.set((signal
|
||||||
|
|> deliverOnMainQueue).start(next: { results in
|
||||||
|
if let context = arguments.context {
|
||||||
|
let controller = textAlertController(context: context, title: nil, text: "Bandwidth: \(results.bandwidthBytesPerSecond * 8 / 1024) kbit/s (expected \(results.expectedBandwidthBytesPerSecond * 8 / 1024) kbit/s)\nAvg latency: \(Int(results.averageDelay * 1000.0)) ms", actions: [TextAlertAction(type: .genericAction, title: "OK", action: {})])
|
||||||
|
arguments.presentController(controller, nil)
|
||||||
|
}
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
case let .enableLocalTranslation(value):
|
case let .enableLocalTranslation(value):
|
||||||
return ItemListSwitchItem(presentationData: presentationData, title: "Local Translation", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
return ItemListSwitchItem(presentationData: presentationData, title: "Local Translation", value: value, sectionId: self.section, style: .blocks, updated: { value in
|
||||||
@ -1530,8 +1576,14 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
|
|||||||
entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction))
|
entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction))
|
||||||
entries.append(.disableCallV2(experimentalSettings.disableCallV2))
|
entries.append(.disableCallV2(experimentalSettings.disableCallV2))
|
||||||
entries.append(.experimentalCallMute(experimentalSettings.experimentalCallMute))
|
entries.append(.experimentalCallMute(experimentalSettings.experimentalCallMute))
|
||||||
entries.append(.liveStreamV2(experimentalSettings.liveStreamV2))
|
|
||||||
entries.append(.dynamicStreaming(experimentalSettings.dynamicStreaming))
|
var defaultAutoBenchmarkReflectors = false
|
||||||
|
if case .internal = sharedContext.applicationBindings.appBuildType {
|
||||||
|
defaultAutoBenchmarkReflectors = true
|
||||||
|
}
|
||||||
|
entries.append(.autoBenchmarkReflectors(experimentalSettings.autoBenchmarkReflectors ?? defaultAutoBenchmarkReflectors))
|
||||||
|
|
||||||
|
entries.append(.benchmarkReflectors)
|
||||||
entries.append(.enableLocalTranslation(experimentalSettings.enableLocalTranslation))
|
entries.append(.enableLocalTranslation(experimentalSettings.enableLocalTranslation))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,6 +73,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
private var audioLevelDisposable: Disposable?
|
private var audioLevelDisposable: Disposable?
|
||||||
private var audioOutputCheckTimer: Foundation.Timer?
|
private var audioOutputCheckTimer: Foundation.Timer?
|
||||||
|
|
||||||
|
private var applicationInForegroundDisposable: Disposable?
|
||||||
|
|
||||||
private var localVideo: AdaptedCallVideoSource?
|
private var localVideo: AdaptedCallVideoSource?
|
||||||
private var remoteVideo: AdaptedCallVideoSource?
|
private var remoteVideo: AdaptedCallVideoSource?
|
||||||
|
|
||||||
@ -212,6 +214,37 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
self.applicationInForegroundDisposable = (self.sharedContext.applicationBindings.applicationInForeground
|
||||||
|
|> filter { $0 }
|
||||||
|
|> deliverOnMainQueue).startStrict(next: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.callScreen.isPictureInPictureRequested {
|
||||||
|
Queue.mainQueue().after(0.5, { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.callScreen.isPictureInPictureRequested && !self.callScreen.restoreFromPictureInPictureIfPossible() {
|
||||||
|
Queue.mainQueue().after(0.2, { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.callScreen.isPictureInPictureRequested && !self.callScreen.restoreFromPictureInPictureIfPossible() {
|
||||||
|
Queue.mainQueue().after(0.3, { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.callScreen.isPictureInPictureRequested && !self.callScreen.restoreFromPictureInPictureIfPossible() {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -220,6 +253,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
self.audioLevelDisposable?.dispose()
|
self.audioLevelDisposable?.dispose()
|
||||||
self.audioOutputCheckTimer?.invalidate()
|
self.audioOutputCheckTimer?.invalidate()
|
||||||
self.signalQualityTimer?.invalidate()
|
self.signalQualityTimer?.invalidate()
|
||||||
|
self.applicationInForegroundDisposable?.dispose()
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateAudioOutputs(availableOutputs: [AudioSessionOutput], currentOutput: AudioSessionOutput?) {
|
func updateAudioOutputs(availableOutputs: [AudioSessionOutput], currentOutput: AudioSessionOutput?) {
|
||||||
@ -623,6 +657,8 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
self.containerView.layer.allowsGroupOpacity = false
|
self.containerView.layer.allowsGroupOpacity = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let _ = self.callScreen.restoreFromPictureInPictureIfPossible()
|
||||||
}
|
}
|
||||||
|
|
||||||
func animateOut(completion: @escaping () -> Void) {
|
func animateOut(completion: @escaping () -> Void) {
|
||||||
|
@ -104,6 +104,24 @@ final class PrivateCallPictureInPictureView: UIView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override var frame: CGRect {
|
||||||
|
didSet {
|
||||||
|
if !self.bounds.isEmpty {
|
||||||
|
/*if let testView = self.viewWithTag(123) {
|
||||||
|
testView.frame = self.bounds.insetBy(dx: 10.0, dy: 10.0)
|
||||||
|
} else {
|
||||||
|
let testView = AnimationTrackingView(frame: self.bounds.insetBy(dx: 10.0, dy: 10.0))
|
||||||
|
testView.layer.borderColor = UIColor.red.cgColor
|
||||||
|
testView.layer.borderWidth = 2.0
|
||||||
|
testView.tag = 123
|
||||||
|
self.addSubview(testView)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
self.updateLayout(size: self.bounds.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override static var layerClass: AnyClass {
|
override static var layerClass: AnyClass {
|
||||||
return AVSampleBufferDisplayLayer.self
|
return AVSampleBufferDisplayLayer.self
|
||||||
}
|
}
|
||||||
@ -146,50 +164,11 @@ final class PrivateCallPictureInPictureView: UIView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func layoutSubviews() {
|
func updateLayout(size: CGSize) {
|
||||||
super.layoutSubviews()
|
|
||||||
|
|
||||||
let size = self.bounds.size
|
|
||||||
if size.width.isZero || size.height.isZero {
|
if size.width.isZero || size.height.isZero {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var animationTemplate: CAAnimation?
|
|
||||||
self.animationTrackingView.onAnimation = { animation in
|
|
||||||
animationTemplate = animation
|
|
||||||
}
|
|
||||||
self.animationTrackingView.frame = CGRect(origin: CGPoint(), size: size)
|
|
||||||
self.animationTrackingView.onAnimation = nil
|
|
||||||
|
|
||||||
let _ = animationTemplate
|
|
||||||
|
|
||||||
let animationDuration = CATransaction.animationDuration()
|
|
||||||
let timingFunction = CATransaction.animationTimingFunction()
|
|
||||||
|
|
||||||
let mappedTransition: ComponentTransition
|
|
||||||
if self.sampleBufferView.bounds.isEmpty {
|
|
||||||
mappedTransition = .immediate
|
|
||||||
} else if animationDuration > 0.0 && !CATransaction.disableActions() {
|
|
||||||
let mappedCurve: ComponentTransition.Animation.Curve
|
|
||||||
if let timingFunction {
|
|
||||||
var controlPoint0: [Float] = [0.0, 0.0]
|
|
||||||
var controlPoint1: [Float] = [0.0, 0.0]
|
|
||||||
timingFunction.getControlPoint(at: 1, values: &controlPoint0)
|
|
||||||
timingFunction.getControlPoint(at: 2, values: &controlPoint1)
|
|
||||||
mappedCurve = .custom(controlPoint0[0], controlPoint0[1], controlPoint1[0], controlPoint1[1])
|
|
||||||
} else if animationDuration >= 0.5 {
|
|
||||||
mappedCurve = .spring
|
|
||||||
} else {
|
|
||||||
mappedCurve = .easeInOut
|
|
||||||
}
|
|
||||||
mappedTransition = ComponentTransition(animation: .curve(
|
|
||||||
duration: animationDuration,
|
|
||||||
curve: mappedCurve
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
mappedTransition = .immediate
|
|
||||||
}
|
|
||||||
|
|
||||||
if let videoMetrics = self.videoMetrics {
|
if let videoMetrics = self.videoMetrics {
|
||||||
let resolvedRotationAngle = resolveCallVideoRotationAngle(angle: videoMetrics.rotationAngle, followsDeviceOrientation: videoMetrics.followsDeviceOrientation, interfaceOrientation: UIApplication.shared.statusBarOrientation)
|
let resolvedRotationAngle = resolveCallVideoRotationAngle(angle: videoMetrics.rotationAngle, followsDeviceOrientation: videoMetrics.followsDeviceOrientation, interfaceOrientation: UIApplication.shared.statusBarOrientation)
|
||||||
|
|
||||||
@ -223,26 +202,34 @@ final class PrivateCallPictureInPictureView: UIView {
|
|||||||
|
|
||||||
if let sublayers = self.sampleBufferView.layer.sublayers {
|
if let sublayers = self.sampleBufferView.layer.sublayers {
|
||||||
if sublayers.count > 1, !sublayers[0].bounds.isEmpty {
|
if sublayers.count > 1, !sublayers[0].bounds.isEmpty {
|
||||||
sublayers[0].position = CGPoint(x: videoFrame.width * 0.5, y: videoFrame.height * 0.5)
|
|
||||||
sublayers[0].bounds = CGRect(origin: CGPoint(), size: videoFrame.size)
|
sublayers[0].bounds = CGRect(origin: CGPoint(), size: videoFrame.size)
|
||||||
|
sublayers[0].position = CGPoint(x: videoFrame.width * 0.5, y: videoFrame.height * 0.5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !mappedTransition.animation.isImmediate {
|
apply()
|
||||||
apply()
|
|
||||||
} else {
|
|
||||||
UIView.performWithoutAnimation {
|
|
||||||
apply()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(iOS 15.0, *)
|
@available(iOS 15.0, *)
|
||||||
final class PrivateCallPictureInPictureController: AVPictureInPictureVideoCallViewController {
|
final class PrivateCallPictureInPictureController: AVPictureInPictureVideoCallViewController {
|
||||||
|
var pipView: PrivateCallPictureInPictureView?
|
||||||
|
|
||||||
|
override func viewDidLoad() {
|
||||||
|
super.viewDidLoad()
|
||||||
|
|
||||||
|
if let pipView = self.pipView {
|
||||||
|
pipView.updateLayout(size: self.view.bounds.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
|
||||||
super.viewWillTransition(to: size, with: coordinator)
|
super.viewWillTransition(to: size, with: coordinator)
|
||||||
|
|
||||||
|
coordinator.animate(alongsideTransition: { context in
|
||||||
|
self.pipView?.frame = CGRect(origin: CGPoint(), size: size)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -204,6 +204,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
private var isEmojiKeyExpanded: Bool = false
|
private var isEmojiKeyExpanded: Bool = false
|
||||||
private var areControlsHidden: Bool = false
|
private var areControlsHidden: Bool = false
|
||||||
private var swapLocalAndRemoteVideo: Bool = false
|
private var swapLocalAndRemoteVideo: Bool = false
|
||||||
|
public private(set) var isPictureInPictureRequested: Bool = false
|
||||||
private var isPictureInPictureActive: Bool = false
|
private var isPictureInPictureActive: Bool = false
|
||||||
|
|
||||||
private var hideEmojiTooltipTimer: Foundation.Timer?
|
private var hideEmojiTooltipTimer: Foundation.Timer?
|
||||||
@ -324,15 +325,14 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
self.closeAction?()
|
self.closeAction?()
|
||||||
}
|
}
|
||||||
|
|
||||||
if !"".isEmpty {
|
if #available(iOS 16.0, *) {
|
||||||
if #available(iOS 16.0, *) {
|
let pipVideoCallViewController = PrivateCallPictureInPictureController()
|
||||||
let pipVideoCallViewController = AVPictureInPictureVideoCallViewController()
|
pipVideoCallViewController.pipView = self.pipView
|
||||||
pipVideoCallViewController.view.addSubview(self.pipView)
|
pipVideoCallViewController.view.addSubview(self.pipView)
|
||||||
self.pipView.frame = pipVideoCallViewController.view.bounds
|
self.pipView.frame = pipVideoCallViewController.view.bounds
|
||||||
self.pipView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
self.pipView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||||
self.pipView.translatesAutoresizingMaskIntoConstraints = true
|
self.pipView.translatesAutoresizingMaskIntoConstraints = true
|
||||||
self.pipVideoCallViewController = pipVideoCallViewController
|
self.pipVideoCallViewController = pipVideoCallViewController
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let blurFilter = makeBlurFilter() {
|
if let blurFilter = makeBlurFilter() {
|
||||||
@ -366,13 +366,19 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
|
|
||||||
public func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
public func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||||
self.isPictureInPictureActive = true
|
self.isPictureInPictureActive = true
|
||||||
|
self.isPictureInPictureRequested = true
|
||||||
if !self.isUpdating {
|
if !self.isUpdating {
|
||||||
self.update(transition: .easeInOut(duration: 0.2))
|
self.update(transition: .easeInOut(duration: 0.2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||||
|
self.isPictureInPictureRequested = false
|
||||||
|
}
|
||||||
|
|
||||||
public func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
public func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) {
|
||||||
self.isPictureInPictureActive = false
|
self.isPictureInPictureActive = false
|
||||||
|
self.isPictureInPictureRequested = false
|
||||||
if !self.isUpdating {
|
if !self.isUpdating {
|
||||||
let wereControlsHidden = self.areControlsHidden
|
let wereControlsHidden = self.areControlsHidden
|
||||||
self.areControlsHidden = true
|
self.areControlsHidden = true
|
||||||
@ -387,6 +393,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
}
|
}
|
||||||
|
|
||||||
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
|
public func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
|
||||||
|
self.isPictureInPictureRequested = false
|
||||||
if self.activeLocalVideoSource != nil || self.activeRemoteVideoSource != nil {
|
if self.activeLocalVideoSource != nil || self.activeRemoteVideoSource != nil {
|
||||||
if let restoreUIForPictureInPicture = self.restoreUIForPictureInPicture {
|
if let restoreUIForPictureInPicture = self.restoreUIForPictureInPicture {
|
||||||
restoreUIForPictureInPicture(completionHandler)
|
restoreUIForPictureInPicture(completionHandler)
|
||||||
@ -472,6 +479,15 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func restoreFromPictureInPictureIfPossible() -> Bool {
|
||||||
|
if let pipController = self.pipController, pipController.isPictureInPictureActive {
|
||||||
|
pipController.stopPictureInPicture()
|
||||||
|
return !self.isPictureInPictureRequested
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public func update(size: CGSize, insets: UIEdgeInsets, interfaceOrientation: UIInterfaceOrientation, screenCornerRadius: CGFloat, state: State, transition: ComponentTransition) {
|
public func update(size: CGSize, insets: UIEdgeInsets, interfaceOrientation: UIInterfaceOrientation, screenCornerRadius: CGFloat, state: State, transition: ComponentTransition) {
|
||||||
let params = Params(size: size, insets: insets, interfaceOrientation: interfaceOrientation, screenCornerRadius: screenCornerRadius, state: state)
|
let params = Params(size: size, insets: insets, interfaceOrientation: interfaceOrientation, screenCornerRadius: screenCornerRadius, state: state)
|
||||||
if self.params == params {
|
if self.params == params {
|
||||||
|
@ -1552,6 +1552,48 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
|
|
||||||
//self.addBackgroundDownloadTask()
|
//self.addBackgroundDownloadTask()
|
||||||
|
|
||||||
|
let reflectorBenchmarkDisposable = MetaDisposable()
|
||||||
|
let runReflectorBenchmarkDisposable = MetaDisposable()
|
||||||
|
let _ = (self.context.get()
|
||||||
|
|> deliverOnMainQueue).startStandalone(next: { context in
|
||||||
|
reflectorBenchmarkDisposable.set(nil)
|
||||||
|
runReflectorBenchmarkDisposable.set(nil)
|
||||||
|
|
||||||
|
guard let context = context?.context else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var defaultAutoBenchmarkReflectors = false
|
||||||
|
if case .internal = context.sharedContext.applicationBindings.appBuildType {
|
||||||
|
defaultAutoBenchmarkReflectors = true
|
||||||
|
}
|
||||||
|
if context.sharedContext.immediateExperimentalUISettings.autoBenchmarkReflectors ?? defaultAutoBenchmarkReflectors {
|
||||||
|
reflectorBenchmarkDisposable.set((context.sharedContext.applicationBindings.applicationInForeground
|
||||||
|
|> distinctUntilChanged
|
||||||
|
|> deliverOnMainQueue).startStrict(next: { value in
|
||||||
|
if value {
|
||||||
|
let signal: Signal<ReflectorBenchmark.Results, NoError> = Signal { subscriber in
|
||||||
|
var reflectorBenchmark: ReflectorBenchmark? = ReflectorBenchmark(address: "91.108.13.35", port: 599)
|
||||||
|
reflectorBenchmark?.start(completion: { results in
|
||||||
|
subscriber.putNext(results)
|
||||||
|
subscriber.putCompletion()
|
||||||
|
})
|
||||||
|
|
||||||
|
return ActionDisposable {
|
||||||
|
reflectorBenchmark = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> runOn(.mainQueue())
|
||||||
|
|> delay(Double.random(in: 1.0 ..< 5.0), queue: Queue.mainQueue())
|
||||||
|
runReflectorBenchmarkDisposable.set(signal.startStrict(next: { results in
|
||||||
|
print("Reflector banchmark:\nBandwidth: \(results.bandwidthBytesPerSecond * 8 / 1024) kbit/s (expected \(results.expectedBandwidthBytesPerSecond * 8 / 1024) kbit/s)\nAvg latency: \(Int(results.averageDelay * 1000.0)) ms")
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
runReflectorBenchmarkDisposable.set(nil)
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,6 +61,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
public var liveStreamV2: Bool
|
public var liveStreamV2: Bool
|
||||||
public var dynamicStreaming: Bool
|
public var dynamicStreaming: Bool
|
||||||
public var enableLocalTranslation: Bool
|
public var enableLocalTranslation: Bool
|
||||||
|
public var autoBenchmarkReflectors: Bool?
|
||||||
|
|
||||||
public static var defaultSettings: ExperimentalUISettings {
|
public static var defaultSettings: ExperimentalUISettings {
|
||||||
return ExperimentalUISettings(
|
return ExperimentalUISettings(
|
||||||
@ -99,7 +100,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
disableReloginTokens: false,
|
disableReloginTokens: false,
|
||||||
liveStreamV2: false,
|
liveStreamV2: false,
|
||||||
dynamicStreaming: false,
|
dynamicStreaming: false,
|
||||||
enableLocalTranslation: false
|
enableLocalTranslation: false,
|
||||||
|
autoBenchmarkReflectors: nil
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +141,8 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
disableReloginTokens: Bool,
|
disableReloginTokens: Bool,
|
||||||
liveStreamV2: Bool,
|
liveStreamV2: Bool,
|
||||||
dynamicStreaming: Bool,
|
dynamicStreaming: Bool,
|
||||||
enableLocalTranslation: Bool
|
enableLocalTranslation: Bool,
|
||||||
|
autoBenchmarkReflectors: Bool?
|
||||||
) {
|
) {
|
||||||
self.keepChatNavigationStack = keepChatNavigationStack
|
self.keepChatNavigationStack = keepChatNavigationStack
|
||||||
self.skipReadHistory = skipReadHistory
|
self.skipReadHistory = skipReadHistory
|
||||||
@ -177,6 +180,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
self.liveStreamV2 = liveStreamV2
|
self.liveStreamV2 = liveStreamV2
|
||||||
self.dynamicStreaming = dynamicStreaming
|
self.dynamicStreaming = dynamicStreaming
|
||||||
self.enableLocalTranslation = enableLocalTranslation
|
self.enableLocalTranslation = enableLocalTranslation
|
||||||
|
self.autoBenchmarkReflectors = autoBenchmarkReflectors
|
||||||
}
|
}
|
||||||
|
|
||||||
public init(from decoder: Decoder) throws {
|
public init(from decoder: Decoder) throws {
|
||||||
@ -218,6 +222,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
self.liveStreamV2 = try container.decodeIfPresent(Bool.self, forKey: "liveStreamV2") ?? false
|
self.liveStreamV2 = try container.decodeIfPresent(Bool.self, forKey: "liveStreamV2") ?? false
|
||||||
self.dynamicStreaming = try container.decodeIfPresent(Bool.self, forKey: "dynamicStreaming_v2") ?? false
|
self.dynamicStreaming = try container.decodeIfPresent(Bool.self, forKey: "dynamicStreaming_v2") ?? false
|
||||||
self.enableLocalTranslation = try container.decodeIfPresent(Bool.self, forKey: "enableLocalTranslation") ?? false
|
self.enableLocalTranslation = try container.decodeIfPresent(Bool.self, forKey: "enableLocalTranslation") ?? false
|
||||||
|
self.autoBenchmarkReflectors = try container.decodeIfPresent(Bool.self, forKey: "autoBenchmarkReflectors")
|
||||||
}
|
}
|
||||||
|
|
||||||
public func encode(to encoder: Encoder) throws {
|
public func encode(to encoder: Encoder) throws {
|
||||||
@ -259,6 +264,7 @@ public struct ExperimentalUISettings: Codable, Equatable {
|
|||||||
try container.encode(self.liveStreamV2, forKey: "liveStreamV2")
|
try container.encode(self.liveStreamV2, forKey: "liveStreamV2")
|
||||||
try container.encode(self.dynamicStreaming, forKey: "dynamicStreaming")
|
try container.encode(self.dynamicStreaming, forKey: "dynamicStreaming")
|
||||||
try container.encode(self.enableLocalTranslation, forKey: "enableLocalTranslation")
|
try container.encode(self.enableLocalTranslation, forKey: "enableLocalTranslation")
|
||||||
|
try container.encodeIfPresent(self.autoBenchmarkReflectors, forKey: "autoBenchmarkReflectors")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
423
submodules/TelegramVoip/Sources/ReflectorBenchmark.swift
Normal file
423
submodules/TelegramVoip/Sources/ReflectorBenchmark.swift
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
import Foundation
|
||||||
|
import SwiftSignalKit
|
||||||
|
import Network
|
||||||
|
|
||||||
|
public final class ReflectorBenchmark {
|
||||||
|
public struct Results {
|
||||||
|
public let bandwidthBytesPerSecond: Int
|
||||||
|
public let expectedBandwidthBytesPerSecond: Int
|
||||||
|
public let averageDelay: Double
|
||||||
|
|
||||||
|
public init(bandwidthBytesPerSecond: Int, expectedBandwidthBytesPerSecond: Int, averageDelay: Double) {
|
||||||
|
self.bandwidthBytesPerSecond = bandwidthBytesPerSecond
|
||||||
|
self.expectedBandwidthBytesPerSecond = expectedBandwidthBytesPerSecond
|
||||||
|
self.averageDelay = averageDelay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class Impl {
|
||||||
|
let queue: Queue
|
||||||
|
let address: String
|
||||||
|
let port: Int
|
||||||
|
|
||||||
|
let incomingTag: Data
|
||||||
|
let outgoingTag: Data
|
||||||
|
let outgoingRandomTag: Data
|
||||||
|
|
||||||
|
let targetBandwidthBytesPerSecond: Int
|
||||||
|
let sendPacketInterval: Double
|
||||||
|
let maxPacketCount: Int
|
||||||
|
|
||||||
|
var outgoingConnection: NWConnection?
|
||||||
|
var incomingConnection: NWConnection?
|
||||||
|
|
||||||
|
var completion: ((Results) -> Void)?
|
||||||
|
|
||||||
|
var incomingPingSendTimestamp: Double?
|
||||||
|
var didReceiveIncomingPing: Bool = false
|
||||||
|
var outgoingPingSendTimestamp: Double?
|
||||||
|
var didReceiveOutgoingPing: Bool = false
|
||||||
|
|
||||||
|
var sentPacketCount: Int = 0
|
||||||
|
var receivedPacketCount: Int = 0
|
||||||
|
var firstReceiveTimestamp: Double?
|
||||||
|
var lastReceiveTimestamp: Double?
|
||||||
|
var unconfirmedPacketSendTimestamp: [Data: Double] = [:]
|
||||||
|
var packetSizeAndTimeToReceive: [(Int, Double)] = []
|
||||||
|
|
||||||
|
var pingTimer: SwiftSignalKit.Timer?
|
||||||
|
var sendPacketTimer: SwiftSignalKit.Timer?
|
||||||
|
var bandwidthTimer: SwiftSignalKit.Timer?
|
||||||
|
|
||||||
|
init(queue: Queue, address: String, port: Int) {
|
||||||
|
self.queue = queue
|
||||||
|
self.address = address
|
||||||
|
self.port = port
|
||||||
|
|
||||||
|
self.targetBandwidthBytesPerSecond = 700 * 1024 / 8
|
||||||
|
self.sendPacketInterval = 1.0 / 30.0
|
||||||
|
self.maxPacketCount = Int(5.0 / self.sendPacketInterval)
|
||||||
|
|
||||||
|
var incomingTag = Data(count: 16)
|
||||||
|
var outgoingTag = Data(count: 16)
|
||||||
|
|
||||||
|
incomingTag.withUnsafeMutableBytes { incomingBuffer -> Void in
|
||||||
|
outgoingTag.withUnsafeMutableBytes { outgoingBuffer -> Void in
|
||||||
|
let incoming = incomingBuffer.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||||
|
let outgoing = outgoingBuffer.baseAddress!.assumingMemoryBound(to: UInt8.self)
|
||||||
|
|
||||||
|
arc4random_buf(incoming, incomingBuffer.count)
|
||||||
|
memcpy(outgoing, incoming, incomingBuffer.count)
|
||||||
|
|
||||||
|
incoming[0] = 0
|
||||||
|
outgoing[0] = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var outgoingRandomTag = Data(count: 4)
|
||||||
|
outgoingRandomTag.withUnsafeMutableBytes { buffer -> Void in
|
||||||
|
arc4random_buf(buffer.baseAddress!, buffer.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.incomingTag = incomingTag
|
||||||
|
self.outgoingTag = outgoingTag
|
||||||
|
self.outgoingRandomTag = outgoingRandomTag
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
self.incomingConnection?.cancel()
|
||||||
|
self.outgoingConnection?.cancel()
|
||||||
|
self.pingTimer?.invalidate()
|
||||||
|
self.sendPacketTimer?.invalidate()
|
||||||
|
self.bandwidthTimer?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func start(completion: @escaping (Results) -> Void) {
|
||||||
|
self.completion = completion
|
||||||
|
|
||||||
|
let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host(self.address), port: NWEndpoint.Port(integerLiteral: UInt16(self.port)))
|
||||||
|
let incomingConnection = NWConnection(to: endpoint, using: .udp)
|
||||||
|
self.incomingConnection = incomingConnection
|
||||||
|
|
||||||
|
let outgoingConnection = NWConnection(to: endpoint, using: .udp)
|
||||||
|
self.outgoingConnection = outgoingConnection
|
||||||
|
|
||||||
|
incomingConnection.start(queue: self.queue.queue)
|
||||||
|
outgoingConnection.start(queue: self.queue.queue)
|
||||||
|
|
||||||
|
self.receiveIncomingPacket()
|
||||||
|
self.receiveOutgoingPacket()
|
||||||
|
self.sendIncomingPingPackets()
|
||||||
|
|
||||||
|
self.pingTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.sendIncomingPingPackets()
|
||||||
|
self.sendOutgoingPingPackets()
|
||||||
|
}, queue: self.queue)
|
||||||
|
self.pingTimer?.start()
|
||||||
|
|
||||||
|
self.sendPacketTimer = SwiftSignalKit.Timer(timeout: self.sendPacketInterval, repeat: true, completion: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.sendOutgoingPacket()
|
||||||
|
}, queue: self.queue)
|
||||||
|
self.sendPacketTimer?.start()
|
||||||
|
|
||||||
|
self.bandwidthTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.calculateStats()
|
||||||
|
}, queue: self.queue)
|
||||||
|
self.bandwidthTimer?.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func calculateStats() {
|
||||||
|
guard let firstReceiveTimestamp = self.firstReceiveTimestamp, let lastReceiveTimestamp = self.lastReceiveTimestamp else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.sentPacketCount < self.maxPacketCount {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var totalSize = 0
|
||||||
|
var totalDelay: Double = 0.0
|
||||||
|
let totalTime: Double = lastReceiveTimestamp - firstReceiveTimestamp
|
||||||
|
|
||||||
|
for item in self.packetSizeAndTimeToReceive {
|
||||||
|
totalSize += item.0
|
||||||
|
totalDelay += item.1
|
||||||
|
}
|
||||||
|
|
||||||
|
let averageDelay: Double
|
||||||
|
if !self.packetSizeAndTimeToReceive.isEmpty {
|
||||||
|
averageDelay = totalDelay / Double(self.packetSizeAndTimeToReceive.count)
|
||||||
|
} else {
|
||||||
|
averageDelay = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
if totalTime != 0.0 {
|
||||||
|
let bandwidthBytesPerSecond = Int(Double(totalSize) / totalTime)
|
||||||
|
if let completion = self.completion {
|
||||||
|
self.completion = nil
|
||||||
|
completion(Results(
|
||||||
|
bandwidthBytesPerSecond: bandwidthBytesPerSecond,
|
||||||
|
expectedBandwidthBytesPerSecond: self.targetBandwidthBytesPerSecond,
|
||||||
|
averageDelay: averageDelay
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func sendIncomingPingPackets() {
|
||||||
|
guard let connection = self.incomingConnection else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var packetData = Data()
|
||||||
|
packetData.append(self.incomingTag)
|
||||||
|
|
||||||
|
var controlByte1: UInt8 = 0xff
|
||||||
|
for _ in 0 ..< 12 {
|
||||||
|
packetData.append(&controlByte1, count: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var controlByte2: UInt8 = 0xfe
|
||||||
|
packetData.append(&controlByte2, count: 1)
|
||||||
|
for _ in 0 ..< 3 {
|
||||||
|
packetData.append(&controlByte1, count: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var testValue: UInt64 = 123
|
||||||
|
withUnsafeBytes(of: &testValue, { buffer -> Void in
|
||||||
|
packetData.append(buffer.assumingMemoryBound(to: UInt8.self).baseAddress!, count: 8)
|
||||||
|
})
|
||||||
|
|
||||||
|
var zeroByte: UInt8 = 0
|
||||||
|
while packetData.count % 4 != 0 {
|
||||||
|
packetData.append(&zeroByte, count: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.incomingPingSendTimestamp == nil {
|
||||||
|
self.incomingPingSendTimestamp = CFAbsoluteTimeGetCurrent()
|
||||||
|
}
|
||||||
|
connection.send(content: packetData, completion: .contentProcessed({ _ in }))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func sendOutgoingPingPackets() {
|
||||||
|
guard let connection = self.outgoingConnection else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var packetData = Data()
|
||||||
|
packetData.append(self.outgoingTag)
|
||||||
|
|
||||||
|
var controlByte1: UInt8 = 0xff
|
||||||
|
for _ in 0 ..< 12 {
|
||||||
|
packetData.append(&controlByte1, count: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var controlByte2: UInt8 = 0xfe
|
||||||
|
packetData.append(&controlByte2, count: 1)
|
||||||
|
for _ in 0 ..< 3 {
|
||||||
|
packetData.append(&controlByte1, count: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var testValue: UInt64 = 123
|
||||||
|
withUnsafeBytes(of: &testValue, { buffer -> Void in
|
||||||
|
packetData.append(buffer.assumingMemoryBound(to: UInt8.self).baseAddress!, count: 8)
|
||||||
|
})
|
||||||
|
|
||||||
|
var zeroByte: UInt8 = 0
|
||||||
|
while packetData.count % 4 != 0 {
|
||||||
|
packetData.append(&zeroByte, count: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.outgoingPingSendTimestamp == nil {
|
||||||
|
self.outgoingPingSendTimestamp = CFAbsoluteTimeGetCurrent()
|
||||||
|
}
|
||||||
|
connection.send(content: packetData, completion: .contentProcessed({ _ in }))
|
||||||
|
}
|
||||||
|
|
||||||
|
private func sendOutgoingPacket() {
|
||||||
|
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||||
|
var timedOutPacketIds: [Data] = []
|
||||||
|
for (packetId, packetTimestamp) in self.unconfirmedPacketSendTimestamp {
|
||||||
|
let packetDelay = timestamp - packetTimestamp
|
||||||
|
if packetDelay > 2.0 {
|
||||||
|
timedOutPacketIds.append(packetId)
|
||||||
|
self.receivedPacketCount += 1
|
||||||
|
self.lastReceiveTimestamp = timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for packetId in timedOutPacketIds {
|
||||||
|
self.unconfirmedPacketSendTimestamp.removeValue(forKey: packetId)
|
||||||
|
}
|
||||||
|
if let outgoingPingSendTimestamp = self.outgoingPingSendTimestamp, !self.didReceiveOutgoingPing {
|
||||||
|
if outgoingPingSendTimestamp < timestamp - 2.0 {
|
||||||
|
self.didReceiveOutgoingPing = true
|
||||||
|
self.sentPacketCount = self.maxPacketCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let incomingPingSendTimestamp = self.incomingPingSendTimestamp, !self.didReceiveIncomingPing {
|
||||||
|
if incomingPingSendTimestamp < timestamp - 2.0 {
|
||||||
|
self.didReceiveIncomingPing = true
|
||||||
|
self.sentPacketCount = self.maxPacketCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let connection = self.outgoingConnection else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.sentPacketCount >= self.maxPacketCount {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !self.didReceiveIncomingPing && self.didReceiveOutgoingPing {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let bandwidthAdjustedPacketLength: Int32 = Int32(Double(self.targetBandwidthBytesPerSecond) * self.sendPacketInterval) + Int32.random(in: 0 ..< 1 * 1024)
|
||||||
|
var remainingPacketLength = bandwidthAdjustedPacketLength
|
||||||
|
|
||||||
|
while remainingPacketLength > 0 {
|
||||||
|
var packetData = Data()
|
||||||
|
|
||||||
|
packetData.append(self.outgoingTag)
|
||||||
|
packetData.append(self.outgoingRandomTag)
|
||||||
|
|
||||||
|
let packetLength = min(remainingPacketLength, 1 * 1024)
|
||||||
|
var dataLength: Int32 = 8 + bandwidthAdjustedPacketLength
|
||||||
|
withUnsafeBytes(of: &dataLength, { buffer -> Void in
|
||||||
|
packetData.append(buffer.assumingMemoryBound(to: UInt8.self).baseAddress!, count: buffer.count)
|
||||||
|
})
|
||||||
|
|
||||||
|
var packetId = Data(count: 8)
|
||||||
|
packetId.withUnsafeMutableBytes { buffer -> Void in
|
||||||
|
arc4random_buf(buffer.baseAddress!, buffer.count)
|
||||||
|
}
|
||||||
|
packetData.append(packetId)
|
||||||
|
|
||||||
|
var innerData = Data(count: Int(packetLength))
|
||||||
|
innerData.withUnsafeMutableBytes { buffer -> Void in
|
||||||
|
arc4random_buf(buffer.baseAddress!, buffer.count)
|
||||||
|
}
|
||||||
|
packetData.append(innerData)
|
||||||
|
|
||||||
|
var zeroByte: UInt8 = 0
|
||||||
|
while packetData.count % 4 != 0 {
|
||||||
|
packetData.append(&zeroByte, count: 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.unconfirmedPacketSendTimestamp[packetId] = timestamp
|
||||||
|
remainingPacketLength -= packetLength
|
||||||
|
self.sentPacketCount += 1
|
||||||
|
|
||||||
|
if self.firstReceiveTimestamp == nil {
|
||||||
|
self.firstReceiveTimestamp = timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
connection.send(content: packetData, completion: .contentProcessed({ _ in }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func receiveIncomingPacket() {
|
||||||
|
guard let connection = self.incomingConnection else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
connection.receive(minimumIncompleteLength: 1, maximumLength: 32 * 1024, completion: { [weak self] content, _, _, error in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if let content {
|
||||||
|
if content.count >= 16 + 4 + 4 + 8 {
|
||||||
|
let tag = content.subdata(in: 0 ..< 16)
|
||||||
|
if tag == self.incomingTag {
|
||||||
|
let packetId = content.subdata(in: (16 + 4 + 4) ..< (16 + 4 + 4 + 8))
|
||||||
|
if let sentTimestamp = self.unconfirmedPacketSendTimestamp.removeValue(forKey: packetId) {
|
||||||
|
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||||
|
let packetSendReceiveDuration = timestamp - sentTimestamp
|
||||||
|
self.lastReceiveTimestamp = timestamp
|
||||||
|
self.receivedPacketCount += 1
|
||||||
|
self.packetSizeAndTimeToReceive.append((content.count, packetSendReceiveDuration))
|
||||||
|
} else {
|
||||||
|
var pingHeaderData = Data()
|
||||||
|
var controlByte1: UInt8 = 0xff
|
||||||
|
for _ in 0 ..< 8 {
|
||||||
|
pingHeaderData.append(&controlByte1, count: 1)
|
||||||
|
}
|
||||||
|
let pingPacketId = content.subdata(in: 16 ..< (16 + 8))
|
||||||
|
if pingPacketId == pingHeaderData {
|
||||||
|
self.didReceiveIncomingPing = true
|
||||||
|
} else {
|
||||||
|
//print("Unknown incoming packet id")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("Invalid incoming tag")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("Invalid content length: \(content.count)")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("Incoming data receive error")
|
||||||
|
}
|
||||||
|
|
||||||
|
self.receiveIncomingPacket()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private func receiveOutgoingPacket() {
|
||||||
|
guard let connection = self.outgoingConnection else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
connection.receive(minimumIncompleteLength: 1, maximumLength: 32 * 1024, completion: { [weak self] content, _, _, error in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if let content {
|
||||||
|
if content.count >= 16 + 8 {
|
||||||
|
let tag = content.subdata(in: 0 ..< 16)
|
||||||
|
if tag == self.outgoingTag {
|
||||||
|
let packetId = content.subdata(in: 16 ..< (16 + 8))
|
||||||
|
var pingHeaderData = Data()
|
||||||
|
var controlByte1: UInt8 = 0xff
|
||||||
|
for _ in 0 ..< 8 {
|
||||||
|
pingHeaderData.append(&controlByte1, count: 1)
|
||||||
|
}
|
||||||
|
if packetId == pingHeaderData {
|
||||||
|
self.didReceiveOutgoingPing = true
|
||||||
|
} else {
|
||||||
|
print("Unknown outgoing packet id")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("Invalid outgoing tag")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print("Invalid content length: \(content.count)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.receiveOutgoingPacket()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static let sharedQueue = Queue(name: "ReflectorBenchmark")
|
||||||
|
private let impl: QueueLocalObject<Impl>
|
||||||
|
|
||||||
|
public init(address: String, port: Int) {
|
||||||
|
let queue = ReflectorBenchmark.sharedQueue
|
||||||
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||||
|
return Impl(queue: queue, address: address, port: port)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public func start(completion: @escaping (Results) -> Void) {
|
||||||
|
self.impl.with { impl in
|
||||||
|
impl.start(completion: completion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user