diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index c40825879d..3f9396f8d0 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -78,7 +78,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { case knockoutWallpaper(PresentationTheme, Bool) case demoVideoChats(Bool) case experimentalCompatibility(Bool) - case enableNoiseSuppression(Bool) + case enableDebugDataDisplay(Bool) case playerEmbedding(Bool) case playlistPlayback(Bool) case voiceConference @@ -100,7 +100,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.logging.rawValue case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries: return DebugControllerSection.experiments.rawValue - case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .demoVideoChats, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableNoiseSuppression: + case .clearTips, .crash, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .demoVideoChats, .playerEmbedding, .playlistPlayback, .voiceConference, .experimentalCompatibility, .enableDebugDataDisplay: return DebugControllerSection.experiments.rawValue case .preferredVideoCodec: return DebugControllerSection.videoExperiments.rawValue @@ -167,7 +167,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return 25 case .experimentalCompatibility: return 26 - case .enableNoiseSuppression: + case .enableDebugDataDisplay: return 27 case .playerEmbedding: return 28 @@ -741,12 +741,12 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }).start() }) - case let .enableNoiseSuppression(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Noise Suppression", value: value, sectionId: self.section, style: .blocks, updated: { value in + case let .enableDebugDataDisplay(value): + return ItemListSwitchItem(presentationData: presentationData, title: "Debug Data Display", 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.enableNoiseSuppression = value + settings.enableDebugDataDisplay = value return settings }) }).start() @@ -862,7 +862,7 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present entries.append(.knockoutWallpaper(presentationData.theme, experimentalSettings.knockoutWallpaper)) entries.append(.demoVideoChats(experimentalSettings.demoVideoChats)) entries.append(.experimentalCompatibility(experimentalSettings.experimentalCompatibility)) - entries.append(.enableNoiseSuppression(experimentalSettings.enableNoiseSuppression)) + entries.append(.enableDebugDataDisplay(experimentalSettings.enableDebugDataDisplay)) entries.append(.playerEmbedding(experimentalSettings.playerEmbedding)) entries.append(.playlistPlayback(experimentalSettings.playlistPlayback)) } diff --git a/submodules/TelegramCallsUI/Sources/GroupVideoNode.swift b/submodules/TelegramCallsUI/Sources/GroupVideoNode.swift index 7bc3ca3289..282a384d67 100644 --- a/submodules/TelegramCallsUI/Sources/GroupVideoNode.swift +++ b/submodules/TelegramCallsUI/Sources/GroupVideoNode.swift @@ -24,6 +24,8 @@ final class GroupVideoNode: ASDisplayNode, PreviewVideoNode { private let containerNode: ASDisplayNode private let videoViewContainer: UIView private let videoView: VideoRenderingView + + private let debugTextNode: ImmediateTextNode private let backdropVideoViewContainer: UIView private let backdropVideoView: VideoRenderingView? @@ -55,6 +57,8 @@ final class GroupVideoNode: ASDisplayNode, PreviewVideoNode { self.backdropVideoViewContainer = UIView() self.backdropVideoViewContainer.isUserInteractionEnabled = false self.backdropVideoView = backdropVideoView + + self.debugTextNode = ImmediateTextNode() super.init() @@ -66,6 +70,7 @@ final class GroupVideoNode: ASDisplayNode, PreviewVideoNode { self.videoViewContainer.addSubview(self.videoView) self.addSubnode(self.sourceContainerNode) self.containerNode.view.addSubview(self.videoViewContainer) + self.containerNode.addSubnode(self.debugTextNode) self.sourceContainerNode.contentNode.addSubnode(self.containerNode) self.clipsToBounds = true @@ -199,9 +204,24 @@ final class GroupVideoNode: ASDisplayNode, PreviewVideoNode { } return rotatedAspect } + + func updateDebugInfo(text: String) { + self.debugTextNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white) + if let (size, layoutMode) = self.validLayout { + self.updateLayout(size: size, layoutMode: layoutMode, transition: .immediate) + } + } func updateLayout(size: CGSize, layoutMode: VideoNodeLayoutMode, transition: ContainedViewLayoutTransition) { self.validLayout = (size, layoutMode) + + let debugTextSize = self.debugTextNode.updateLayout(CGSize(width: 200.0, height: 200.0)) + if size.height > size.width + 100.0 { + self.debugTextNode.frame = CGRect(origin: CGPoint(x: 5.0, y: 44.0), size: debugTextSize) + } else { + self.debugTextNode.frame = CGRect(origin: CGPoint(x: 5.0, y: 5.0), size: debugTextSize) + } + let bounds = CGRect(origin: CGPoint(), size: size) self.sourceContainerNode.update(size: size, transition: .immediate) transition.updateFrameAsPositionAndBounds(node: self.sourceContainerNode, frame: bounds) diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index e28ca36679..6ee0804dd9 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -1386,8 +1386,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { outgoingAudioBitrateKbit = value } - let enableNoiseSuppression = accountContext.sharedContext.immediateExperimentalUISettings.enableNoiseSuppression - genericCallContext = OngoingGroupCallContext(video: self.videoCapturer, requestMediaChannelDescriptions: { [weak self] ssrcs, completion in let disposable = MetaDisposable() Queue.mainQueue().async { @@ -1406,7 +1404,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { strongSelf.requestCall(movingFromBroadcastToRtc: false) } } - }, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: self.isVideoEnabled ? .generic : .none, enableNoiseSuppression: enableNoiseSuppression) + }, outgoingAudioBitrateKbit: outgoingAudioBitrateKbit, videoContentType: self.isVideoEnabled ? .generic : .none, enableNoiseSuppression: false) self.genericCallContext = genericCallContext self.stateVersionValue += 1 @@ -3202,4 +3200,24 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { public func loadMoreMembers(token: String) { self.participantsContext?.loadMore(token: token) } + + func getStats() -> Signal { + return Signal { [weak self] subscriber in + guard let strongSelf = self else { + subscriber.putCompletion() + return EmptyDisposable + } + if let genericCallContext = strongSelf.genericCallContext { + genericCallContext.getStats(completion: { stats in + subscriber.putNext(stats) + subscriber.putCompletion() + }) + } else { + subscriber.putCompletion() + } + + return EmptyDisposable + } + |> runOn(.mainQueue()) + } } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index a999734b3a..4bc0039ed4 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -945,6 +945,8 @@ public final class VoiceChatController: ViewController { return false } } + + private var statsDisposable: Disposable? init(controller: VoiceChatController, sharedContext: SharedAccountContext, call: PresentationGroupCall) { self.controller = controller @@ -2338,6 +2340,27 @@ public final class VoiceChatController: ViewController { } strongSelf.appIsActive = active }) + + if self.context.sharedContext.immediateExperimentalUISettings.enableDebugDataDisplay { + self.statsDisposable = ((call as! PresentationGroupCallImpl).getStats() + |> deliverOnMainQueue + |> then(.complete() |> delay(1.0, queue: .mainQueue())) + |> restart).start(next: { [weak self] stats in + guard let strongSelf = self else { + return + } + for (endpointId, videoNode) in strongSelf.videoNodes { + if let incomingVideoStats = stats.incomingVideoStats[endpointId] { + videoNode.updateDebugInfo(text: "in: \(incomingVideoStats.receivingQuality)\n srv: \(incomingVideoStats.availableQuality)") + } + } + if let (_, maybeEndpointId, _, _, _) = strongSelf.mainStageNode.currentPeer, let endpointId = maybeEndpointId { + if let incomingVideoStats = stats.incomingVideoStats[endpointId] { + strongSelf.mainStageNode.currentVideoNode?.updateDebugInfo(text: "in: \(incomingVideoStats.receivingQuality)\n srv: \(incomingVideoStats.availableQuality)") + } + } + }) + } } deinit { @@ -2361,6 +2384,7 @@ public final class VoiceChatController: ViewController { self.readyVideoDisposables.dispose() self.applicationStateDisposable?.dispose() self.myPeerVideoReadyDisposable.dispose() + self.statsDisposable?.dispose() } private func openSettingsMenu(sourceNode: ASDisplayNode, gesture: ContextGesture?) { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift b/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift index 2997398e51..7c0cc6f84d 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatMainStageNode.swift @@ -95,12 +95,12 @@ private class VoiceChatPinButtonNode: HighlightTrackingButtonNode { final class VoiceChatMainStageNode: ASDisplayNode { private let context: AccountContext private let call: PresentationGroupCall - private var currentPeer: (PeerId, String?, Bool, Bool, Bool)? + private(set) var currentPeer: (PeerId, String?, Bool, Bool, Bool)? private var currentPeerEntry: VoiceChatPeerEntry? var callState: PresentationGroupCallState? - private var currentVideoNode: GroupVideoNode? + private(set) var currentVideoNode: GroupVideoNode? private let backgroundNode: ASDisplayNode private let topFadeNode: ASDisplayNode diff --git a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift index 1ae9c83d4c..d08fd83283 100644 --- a/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift +++ b/submodules/TelegramUIPreferences/Sources/ExperimentalUISettings.swift @@ -16,7 +16,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry { public var enableVoipTcp: Bool public var demoVideoChats: Bool public var experimentalCompatibility: Bool - public var enableNoiseSuppression: Bool + public var enableDebugDataDisplay: Bool public static var defaultSettings: ExperimentalUISettings { return ExperimentalUISettings( @@ -33,7 +33,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry { enableVoipTcp: false, demoVideoChats: false, experimentalCompatibility: false, - enableNoiseSuppression: false + enableDebugDataDisplay: false ) } @@ -51,7 +51,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry { enableVoipTcp: Bool, demoVideoChats: Bool, experimentalCompatibility: Bool, - enableNoiseSuppression: Bool + enableDebugDataDisplay: Bool ) { self.keepChatNavigationStack = keepChatNavigationStack self.skipReadHistory = skipReadHistory @@ -66,7 +66,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry { self.enableVoipTcp = enableVoipTcp self.demoVideoChats = demoVideoChats self.experimentalCompatibility = experimentalCompatibility - self.enableNoiseSuppression = enableNoiseSuppression + self.enableDebugDataDisplay = enableDebugDataDisplay } public init(decoder: PostboxDecoder) { @@ -83,7 +83,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry { self.enableVoipTcp = decoder.decodeInt32ForKey("enableVoipTcp", orElse: 0) != 0 self.demoVideoChats = decoder.decodeInt32ForKey("demoVideoChats", orElse: 0) != 0 self.experimentalCompatibility = decoder.decodeInt32ForKey("experimentalCompatibility", orElse: 0) != 0 - self.enableNoiseSuppression = decoder.decodeInt32ForKey("enableNoiseSuppression", orElse: 0) != 0 + self.enableDebugDataDisplay = decoder.decodeInt32ForKey("enableDebugDataDisplay", orElse: 0) != 0 } public func encode(_ encoder: PostboxEncoder) { @@ -102,7 +102,7 @@ public struct ExperimentalUISettings: Equatable, PreferencesEntry { encoder.encodeInt32(self.enableVoipTcp ? 1 : 0, forKey: "enableVoipTcp") encoder.encodeInt32(self.demoVideoChats ? 1 : 0, forKey: "demoVideoChats") encoder.encodeInt32(self.experimentalCompatibility ? 1 : 0, forKey: "experimentalCompatibility") - encoder.encodeInt32(self.enableNoiseSuppression ? 1 : 0, forKey: "enableNoiseSuppression") + encoder.encodeInt32(self.enableDebugDataDisplay ? 1 : 0, forKey: "enableDebugDataDisplay") } public func isEqual(to: PreferencesEntry) -> Bool { diff --git a/submodules/TelegramVoip/Sources/GroupCallContext.swift b/submodules/TelegramVoip/Sources/GroupCallContext.swift index 0f16e5c147..62e7fb1687 100644 --- a/submodules/TelegramVoip/Sources/GroupCallContext.swift +++ b/submodules/TelegramVoip/Sources/GroupCallContext.swift @@ -311,6 +311,15 @@ public final class OngoingGroupCallContext { self.mirrorVertically = frameData.mirrorVertically } } + + public struct Stats { + public struct IncomingVideoStats { + public var receivingQuality: Int + public var availableQuality: Int + } + + public var incomingVideoStats: [String: IncomingVideoStats] + } private final class Impl { let queue: Queue @@ -732,6 +741,16 @@ public final class OngoingGroupCallContext { func addExternalAudioData(data: Data) { self.context.addExternalAudioData(data) } + + func getStats(completion: @escaping (Stats) -> Void) { + self.context.getStats({ stats in + var incomingVideoStats: [String: Stats.IncomingVideoStats] = [:] + for (key, value) in stats.incomingVideoStats { + incomingVideoStats[key] = Stats.IncomingVideoStats(receivingQuality: Int(value.receivingQuality), availableQuality: Int(value.availableQuality)) + } + completion(Stats(incomingVideoStats: incomingVideoStats)) + }) + } } private let queue = Queue() @@ -909,4 +928,10 @@ public final class OngoingGroupCallContext { impl.addExternalAudioData(data: data) } } + + public func getStats(completion: @escaping (Stats) -> Void) { + self.impl.with { impl in + impl.getStats(completion: completion) + } + } } diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h index 6671454e7c..55e3492af0 100644 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h @@ -318,6 +318,23 @@ typedef NS_ENUM(int32_t, OngoingGroupCallRequestedVideoQuality) { @end +@interface OngoingGroupCallIncomingVideoStats : NSObject + +@property (nonatomic, readonly) int receivingQuality; +@property (nonatomic, readonly) int availableQuality; + +- (instancetype _Nonnull)initWithReceivingQuality:(int)receivingQuality availableQuality:(int)availableQuality; + +@end + +@interface OngoingGroupCallStats : NSObject + +@property (nonatomic, strong, readonly) NSDictionary * _Nonnull incomingVideoStats; + +- (instancetype _Nonnull)initWithIncomingVideoStats:(NSDictionary * _Nonnull)incomingVideoStats; + +@end + @interface GroupCallThreadLocalContext : NSObject - (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue @@ -355,6 +372,8 @@ typedef NS_ENUM(int32_t, OngoingGroupCallRequestedVideoQuality) { - (void)addExternalAudioData:(NSData * _Nonnull)data; +- (void)getStats:(void (^ _Nonnull)(OngoingGroupCallStats * _Nonnull))completion; + @end #endif diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index 09415bfdef..9a652771a2 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -1785,6 +1785,20 @@ private: } } +- (void)getStats:(void (^ _Nonnull)(OngoingGroupCallStats * _Nonnull))completion { + if (_instance) { + _instance->getStats([completion](tgcalls::GroupInstanceStats stats) { + NSMutableDictionary *incomingVideoStats = [[NSMutableDictionary alloc] init]; + + for (const auto &it : stats.incomingVideoStats) { + incomingVideoStats[[NSString stringWithUTF8String:it.first.c_str()]] = [[OngoingGroupCallIncomingVideoStats alloc] initWithReceivingQuality:it.second.receivingQuality availableQuality:it.second.availableQuality]; + } + + completion([[OngoingGroupCallStats alloc] initWithIncomingVideoStats:incomingVideoStats]); + }); + } +} + @end @implementation OngoingGroupCallMediaChannelDescription @@ -1846,3 +1860,28 @@ private: } @end + +@implementation OngoingGroupCallIncomingVideoStats + +- (instancetype _Nonnull)initWithReceivingQuality:(int)receivingQuality availableQuality:(int)availableQuality { + self = [super init]; + if (self != nil) { + _receivingQuality = receivingQuality; + _availableQuality = availableQuality; + } + return self; +} + +@end + +@implementation OngoingGroupCallStats + +- (instancetype _Nonnull)initWithIncomingVideoStats:(NSDictionary * _Nonnull)incomingVideoStats { + self = [super init]; + if (self != nil) { + _incomingVideoStats = incomingVideoStats; + } + return self; +} + +@end diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index 818c174b37..68eadd0cb3 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit 818c174b37c9c9b6d267e401d0198fa7b1dc1dde +Subproject commit 68eadd0cb31ceedcfbf7f810dc86d2ebc7dabf9d