mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'experiments/group-calls' into experimental-2
This commit is contained in:
commit
1626214f66
@ -13,6 +13,7 @@ import ItemListUI
|
||||
import PresentationDataUtils
|
||||
import OverlayStatusController
|
||||
import AccountContext
|
||||
import TelegramCallsUI
|
||||
|
||||
@objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate {
|
||||
public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||
@ -74,6 +75,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
case alternativeFolderTabs(Bool)
|
||||
case playerEmbedding(Bool)
|
||||
case playlistPlayback(Bool)
|
||||
case voiceConference
|
||||
case preferredVideoCodec(Int, String, String?, Bool)
|
||||
case disableVideoAspectScaling(Bool)
|
||||
case enableVoipTcp(Bool)
|
||||
@ -90,7 +92,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return DebugControllerSection.logging.rawValue
|
||||
case .enableRaiseToSpeak, .keepChatNavigationStack, .skipReadHistory, .crashOnSlowQueries:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .clearTips, .reimport, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .alternativeFolderTabs, .playerEmbedding, .playlistPlayback:
|
||||
case .clearTips, .reimport, .resetData, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .reindexUnread, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .alternativeFolderTabs, .playerEmbedding, .playlistPlayback, .voiceConference:
|
||||
return DebugControllerSection.experiments.rawValue
|
||||
case .preferredVideoCodec:
|
||||
return DebugControllerSection.videoExperiments.rawValue
|
||||
@ -155,8 +157,10 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
return 24
|
||||
case .playlistPlayback:
|
||||
return 25
|
||||
case .voiceConference:
|
||||
return 26
|
||||
case let .preferredVideoCodec(index, _, _, _):
|
||||
return 26 + index
|
||||
return 27 + index
|
||||
case .disableVideoAspectScaling:
|
||||
return 100
|
||||
case .enableVoipTcp:
|
||||
@ -648,6 +652,15 @@ private enum DebugControllerEntry: ItemListNodeEntry {
|
||||
})
|
||||
}).start()
|
||||
})
|
||||
case .voiceConference:
|
||||
return ItemListDisclosureItem(presentationData: presentationData, title: "Voice Conference (Test)", label: "", sectionId: self.section, style: .blocks, action: {
|
||||
guard let context = arguments.context else {
|
||||
return
|
||||
}
|
||||
let controller = GroupCallController(context: context)
|
||||
controller.navigationPresentation = .modal
|
||||
arguments.pushController(controller)
|
||||
})
|
||||
case let .preferredVideoCodec(_, title, value, isSelected):
|
||||
return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .right, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: {
|
||||
let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
|
||||
@ -725,6 +738,8 @@ private func debugControllerEntries(presentationData: PresentationData, loggingS
|
||||
entries.append(.playerEmbedding(experimentalSettings.playerEmbedding))
|
||||
entries.append(.playlistPlayback(experimentalSettings.playlistPlayback))
|
||||
|
||||
entries.append(.voiceConference)
|
||||
|
||||
let codecs: [(String, String?)] = [
|
||||
("No Preference", nil),
|
||||
("H265", "H265"),
|
||||
|
147
submodules/TelegramCallsUI/Sources/GroupCallController.swift
Normal file
147
submodules/TelegramCallsUI/Sources/GroupCallController.swift
Normal file
@ -0,0 +1,147 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import TelegramVoip
|
||||
import TelegramAudio
|
||||
import AccountContext
|
||||
|
||||
public final class GroupCallController: ViewController {
|
||||
private final class Node: ViewControllerTracingNode {
|
||||
private let context: AccountContext
|
||||
private let presentationData: PresentationData
|
||||
|
||||
private var callContext: GroupCallContext?
|
||||
private var callDisposable: Disposable?
|
||||
private var memberCountDisposable: Disposable?
|
||||
private var isMutedDisposable: Disposable?
|
||||
private let audioSessionActive = Promise<Bool>(false)
|
||||
|
||||
private var memberCount: Int = 0
|
||||
private let memberCountNode: ImmediateTextNode
|
||||
|
||||
private var isMuted: Bool = false
|
||||
private let isMutedNode: ImmediateTextNode
|
||||
private let muteButton: HighlightableButtonNode
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
init(context: AccountContext) {
|
||||
self.context = context
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
self.memberCountNode = ImmediateTextNode()
|
||||
self.isMutedNode = ImmediateTextNode()
|
||||
|
||||
self.muteButton = HighlightableButtonNode()
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor
|
||||
|
||||
self.addSubnode(self.memberCountNode)
|
||||
|
||||
self.muteButton.addSubnode(self.isMutedNode)
|
||||
self.addSubnode(self.muteButton)
|
||||
|
||||
let audioSessionActive = self.audioSessionActive
|
||||
self.callDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: .voiceCall, manualActivate: { audioSessionControl in
|
||||
audioSessionControl.activate({ _ in })
|
||||
audioSessionActive.set(.single(true))
|
||||
}, deactivate: {
|
||||
return Signal { subscriber in
|
||||
subscriber.putCompletion()
|
||||
return EmptyDisposable
|
||||
}
|
||||
}, availableOutputsChanged: { _, _ in
|
||||
})
|
||||
|
||||
let callContext = GroupCallContext(audioSessionActive: self.audioSessionActive.get())
|
||||
self.callContext = callContext
|
||||
|
||||
self.memberCountDisposable = (callContext.memberCount
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.memberCount = value
|
||||
if let layout = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .immediate)
|
||||
}
|
||||
})
|
||||
|
||||
self.isMutedDisposable = (callContext.isMuted
|
||||
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.isMuted = value
|
||||
if let layout = strongSelf.validLayout {
|
||||
strongSelf.containerLayoutUpdated(layout, transition: .immediate)
|
||||
}
|
||||
})
|
||||
|
||||
self.muteButton.addTarget(self, action: #selector(self.muteButtonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.callDisposable?.dispose()
|
||||
self.memberCountDisposable?.dispose()
|
||||
}
|
||||
|
||||
@objc private func muteButtonPressed() {
|
||||
self.callContext?.toggleIsMuted()
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = layout
|
||||
|
||||
self.memberCountNode.attributedText = NSAttributedString(string: "Members: \(self.memberCount)", font: Font.regular(17.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor)
|
||||
|
||||
self.isMutedNode.attributedText = NSAttributedString(string: self.isMuted ? "Unmute" : "Mute", font: Font.regular(17.0), textColor: self.presentationData.theme.list.itemAccentColor)
|
||||
|
||||
let textSize = self.memberCountNode.updateLayout(CGSize(width: layout.size.width - 16.0 * 2.0, height: 100.0))
|
||||
let isMutedSize = self.isMutedNode.updateLayout(CGSize(width: layout.size.width - 16.0 * 2.0, height: 100.0))
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: floor((layout.size.height - textSize.width) / 2.0)), size: textSize)
|
||||
transition.updateFrameAdditiveToCenter(node: self.memberCountNode, frame: textFrame)
|
||||
|
||||
let isMutedFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - isMutedSize.width) / 2.0), y: textFrame.maxY + 12.0), size: isMutedSize)
|
||||
transition.updateFrame(node: self.muteButton, frame: isMutedFrame)
|
||||
self.isMutedNode.frame = CGRect(origin: CGPoint(), size: isMutedFrame.size)
|
||||
}
|
||||
}
|
||||
|
||||
private let context: AccountContext
|
||||
private let presentationData: PresentationData
|
||||
|
||||
private var controllerNode: Node {
|
||||
return self.displayNode as! Node
|
||||
}
|
||||
|
||||
public init(context: AccountContext) {
|
||||
self.context = context
|
||||
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData))
|
||||
}
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = Node(context: self.context)
|
||||
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, transition: transition)
|
||||
}
|
||||
}
|
@ -52,6 +52,7 @@ import PeerInfoUI
|
||||
import ListMessageItem
|
||||
import GalleryData
|
||||
import ChatInterfaceState
|
||||
import TelegramVoip
|
||||
|
||||
protocol PeerInfoScreenItem: class {
|
||||
var id: AnyHashable { get }
|
||||
@ -3153,6 +3154,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD
|
||||
self.controller?.present(shareController, in: .window(.root))
|
||||
}
|
||||
|
||||
private let groupCallDisposable = MetaDisposable()
|
||||
private var groupCall: GroupCallContext?
|
||||
|
||||
private func requestCall(isVideo: Bool) {
|
||||
guard let peer = self.data?.peer as? TelegramUser, let cachedUserData = self.data?.cachedData as? CachedUserData else {
|
||||
return
|
||||
|
1469
submodules/TelegramVoip/Sources/GroupCallContext.swift
Normal file
1469
submodules/TelegramVoip/Sources/GroupCallContext.swift
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,18 @@
|
||||
#ifndef GroupCallThreadLocalContext_h
|
||||
#define GroupCallThreadLocalContext_h
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <TgVoipWebrtc/OngoingCallThreadLocalContext.h>
|
||||
|
||||
@interface GroupCallThreadLocalContext : NSObject
|
||||
|
||||
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer;
|
||||
|
||||
- (void)emitOffer;
|
||||
- (void)setOfferSdp:(NSString * _Nonnull)offerSdp isPartial:(bool)isPartial;
|
||||
- (void)setIsMuted:(bool)isMuted;
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
@ -0,0 +1,57 @@
|
||||
|
||||
#import <TgVoipWebrtc/GroupCallThreadLocalContext.h>
|
||||
|
||||
#import "group/GroupInstanceImpl.h"
|
||||
|
||||
@interface GroupCallThreadLocalContext () {
|
||||
id<OngoingCallThreadLocalContextQueueWebrtc> _queue;
|
||||
|
||||
std::unique_ptr<tgcalls::GroupInstanceImpl> _instance;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation GroupCallThreadLocalContext
|
||||
|
||||
- (instancetype _Nonnull)initWithQueue:(id<OngoingCallThreadLocalContextQueueWebrtc> _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_queue = queue;
|
||||
|
||||
tgcalls::GroupInstanceDescriptor descriptor;
|
||||
__weak GroupCallThreadLocalContext *weakSelf = self;
|
||||
descriptor.sdpAnswerEmitted = [weakSelf, queue, relaySdpAnswer](std::string const &sdpAnswer) {
|
||||
NSString *string = [NSString stringWithUTF8String:sdpAnswer.c_str()];
|
||||
[queue dispatch:^{
|
||||
__strong GroupCallThreadLocalContext *strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
return;
|
||||
}
|
||||
relaySdpAnswer(string);
|
||||
}];
|
||||
};
|
||||
|
||||
_instance.reset(new tgcalls::GroupInstanceImpl(std::move(descriptor)));
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)emitOffer {
|
||||
if (_instance) {
|
||||
_instance->emitOffer();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setOfferSdp:(NSString * _Nonnull)offerSdp isPartial:(bool)isPartial {
|
||||
if (_instance) {
|
||||
_instance->setOfferSdp([offerSdp UTF8String], isPartial);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setIsMuted:(bool)isMuted {
|
||||
if (_instance) {
|
||||
_instance->setIsMuted(isMuted);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
@ -1,7 +1,7 @@
|
||||
#ifndef WEBRTC_IOS
|
||||
#import "OngoingCallThreadLocalContext.h"
|
||||
#else
|
||||
#import <TgVoip/OngoingCallThreadLocalContext.h>
|
||||
#import <TgVoipWebrtc/OngoingCallThreadLocalContext.h>
|
||||
#endif
|
||||
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 64f96a1b4fcfb8afdb0fb7749082cb42cdad7901
|
||||
Subproject commit 4f7501d281b851e6302b2a2d7298c733eee82414
|
2
third-party/webrtc/BUILD
vendored
2
third-party/webrtc/BUILD
vendored
@ -1,4 +1,4 @@
|
||||
use_gn_build = True
|
||||
use_gn_build = False
|
||||
|
||||
webrtc_libs = [
|
||||
"libwebrtc.a",
|
||||
|
2
third-party/webrtc/webrtc-ios
vendored
2
third-party/webrtc/webrtc-ios
vendored
@ -1 +1 @@
|
||||
Subproject commit 11255bcfff3180210a012f368e2d2bcd169b6877
|
||||
Subproject commit 782743c7931d09c1d2e4a0cf6cd349ee45452f1d
|
Loading…
x
Reference in New Issue
Block a user