diff --git a/Telegram/BUILD b/Telegram/BUILD
index 5fabcd142c..f3af41994c 100644
--- a/Telegram/BUILD
+++ b/Telegram/BUILD
@@ -277,6 +277,7 @@ official_apple_pay_merchants = [
"merchant.sberbank.test.ph.telegra.Telegraph",
"merchant.privatbank.test.telergramios",
"merchant.privatbank.prod.telergram",
+ "merchant.paymaster.test.telegramios",
]
official_bundle_ids = [
@@ -1430,6 +1431,94 @@ ios_extension(
],
)
+plist_fragment(
+ name = "BroadcastUploadInfoPlist",
+ extension = "plist",
+ template =
+ """
+ CFBundleDevelopmentRegion
+ en
+ CFBundleIdentifier
+ {telegram_bundle_id}.BroadcastUpload
+ CFBundleName
+ Telegram
+ CFBundlePackageType
+ XPC!
+ NSExtension
+
+ NSExtensionPointIdentifier
+ com.apple.broadcast-services-upload
+ NSExtensionPrincipalClass
+ BroadcastUploadSampleHandler
+ RPBroadcastProcessMode
+ RPBroadcastProcessModeSampleBuffer
+
+ """.format(
+ telegram_bundle_id = telegram_bundle_id,
+ )
+)
+
+swift_library(
+ name = "BroadcastUploadExtensionLib",
+ module_name = "BroadcastUploadExtensionLib",
+ srcs = glob([
+ "BroadcastUpload/**/*.swift",
+ ]),
+ deps = [
+ "//submodules/TelegramUI:TelegramUI",
+ "//submodules/TelegramVoip:TelegramVoip",
+ "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
+ "//submodules/BuildConfig:BuildConfig",
+ ],
+)
+
+genrule(
+ name = "SetMinOsVersionBroadcastUploadExtension",
+ cmd_bash =
+"""
+ name=BroadcastUploadExtension.appex
+ cat $(location PatchMinOSVersion.source.sh) | sed -e "s/<<>>/11\\.0/g" | sed -e "s/<<>>/$$name/g" > $(location SetMinOsVersionBroadcastUploadExtension.sh)
+""",
+ srcs = [
+ "PatchMinOSVersion.source.sh",
+ ],
+ outs = [
+ "SetMinOsVersionBroadcastUploadExtension.sh",
+ ],
+ executable = True,
+ visibility = [
+ "//visibility:public",
+ ]
+)
+
+ios_extension(
+ name = "BroadcastUploadExtension",
+ bundle_id = "{telegram_bundle_id}.BroadcastUpload".format(
+ telegram_bundle_id = telegram_bundle_id,
+ ),
+ families = [
+ "iphone",
+ "ipad",
+ ],
+ infoplists = [
+ ":BroadcastUploadInfoPlist",
+ ":VersionInfoPlist",
+ ":BuildNumberInfoPlist",
+ ":AppNameInfoPlist",
+ ],
+ minimum_os_version = "9.0", # maintain the same minimum OS version across extensions
+ ipa_post_processor = ":SetMinOsVersionBroadcastUploadExtension",
+ provisioning_profile = select({
+ ":disableProvisioningProfilesSetting": None,
+ "//conditions:default": "@build_configuration//provisioning:BroadcastUpload.mobileprovision",
+ }),
+ deps = [":BroadcastUploadExtensionLib"],
+ frameworks = [
+ ":TelegramUIFramework",
+ ":SwiftSignalKitFramework",
+ ],
+)
+
plist_fragment(
name = "NotificationServiceInfoPlist",
extension = "plist",
@@ -1718,6 +1807,7 @@ ios_application(
":NotificationServiceExtension",
":IntentsExtension",
":WidgetExtension",
+ ":BroadcastUploadExtension",
],
}),
watch_application = select({
diff --git a/Telegram/BroadcastUpload/BroadcastUploadExtension.swift b/Telegram/BroadcastUpload/BroadcastUploadExtension.swift
new file mode 100644
index 0000000000..9fcb952043
--- /dev/null
+++ b/Telegram/BroadcastUpload/BroadcastUploadExtension.swift
@@ -0,0 +1,192 @@
+import Foundation
+import ReplayKit
+import CoreVideo
+import TelegramVoip
+import SwiftSignalKit
+import BuildConfig
+
+private func rootPathForBasePath(_ appGroupPath: String) -> String {
+ return appGroupPath + "/telegram-data"
+}
+
+@available(iOS 10.0, *)
+@objc(BroadcastUploadSampleHandler) class BroadcastUploadSampleHandler: RPBroadcastSampleHandler {
+ /*private var ipcContext: IpcGroupCallBroadcastContext?
+ private var callContext: OngoingGroupCallContext?
+ private var videoCapturer: OngoingCallVideoCapturer?
+ private var requestDisposable: Disposable?
+ private var joinPayloadDisposable: Disposable?
+ private var joinResponsePayloadDisposable: Disposable?*/
+
+ private var screencastBufferClientContext: IpcGroupCallBufferBroadcastContext?
+ private var statusDisposable: Disposable?
+
+ deinit {
+ /*self.requestDisposable?.dispose()
+ self.joinPayloadDisposable?.dispose()
+ self.joinResponsePayloadDisposable?.dispose()
+ self.callContext?.stop()*/
+
+ self.statusDisposable?.dispose()
+ }
+
+ public override func beginRequest(with context: NSExtensionContext) {
+ super.beginRequest(with: context)
+ }
+
+ private func finishWithGenericError() {
+ let error = NSError(domain: "BroadcastUploadExtension", code: 1, userInfo: [
+ NSLocalizedDescriptionKey: "Finished"
+ ])
+ finishBroadcastWithError(error)
+
+ /*self.callContext?.stop()
+ self.callContext = nil
+
+ self.ipcContext = nil*/
+ }
+
+ private func finishWithNoBroadcast() {
+ let error = NSError(domain: "BroadcastUploadExtension", code: 1, userInfo: [
+ NSLocalizedDescriptionKey: "You're not in a voice chat"
+ ])
+ finishBroadcastWithError(error)
+ }
+
+ override public func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?) {
+ guard let appBundleIdentifier = Bundle.main.bundleIdentifier, let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
+ self.finishWithGenericError()
+ return
+ }
+
+ let baseAppBundleId = String(appBundleIdentifier[.. deliverOnMainQueue).start(next: { [weak self] status in
+ guard let strongSelf = self else {
+ return
+ }
+ switch status {
+ case .finished:
+ strongSelf.finishWithNoBroadcast()
+ }
+ })
+
+ /*let ipcContext = IpcGroupCallBroadcastContext(basePath: rootPath + "/broadcast-coordination")
+ self.ipcContext = ipcContext
+
+ self.requestDisposable = (ipcContext.request
+ |> timeout(3.0, queue: .mainQueue(), alternate: .single(.failed))
+ |> take(1)
+ |> deliverOnMainQueue).start(next: { [weak self] request in
+ guard let strongSelf = self else {
+ return
+ }
+ switch request {
+ case .request:
+ strongSelf.beginWithRequest()
+ case .failed:
+ strongSelf.finishWithGenericError()
+ }
+ })*/
+ }
+
+ /*private func beginWithRequest() {
+ let videoCapturer = OngoingCallVideoCapturer(isCustom: true)
+ self.videoCapturer = videoCapturer
+
+ let callContext = OngoingGroupCallContext(video: videoCapturer, requestMediaChannelDescriptions: { _, _ in return EmptyDisposable }, audioStreamData: nil, rejoinNeeded: {
+ }, outgoingAudioBitrateKbit: nil, videoContentType: .screencast, enableNoiseSuppression: false)
+ self.callContext = callContext
+
+ self.joinPayloadDisposable = (callContext.joinPayload
+ |> take(1)
+ |> deliverOnMainQueue).start(next: { [weak self] joinPayload in
+ guard let strongSelf = self, let ipcContext = strongSelf.ipcContext else {
+ return
+ }
+ ipcContext.setJoinPayload(joinPayload.0)
+
+ strongSelf.joinResponsePayloadDisposable = (ipcContext.joinResponsePayload
+ |> take(1)
+ |> deliverOnMainQueue).start(next: { joinResponsePayload in
+ guard let strongSelf = self, let callContext = strongSelf.callContext, let ipcContext = strongSelf.ipcContext else {
+ return
+ }
+
+ callContext.setConnectionMode(.rtc, keepBroadcastConnectedIfWasEnabled: false)
+ callContext.setJoinResponse(payload: joinResponsePayload)
+
+ ipcContext.beginActiveIndication()
+ })
+ })
+ }*/
+
+ override public func broadcastPaused() {
+ }
+
+ override public func broadcastResumed() {
+ }
+
+ override public func broadcastFinished() {
+ }
+
+ override public func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
+ switch sampleBufferType {
+ case RPSampleBufferType.video:
+ processVideoSampleBuffer(sampleBuffer: sampleBuffer)
+ case RPSampleBufferType.audioApp:
+ break
+ case RPSampleBufferType.audioMic:
+ break
+ @unknown default:
+ break
+ }
+ }
+
+ private func processVideoSampleBuffer(sampleBuffer: CMSampleBuffer) {
+ guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
+ return
+ }
+
+ if let data = serializePixelBuffer(buffer: pixelBuffer) {
+ self.screencastBufferClientContext?.setCurrentFrame(data: data)
+ }
+
+ //self.videoCapturer?.injectSampleBuffer(sampleBuffer)
+ /*if CMSampleBufferGetNumSamples(sampleBuffer) != 1 {
+ return
+ }
+ if !CMSampleBufferIsValid(sampleBuffer) {
+ return
+ }
+ if !CMSampleBufferDataIsReady(sampleBuffer) {
+ return
+ }
+ guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else {
+ return
+ }
+
+ let pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer)
+
+ CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly)
+
+ CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly)*/
+ }
+}
diff --git a/Telegram/NotificationContent/NotificationViewController.swift b/Telegram/NotificationContent/NotificationViewController.swift
index d61ef5ffa2..56301732f9 100644
--- a/Telegram/NotificationContent/NotificationViewController.swift
+++ b/Telegram/NotificationContent/NotificationViewController.swift
@@ -38,7 +38,7 @@ class NotificationViewController: UIViewController, UNNotificationContentExtensi
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
- self.impl = NotificationViewControllerImpl(initializationData: NotificationViewControllerInitializationData(appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), setPreferredContentSize: { [weak self] size in
+ self.impl = NotificationViewControllerImpl(initializationData: NotificationViewControllerInitializationData(appBundleId: baseAppBundleId, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), setPreferredContentSize: { [weak self] size in
self?.preferredContentSize = size
})
}
diff --git a/Telegram/Share/ShareRootController.swift b/Telegram/Share/ShareRootController.swift
index c0da95155d..1c53d48b3e 100644
--- a/Telegram/Share/ShareRootController.swift
+++ b/Telegram/Share/ShareRootController.swift
@@ -45,7 +45,7 @@ class ShareRootController: UIViewController {
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
- self.impl = ShareRootControllerImpl(initializationData: ShareRootControllerInitializationData(appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), getExtensionContext: { [weak self] in
+ self.impl = ShareRootControllerImpl(initializationData: ShareRootControllerInitializationData(appBundleId: baseAppBundleId, appGroupPath: appGroupUrl.path, apiId: buildConfig.apiId, apiHash: buildConfig.apiHash, languagesCategory: languagesCategory, encryptionParameters: encryptionParameters, appVersion: appVersion, bundleData: buildConfig.bundleData(withAppToken: nil, signatureDict: nil)), getExtensionContext: { [weak self] in
return self?.extensionContext
})
}
diff --git a/Telegram/SiriIntents/Info.plist b/Telegram/SiriIntents/Info.plist
deleted file mode 100644
index d77820f815..0000000000
--- a/Telegram/SiriIntents/Info.plist
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- en
- CFBundleDisplayName
- ${APP_NAME}
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- XPC!
- CFBundleShortVersionString
- $(PRODUCT_BUNDLE_SHORT_VERSION)
- CFBundleVersion
- ${BUILD_NUMBER}
- NSExtension
-
- NSExtensionAttributes
-
- IntentsRestrictedWhileLocked
-
- IntentsRestrictedWhileProtectedDataUnavailable
-
- IntentsSupported
-
- INSendMessageIntent
- INStartAudioCallIntent
- INSearchForMessagesIntent
- INSetMessageAttributeIntent
- INSearchCallHistoryIntent
-
-
- NSExtensionPointIdentifier
- com.apple.intents-service
- NSExtensionPrincipalClass
- IntentHandler
-
-
-
diff --git a/Telegram/SupportFiles/Empty.swift b/Telegram/SupportFiles/Empty.swift
deleted file mode 100644
index 8b13789179..0000000000
--- a/Telegram/SupportFiles/Empty.swift
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/Telegram/Widget/Info.plist b/Telegram/Widget/Info.plist
deleted file mode 100644
index 6917b6ac76..0000000000
--- a/Telegram/Widget/Info.plist
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
- CFBundleDevelopmentRegion
- $(DEVELOPMENT_LANGUAGE)
- CFBundleDisplayName
- ${APP_NAME}
- CFBundleExecutable
- $(EXECUTABLE_NAME)
- CFBundleIdentifier
- $(PRODUCT_BUNDLE_IDENTIFIER)
- CFBundleInfoDictionaryVersion
- 6.0
- CFBundleName
- $(PRODUCT_NAME)
- CFBundlePackageType
- XPC!
- CFBundleShortVersionString
- $(PRODUCT_BUNDLE_SHORT_VERSION)
- CFBundleVersion
- ${BUILD_NUMBER}
- NSExtension
-
- NSExtensionPointIdentifier
- com.apple.widget-extension
- NSExtensionPrincipalClass
- TodayViewController
-
-
-
diff --git a/Telegram/Widget/PeerNode.swift b/Telegram/Widget/PeerNode.swift
deleted file mode 100644
index b89deba9e6..0000000000
--- a/Telegram/Widget/PeerNode.swift
+++ /dev/null
@@ -1,172 +0,0 @@
-import Foundation
-import UIKit
-import WidgetItems
-
-private extension UIColor {
- convenience init(rgb: UInt32) {
- self.init(red: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: 1.0)
- }
-}
-
-private let UIScreenScale = UIScreen.main.scale
-private func floorToScreenPixels(_ value: CGFloat) -> CGFloat {
- return floor(value * UIScreenScale) / UIScreenScale
-}
-
-private let gradientColors: [NSArray] = [
- [UIColor(rgb: 0xff516a).cgColor, UIColor(rgb: 0xff885e).cgColor],
- [UIColor(rgb: 0xffa85c).cgColor, UIColor(rgb: 0xffcd6a).cgColor],
- [UIColor(rgb: 0x665fff).cgColor, UIColor(rgb: 0x82b1ff).cgColor],
- [UIColor(rgb: 0x54cb68).cgColor, UIColor(rgb: 0xa0de7e).cgColor],
- [UIColor(rgb: 0x4acccd).cgColor, UIColor(rgb: 0x00fcfd).cgColor],
- [UIColor(rgb: 0x2a9ef1).cgColor, UIColor(rgb: 0x72d5fd).cgColor],
- [UIColor(rgb: 0xd669ed).cgColor, UIColor(rgb: 0xe0a2f3).cgColor],
-]
-
-private func avatarRoundImage(size: CGSize, source: UIImage) -> UIImage? {
- UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
- let context = UIGraphicsGetCurrentContext()
-
- context?.beginPath()
- context?.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
- context?.clip()
-
- source.draw(in: CGRect(origin: CGPoint(), size: size))
-
- let image = UIGraphicsGetImageFromCurrentImageContext()
- UIGraphicsEndImageContext()
- return image
-}
-
-private let deviceColorSpace: CGColorSpace = {
- if #available(iOSApplicationExtension 9.3, *) {
- if let colorSpace = CGColorSpace(name: CGColorSpace.displayP3) {
- return colorSpace
- } else {
- return CGColorSpaceCreateDeviceRGB()
- }
- } else {
- return CGColorSpaceCreateDeviceRGB()
- }
-}()
-
-private func avatarViewLettersImage(size: CGSize, peerId: Int64, accountPeerId: Int64, letters: [String]) -> UIImage? {
- UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
- let context = UIGraphicsGetCurrentContext()
-
- context?.beginPath()
- context?.addEllipse(in: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height))
- context?.clip()
-
- let colorIndex = abs(Int(accountPeerId + peerId))
-
- let colorsArray = gradientColors[colorIndex % gradientColors.count]
- var locations: [CGFloat] = [1.0, 0.0]
- let gradient = CGGradient(colorsSpace: deviceColorSpace, colors: colorsArray, locations: &locations)!
-
- context?.drawLinearGradient(gradient, start: CGPoint(), end: CGPoint(x: 0.0, y: size.height), options: CGGradientDrawingOptions())
-
- context?.setBlendMode(.normal)
-
- let string = letters.count == 0 ? "" : (letters[0] + (letters.count == 1 ? "" : letters[1]))
- let attributedString = NSAttributedString(string: string, attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20.0), NSAttributedString.Key.foregroundColor: UIColor.white])
-
- let line = CTLineCreateWithAttributedString(attributedString)
- let lineBounds = CTLineGetBoundsWithOptions(line, .useGlyphPathBounds)
-
- let lineOffset = CGPoint(x: string == "B" ? 1.0 : 0.0, y: 0.0)
- let lineOrigin = CGPoint(x: floorToScreenPixels(-lineBounds.origin.x + (size.width - lineBounds.size.width) / 2.0) + lineOffset.x, y: floorToScreenPixels(-lineBounds.origin.y + (size.height - lineBounds.size.height) / 2.0))
-
- context?.translateBy(x: size.width / 2.0, y: size.height / 2.0)
- context?.scaleBy(x: 1.0, y: -1.0)
- context?.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
-
- context?.translateBy(x: lineOrigin.x, y: lineOrigin.y)
- if let context = context {
- CTLineDraw(line, context)
- }
- context?.translateBy(x: -lineOrigin.x, y: -lineOrigin.y)
-
- let image = UIGraphicsGetImageFromCurrentImageContext()
- UIGraphicsEndImageContext()
- return image
-}
-
-private let avatarSize = CGSize(width: 50.0, height: 50.0)
-
-private final class AvatarView: UIImageView {
- init(accountPeerId: Int64, peer: WidgetDataPeer, size: CGSize) {
- super.init(frame: CGRect())
-
- if let path = peer.avatarPath, let image = UIImage(contentsOfFile: path), let roundImage = avatarRoundImage(size: size, source: image) {
- self.image = roundImage
- } else {
- self.image = avatarViewLettersImage(size: size, peerId: peer.id, accountPeerId: accountPeerId, letters: peer.letters)
- }
- }
-
- required init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-}
-
-final class PeerView: UIView {
- let peer: WidgetDataPeer
- private let avatarView: AvatarView
- private let titleLabel: UILabel
-
- private let tapped: () -> Void
-
- init(primaryColor: UIColor, accountPeerId: Int64, peer: WidgetDataPeer, tapped: @escaping () -> Void) {
- self.peer = peer
- self.tapped = tapped
- self.avatarView = AvatarView(accountPeerId: accountPeerId, peer: peer, size: avatarSize)
-
- self.titleLabel = UILabel()
- var title = peer.name
- if let lastName = peer.lastName, !lastName.isEmpty {
- title.append("\n")
- title.append(lastName)
- }
-
- let systemFontSize = UIFont.preferredFont(forTextStyle: .body).pointSize
- let fontSize = floor(systemFontSize * 11.0 / 17.0)
-
- self.titleLabel.text = title
- if #available(iOSApplicationExtension 13.0, *) {
- self.titleLabel.textColor = UIColor.label
- } else {
- self.titleLabel.textColor = primaryColor
- }
- self.titleLabel.font = UIFont.systemFont(ofSize: fontSize)
- self.titleLabel.lineBreakMode = .byTruncatingTail
- self.titleLabel.numberOfLines = 2
- self.titleLabel.textAlignment = .center
-
- super.init(frame: CGRect())
-
- self.addSubview(self.avatarView)
- self.addSubview(self.titleLabel)
-
- self.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))))
- }
-
- required init?(coder aDecoder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- func updateLayout(size: CGSize) {
- self.avatarView.frame = CGRect(origin: CGPoint(x: floor((size.width - avatarSize.width) / 2.0), y: 0.0), size: avatarSize)
-
- var titleSize = self.titleLabel.sizeThatFits(size)
- titleSize.width = min(size.width - 6.0, ceil(titleSize.width))
- titleSize.height = ceil(titleSize.height)
- self.titleLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: avatarSize.height + 5.0), size: titleSize)
- }
-
- @objc func tapGesture(_ recognizer: UITapGestureRecognizer) {
- if case .ended = recognizer.state {
- self.tapped()
- }
- }
-}
diff --git a/Telegram/Widget/TodayViewController.swift b/Telegram/Widget/TodayViewController.swift
deleted file mode 100644
index 454691e89d..0000000000
--- a/Telegram/Widget/TodayViewController.swift
+++ /dev/null
@@ -1,171 +0,0 @@
-import UIKit
-import NotificationCenter
-import BuildConfig
-import WidgetItems
-import AppLockState
-
-private func rootPathForBasePath(_ appGroupPath: String) -> String {
- return appGroupPath + "/telegram-data"
-}
-
-@objc(TodayViewController)
-class TodayViewController: UIViewController, NCWidgetProviding {
- private var initializedInterface = false
-
- private var buildConfig: BuildConfig?
-
- private var primaryColor: UIColor = .black
- private var placeholderLabel: UILabel?
-
- override func viewDidLoad() {
- super.viewDidLoad()
-
- let appBundleIdentifier = Bundle.main.bundleIdentifier!
- guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
- return
- }
- let baseAppBundleId = String(appBundleIdentifier[.. Void)) {
- completionHandler(.newData)
- }
-
- @available(iOSApplicationExtension 10.0, *)
- func widgetActiveDisplayModeDidChange(_ activeDisplayMode: NCWidgetDisplayMode, withMaximumSize maxSize: CGSize) {
-
- }
-
- private var widgetData: WidgetData?
-
- private func setWidgetData(widgetData: WidgetData, presentationData: WidgetPresentationData) {
- self.widgetData = widgetData
- self.peerViews.forEach {
- $0.removeFromSuperview()
- }
- self.peerViews = []
- switch widgetData {
- case .notAuthorized, .disabled:
- break
- case let .peers(peers):
- for peer in peers.peers {
- let peerView = PeerView(primaryColor: self.primaryColor, accountPeerId: peers.accountPeerId, peer: peer, tapped: { [weak self] in
- if let strongSelf = self, let buildConfig = strongSelf.buildConfig {
- if let url = URL(string: "\(buildConfig.appSpecificUrlScheme)://localpeer?id=\(peer.id)") {
- strongSelf.extensionContext?.open(url, completionHandler: nil)
- }
- }
- })
- self.view.addSubview(peerView)
- self.peerViews.append(peerView)
- }
- }
-
- if self.peerViews.isEmpty {
- self.setPlaceholderText(presentationData.applicationStartRequiredString)
- } else {
- self.placeholderLabel?.removeFromSuperview()
- self.placeholderLabel = nil
- }
-
- if let size = self.validLayout {
- self.updateLayout(size: size)
- }
- }
-
- private var validLayout: CGSize?
-
- private var peerViews: [PeerView] = []
-
- override func viewDidLayoutSubviews() {
- super.viewDidLayoutSubviews()
-
- self.updateLayout(size: self.view.bounds.size)
- }
-
- private func updateLayout(size: CGSize) {
- self.validLayout = size
-
- if let placeholderLabel = self.placeholderLabel {
- placeholderLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - placeholderLabel.bounds.width) / 2.0), y: floor((size.height - placeholderLabel.bounds.height) / 2.0)), size: placeholderLabel.bounds.size)
- }
-
- let peerSize = CGSize(width: 70.0, height: 100.0)
-
- var peerFrames: [CGRect] = []
-
- var offset: CGFloat = 0.0
- for _ in self.peerViews {
- let peerFrame = CGRect(origin: CGPoint(x: offset, y: 10.0), size: peerSize)
- offset += peerFrame.size.width
- if peerFrame.maxX > size.width {
- break
- }
- peerFrames.append(peerFrame)
- }
-
- var totalSize: CGFloat = 0.0
- for i in 0 ..< peerFrames.count {
- totalSize += peerFrames[i].width
- }
-
- let spacing: CGFloat = floor((size.width - totalSize) / CGFloat(peerFrames.count))
- offset = floor(spacing / 2.0)
- for i in 0 ..< peerFrames.count {
- let peerView = self.peerViews[i]
- peerView.frame = CGRect(origin: CGPoint(x: offset, y: 16.0), size: peerFrames[i].size)
- peerView.updateLayout(size: peerFrames[i].size)
- offset += peerFrames[i].width + spacing
- }
- }
-}
diff --git a/Telegram/Widget/Widget-Bridging-Header.h b/Telegram/Widget/Widget-Bridging-Header.h
deleted file mode 100644
index 16747def3f..0000000000
--- a/Telegram/Widget/Widget-Bridging-Header.h
+++ /dev/null
@@ -1,4 +0,0 @@
-#ifndef Widget_Bridging_Header_h
-#define Widget_Bridging_Header_h
-
-#endif
diff --git a/Telegram/Widget/ar.lproj/InfoPlist.strings b/Telegram/Widget/ar.lproj/InfoPlist.strings
deleted file mode 100644
index 07394dd6c9..0000000000
--- a/Telegram/Widget/ar.lproj/InfoPlist.strings
+++ /dev/null
@@ -1 +0,0 @@
-"CFBundleDisplayName" = "الأشخاص";
diff --git a/Telegram/Widget/de.lproj/InfoPlist.strings b/Telegram/Widget/de.lproj/InfoPlist.strings
deleted file mode 100644
index 1ad24433c2..0000000000
--- a/Telegram/Widget/de.lproj/InfoPlist.strings
+++ /dev/null
@@ -1 +0,0 @@
-"CFBundleDisplayName" = "Leute";
diff --git a/Telegram/Widget/en.lproj/InfoPlist.strings b/Telegram/Widget/en.lproj/InfoPlist.strings
deleted file mode 100644
index b1ddd1ac39..0000000000
--- a/Telegram/Widget/en.lproj/InfoPlist.strings
+++ /dev/null
@@ -1 +0,0 @@
-"CFBundleDisplayName" = "People";
diff --git a/Telegram/Widget/en.lproj/Localizable.strings b/Telegram/Widget/en.lproj/Localizable.strings
deleted file mode 100644
index c90696d0fb..0000000000
--- a/Telegram/Widget/en.lproj/Localizable.strings
+++ /dev/null
@@ -1,2 +0,0 @@
-"Widget.NoUsers" = "No users here yet...";
-"Widget.AuthRequired" = "Open Telegram and log in.";
diff --git a/Telegram/Widget/es.lproj/InfoPlist.strings b/Telegram/Widget/es.lproj/InfoPlist.strings
deleted file mode 100644
index 3d5094963a..0000000000
--- a/Telegram/Widget/es.lproj/InfoPlist.strings
+++ /dev/null
@@ -1 +0,0 @@
-"CFBundleDisplayName" = "Personas";
diff --git a/Telegram/Widget/it.lproj/InfoPlist.strings b/Telegram/Widget/it.lproj/InfoPlist.strings
deleted file mode 100644
index f118d25a4d..0000000000
--- a/Telegram/Widget/it.lproj/InfoPlist.strings
+++ /dev/null
@@ -1 +0,0 @@
-"CFBundleDisplayName" = "Persone";
diff --git a/Telegram/Widget/ko.lproj/InfoPlist.strings b/Telegram/Widget/ko.lproj/InfoPlist.strings
deleted file mode 100644
index e1bc831c53..0000000000
--- a/Telegram/Widget/ko.lproj/InfoPlist.strings
+++ /dev/null
@@ -1 +0,0 @@
-"CFBundleDisplayName" = "사람";
diff --git a/Telegram/Widget/nl.lproj/InfoPlist.strings b/Telegram/Widget/nl.lproj/InfoPlist.strings
deleted file mode 100644
index a23cbfc4a2..0000000000
--- a/Telegram/Widget/nl.lproj/InfoPlist.strings
+++ /dev/null
@@ -1 +0,0 @@
-"CFBundleDisplayName" = "Mensen";
diff --git a/Telegram/Widget/pt.lproj/InfoPlist.strings b/Telegram/Widget/pt.lproj/InfoPlist.strings
deleted file mode 100644
index a6c032d0ed..0000000000
--- a/Telegram/Widget/pt.lproj/InfoPlist.strings
+++ /dev/null
@@ -1 +0,0 @@
-"CFBundleDisplayName" = "Pessoas";
diff --git a/Telegram/Widget/ru.lproj/InfoPlist.strings b/Telegram/Widget/ru.lproj/InfoPlist.strings
deleted file mode 100644
index 689e714f47..0000000000
--- a/Telegram/Widget/ru.lproj/InfoPlist.strings
+++ /dev/null
@@ -1 +0,0 @@
-"CFBundleDisplayName" = "Люди";
diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift
index 4968ec27ed..7f2652cf5c 100644
--- a/submodules/AccountContext/Sources/AccountContext.swift
+++ b/submodules/AccountContext/Sources/AccountContext.swift
@@ -30,6 +30,7 @@ public enum AccessType {
public final class TelegramApplicationBindings {
public let isMainApp: Bool
+ public let appBundleId: String
public let containerPath: String
public let appSpecificScheme: String
public let openUrl: (String) -> Void
@@ -54,8 +55,9 @@ public final class TelegramApplicationBindings {
public let requestSetAlternateIconName: (String?, @escaping (Bool) -> Void) -> Void
public let forceOrientation: (UIInterfaceOrientation) -> Void
- public init(isMainApp: Bool, containerPath: String, appSpecificScheme: String, openUrl: @escaping (String) -> Void, openUniversalUrl: @escaping (String, TelegramApplicationOpenUrlCompletion) -> Void, canOpenUrl: @escaping (String) -> Bool, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void, applicationInForeground: Signal, applicationIsActive: Signal, clearMessageNotifications: @escaping ([MessageId]) -> Void, pushIdleTimerExtension: @escaping () -> Disposable, openSettings: @escaping () -> Void, openAppStorePage: @escaping () -> Void, registerForNotifications: @escaping (@escaping (Bool) -> Void) -> Void, requestSiriAuthorization: @escaping (@escaping (Bool) -> Void) -> Void, siriAuthorization: @escaping () -> AccessType, getWindowHost: @escaping () -> WindowHost?, presentNativeController: @escaping (UIViewController) -> Void, dismissNativeController: @escaping () -> Void, getAvailableAlternateIcons: @escaping () -> [PresentationAppIcon], getAlternateIconName: @escaping () -> String?, requestSetAlternateIconName: @escaping (String?, @escaping (Bool) -> Void) -> Void, forceOrientation: @escaping (UIInterfaceOrientation) -> Void) {
+ public init(isMainApp: Bool, appBundleId: String, containerPath: String, appSpecificScheme: String, openUrl: @escaping (String) -> Void, openUniversalUrl: @escaping (String, TelegramApplicationOpenUrlCompletion) -> Void, canOpenUrl: @escaping (String) -> Bool, getTopWindow: @escaping () -> UIWindow?, displayNotification: @escaping (String) -> Void, applicationInForeground: Signal, applicationIsActive: Signal, clearMessageNotifications: @escaping ([MessageId]) -> Void, pushIdleTimerExtension: @escaping () -> Disposable, openSettings: @escaping () -> Void, openAppStorePage: @escaping () -> Void, registerForNotifications: @escaping (@escaping (Bool) -> Void) -> Void, requestSiriAuthorization: @escaping (@escaping (Bool) -> Void) -> Void, siriAuthorization: @escaping () -> AccessType, getWindowHost: @escaping () -> WindowHost?, presentNativeController: @escaping (UIViewController) -> Void, dismissNativeController: @escaping () -> Void, getAvailableAlternateIcons: @escaping () -> [PresentationAppIcon], getAlternateIconName: @escaping () -> String?, requestSetAlternateIconName: @escaping (String?, @escaping (Bool) -> Void) -> Void, forceOrientation: @escaping (UIInterfaceOrientation) -> Void) {
self.isMainApp = isMainApp
+ self.appBundleId = appBundleId
self.containerPath = containerPath
self.appSpecificScheme = appSpecificScheme
self.openUrl = openUrl
@@ -544,6 +546,7 @@ public protocol RecentSessionsController: class {
}
public protocol SharedAccountContext: class {
+ var sharedContainerPath: String { get }
var basePath: String { get }
var mainWindow: Window1? { get }
var accountManager: AccountManager { get }
diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift
index c98e57d1d8..dfed87e9b2 100644
--- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift
+++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift
@@ -450,7 +450,13 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private var genericCallContext: OngoingGroupCallContext?
private var currentConnectionMode: OngoingGroupCallContext.ConnectionMode = .none
+
private var screencastCallContext: OngoingGroupCallContext?
+ private var screencastBufferServerContext: IpcGroupCallBufferAppContext?
+ private var screencastCapturer: OngoingCallVideoCapturer?
+
+ //private var screencastIpcContext: IpcGroupCallAppContext?
+
private var ssrcMapping: [UInt32: PeerId] = [:]
private var requestedSsrcs = Set()
@@ -620,8 +626,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private var videoCapturer: OngoingCallVideoCapturer?
private var useFrontCamera: Bool = true
- private var screenCapturer: OngoingCallVideoCapturer?
-
private let incomingVideoSourcePromise = Promise>(Set())
public var incomingVideoSources: Signal, NoError> {
return self.incomingVideoSourcePromise.get()
@@ -632,6 +636,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
public private(set) var schedulePending = false
private var isScheduled = false
private var isScheduledStarted = false
+
+ private var screencastFramesDisposable: Disposable?
+ private var screencastStateDisposable: Disposable?
init(
accountContext: AccountContext,
@@ -891,6 +898,38 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
if let _ = self.initialCall {
self.requestCall(movingFromBroadcastToRtc: false)
}
+
+ let basePath = self.accountContext.sharedContext.basePath + "/broadcast-coordination"
+ let screencastBufferServerContext = IpcGroupCallBufferAppContext(basePath: basePath)
+ self.screencastBufferServerContext = screencastBufferServerContext
+ let screencastCapturer = OngoingCallVideoCapturer(isCustom: true)
+ self.screencastCapturer = screencastCapturer
+ self.screencastFramesDisposable = (screencastBufferServerContext.frames
+ |> deliverOnMainQueue).start(next: { [weak screencastCapturer] screencastFrame in
+ guard let screencastCapturer = screencastCapturer else {
+ return
+ }
+ screencastCapturer.injectPixelBuffer(screencastFrame)
+ })
+ self.screencastStateDisposable = (screencastBufferServerContext.isActive
+ |> distinctUntilChanged
+ |> deliverOnMainQueue).start(next: { [weak self] isActive in
+ guard let strongSelf = self else {
+ return
+ }
+ if isActive {
+ strongSelf.requestScreencast()
+ } else {
+ strongSelf.disableScreencast()
+ }
+ })
+
+ /*Queue.mainQueue().after(2.0, { [weak self] in
+ guard let strongSelf = self else {
+ return
+ }
+ strongSelf.screencastBufferClientContext = IpcGroupCallBufferBroadcastContext(basePath: basePath)
+ })*/
}
deinit {
@@ -929,6 +968,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.removedChannelMembersDisposable?.dispose()
self.peerUpdatesSubscription?.dispose()
+
+ self.screencastFramesDisposable?.dispose()
+ self.screencastStateDisposable?.dispose()
}
private func switchToTemporaryParticipantsContext(sourceContext: GroupCallParticipantsContext?, oldMyPeerId: PeerId) {
@@ -2139,7 +2181,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.markedAsCanBeRemoved = true
self.genericCallContext?.stop()
+
+ //self.screencastIpcContext = nil
self.screencastCallContext?.stop()
+
self._canBeRemoved.set(.single(true))
if self.didConnectOnce {
@@ -2493,7 +2538,87 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
return
}
- if self.screenCapturer == nil {
+ let screencastCallContext = OngoingGroupCallContext(video: self.screencastCapturer, requestMediaChannelDescriptions: { _, _ in EmptyDisposable }, audioStreamData: nil, rejoinNeeded: { }, outgoingAudioBitrateKbit: nil, videoContentType: .screencast, enableNoiseSuppression: false)
+ self.screencastCallContext = screencastCallContext
+
+ self.screencastJoinDisposable.set((screencastCallContext.joinPayload
+ |> take(1)
+ |> deliverOnMainQueue).start(next: { [weak self] joinPayload in
+ guard let strongSelf = self else {
+ return
+ }
+
+ strongSelf.requestDisposable.set((joinGroupCallAsScreencast(
+ account: strongSelf.account,
+ peerId: strongSelf.peerId,
+ callId: callInfo.id,
+ accessHash: callInfo.accessHash,
+ joinPayload: joinPayload.0
+ )
+ |> deliverOnMainQueue).start(next: { joinCallResult in
+ guard let strongSelf = self, let screencastCallContext = strongSelf.screencastCallContext else {
+ return
+ }
+ let clientParams = joinCallResult.jsonParams
+
+ //screencastIpcContext.setJoinResponsePayload(clientParams)
+
+ screencastCallContext.setConnectionMode(.rtc, keepBroadcastConnectedIfWasEnabled: false)
+ screencastCallContext.setJoinResponse(payload: clientParams)
+
+ strongSelf.genericCallContext?.setIgnoreVideoEndpointIds(endpointIds: [joinCallResult.endpointId])
+ }, error: { error in
+ guard let _ = self else {
+ return
+ }
+ }))
+ }))
+
+ /*if self.screencastIpcContext != nil {
+ return
+ }
+
+ let maybeCallInfo: GroupCallInfo? = self.internalState.callInfo
+
+ guard let callInfo = maybeCallInfo else {
+ return
+ }
+
+ let screencastIpcContext = IpcGroupCallAppContext(basePath: self.accountContext.sharedContext.basePath + "/broadcast-coordination")
+ self.screencastIpcContext = screencastIpcContext
+ self.hasScreencast = true
+
+ self.screencastJoinDisposable.set((screencastIpcContext.joinPayload
+ |> take(1)
+ |> deliverOnMainQueue).start(next: { [weak self] joinPayload in
+ guard let strongSelf = self else {
+ return
+ }
+
+ strongSelf.requestDisposable.set((joinGroupCallAsScreencast(
+ account: strongSelf.account,
+ peerId: strongSelf.peerId,
+ callId: callInfo.id,
+ accessHash: callInfo.accessHash,
+ joinPayload: joinPayload
+ )
+ |> deliverOnMainQueue).start(next: { joinCallResult in
+ guard let strongSelf = self, let screencastIpcContext = strongSelf.screencastIpcContext else {
+ return
+ }
+ let clientParams = joinCallResult.jsonParams
+
+ screencastIpcContext.setJoinResponsePayload(clientParams)
+
+ strongSelf.genericCallContext?.setIgnoreVideoEndpointIds(endpointIds: [joinCallResult.endpointId])
+ }, error: { error in
+ guard let _ = self else {
+ return
+ }
+ }))
+ }))
+
+ /*if self.screenCapturer == nil {
let screenCapturer = OngoingCallVideoCapturer()
self.screenCapturer = screenCapturer
}
@@ -2512,7 +2637,6 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
)
self.screencastCallContext = screencastCallContext
- self.hasScreencast = true
self.screencastJoinDisposable.set((screencastCallContext.joinPayload
|> distinctUntilChanged(isEqual: { lhs, rhs in
@@ -2551,7 +2675,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
return
}
}))
- }))
+ }))*/*/
}
public func disableScreencast() {
@@ -2570,10 +2694,19 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
).start())
}
}
- if let _ = self.screenCapturer {
- self.screenCapturer = nil
- self.screencastCallContext?.disableVideo()
- }
+ /*if let _ = self.screencastIpcContext {
+ self.screencastIpcContext = nil
+
+ let maybeCallInfo: GroupCallInfo? = self.internalState.callInfo
+
+ if let callInfo = maybeCallInfo {
+ self.screencastJoinDisposable.set(leaveGroupCallAsScreencast(
+ account: self.account,
+ callId: callInfo.id,
+ accessHash: callInfo.accessHash
+ ).start())
+ }
+ }*/
}
public func setVolume(peerId: PeerId, volume: Int32, sync: Bool) {
diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift
index a0b59a8edf..0970ec6cd9 100644
--- a/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift
+++ b/submodules/TelegramCallsUI/Sources/VoiceChatCameraPreviewController.swift
@@ -11,6 +11,7 @@ import TelegramPresentationData
import SolidRoundedButtonNode
import PresentationDataUtils
import UIKitRuntimeUtils
+import ReplayKit
final class VoiceChatCameraPreviewController: ViewController {
private var controllerNode: VoiceChatCameraPreviewControllerNode {
@@ -60,7 +61,7 @@ final class VoiceChatCameraPreviewController: ViewController {
}
override public func loadDisplayNode() {
- self.displayNode = VoiceChatCameraPreviewControllerNode(context: self.context, cameraNode: self.cameraNode)
+ self.displayNode = VoiceChatCameraPreviewControllerNode(controller: self, context: self.context, cameraNode: self.cameraNode)
self.controllerNode.shareCamera = { [weak self] in
if let strongSelf = self {
strongSelf.shareCamera(strongSelf.cameraNode)
@@ -107,6 +108,7 @@ final class VoiceChatCameraPreviewController: ViewController {
}
private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, UIScrollViewDelegate {
+ private weak var controller: VoiceChatCameraPreviewController?
private let context: AccountContext
private var presentationData: PresentationData
@@ -121,6 +123,7 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
private let previewContainerNode: ASDisplayNode
private let cameraButton: SolidRoundedButtonNode
private let screenButton: SolidRoundedButtonNode
+ private var broadcastPickerView: UIView?
private let cancelButton: SolidRoundedButtonNode
private let switchCameraButton: HighlightTrackingButtonNode
@@ -128,6 +131,8 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
private let switchCameraIconNode: ASImageNode
private var containerLayout: (ContainerViewLayout, CGFloat)?
+
+ private var applicationStateDisposable: Disposable?
var shareCamera: (() -> Void)?
var switchCamera: (() -> Void)?
@@ -135,7 +140,8 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
var dismiss: (() -> Void)?
var cancel: (() -> Void)?
- init(context: AccountContext, cameraNode: GroupVideoNode) {
+ init(controller: VoiceChatCameraPreviewController, context: AccountContext, cameraNode: GroupVideoNode) {
+ self.controller = controller
self.context = context
self.presentationData = context.sharedContext.currentPresentationData.with { $0 }
@@ -179,6 +185,14 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
self.screenButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: buttonTextColor), font: .bold, height: 52.0, cornerRadius: 11.0, gloss: false)
self.screenButton.title = self.presentationData.strings.VoiceChat_VideoPreviewShareScreen
+
+ if #available(iOS 12.0, *) {
+ let broadcastPickerView = RPSystemBroadcastPickerView(frame: CGRect(x: 0, y: 0, width: 50, height: 52.0))
+ broadcastPickerView.alpha = 0.1
+ broadcastPickerView.preferredExtension = "\(self.context.sharedContext.applicationBindings.appBundleId).BroadcastUpload"
+ broadcastPickerView.showsMicrophoneButton = false
+ self.broadcastPickerView = broadcastPickerView
+ }
self.cancelButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: buttonTextColor), font: .regular, height: 52.0, cornerRadius: 11.0, gloss: false)
self.cancelButton.title = self.presentationData.strings.Common_Cancel
@@ -217,6 +231,9 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
self.contentContainerNode.addSubnode(self.titleNode)
self.contentContainerNode.addSubnode(self.cameraButton)
self.contentContainerNode.addSubnode(self.screenButton)
+ if let broadcastPickerView = self.broadcastPickerView {
+ self.contentContainerNode.view.addSubview(broadcastPickerView)
+ }
self.contentContainerNode.addSubnode(self.cancelButton)
self.contentContainerNode.addSubnode(self.previewContainerNode)
@@ -292,6 +309,16 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
transition.animateView({
self.bounds = targetBounds
})
+
+ self.applicationStateDisposable = (self.context.sharedContext.applicationBindings.applicationIsActive
+ |> filter { !$0 }
+ |> take(1)
+ |> deliverOnMainQueue).start(next: { [weak self] _ in
+ guard let strongSelf = self else {
+ return
+ }
+ strongSelf.controller?.dismiss()
+ })
}
func animateOut(completion: (() -> Void)? = nil) {
@@ -390,6 +417,9 @@ private class VoiceChatCameraPreviewControllerNode: ViewControllerTracingNode, U
let screenButtonHeight = self.screenButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.screenButton, frame: CGRect(x: buttonInset, y: contentHeight - cameraButtonHeight - 8.0 - screenButtonHeight - insets.bottom - 16.0, width: contentFrame.width, height: screenButtonHeight))
+ if let broadcastPickerView = self.broadcastPickerView {
+ broadcastPickerView.frame = CGRect(x: buttonInset, y: contentHeight - cameraButtonHeight - 8.0 - screenButtonHeight - insets.bottom - 16.0, width: contentFrame.width + 1000.0, height: screenButtonHeight)
+ }
let cancelButtonHeight = self.cancelButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition)
transition.updateFrame(node: self.cancelButton, frame: CGRect(x: buttonInset, y: contentHeight - cancelButtonHeight - insets.bottom - 16.0, width: contentFrame.width, height: cancelButtonHeight))
diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift
index bbf9d4a790..1dabf3860a 100644
--- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift
+++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift
@@ -3345,6 +3345,10 @@ public final class VoiceChatController: ViewController {
}, switchCamera: { [weak self] in
self?.call.switchVideoCamera()
}, shareScreen: { [weak self] in
+ guard let strongSelf = self else {
+ return
+ }
+
self?.call.requestScreencast()
})
strongSelf.controller?.present(controller, in: .window(.root))
diff --git a/submodules/TelegramCore/Sources/GroupCalls.swift b/submodules/TelegramCore/Sources/GroupCalls.swift
index 0ed12b5871..8cd8993402 100644
--- a/submodules/TelegramCore/Sources/GroupCalls.swift
+++ b/submodules/TelegramCore/Sources/GroupCalls.swift
@@ -382,19 +382,19 @@ public enum GetGroupCallParticipantsError {
}
public func getGroupCallParticipants(account: Account, callId: Int64, accessHash: Int64, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal {
- let sortAscendingValue: Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?), GetGroupCallParticipantsError>
+ let sortAscendingValue: Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool), GetGroupCallParticipantsError>
if let sortAscending = sortAscending {
- sortAscendingValue = .single((sortAscending, nil, false, nil))
+ sortAscendingValue = .single((sortAscending, nil, false, nil, false))
} else {
sortAscendingValue = getCurrentGroupCall(account: account, callId: callId, accessHash: accessHash)
|> mapError { _ -> GetGroupCallParticipantsError in
return .generic
}
- |> mapToSignal { result -> Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?), GetGroupCallParticipantsError> in
+ |> mapToSignal { result -> Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool), GetGroupCallParticipantsError> in
guard let result = result else {
return .fail(.generic)
}
- return .single((result.info.sortAscending, result.info.scheduleTimestamp, result.info.subscribedToScheduled, result.info.defaultParticipantsAreMuted))
+ return .single((result.info.sortAscending, result.info.scheduleTimestamp, result.info.subscribedToScheduled, result.info.defaultParticipantsAreMuted, result.info.isVideoEnabled))
}
}
@@ -412,7 +412,8 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
let version: Int32
let nextParticipantsFetchOffset: String?
- let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted) = sortAscendingAndScheduleTimestamp
+ let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, isVideoEnabled) = sortAscendingAndScheduleTimestamp
+
switch result {
case let .groupParticipants(count, participants, nextOffset, chats, users, apiVersion):
@@ -510,7 +511,7 @@ public func getGroupCallParticipants(account: Account, callId: Int64, accessHash
scheduleTimestamp: scheduleTimestamp,
subscribedToScheduled: subscribedToScheduled,
totalCount: totalCount,
- isVideoEnabled: false,
+ isVideoEnabled: isVideoEnabled,
version: version
)
}
diff --git a/submodules/TelegramUI/Sources/AppDelegate.swift b/submodules/TelegramUI/Sources/AppDelegate.swift
index 9b35f62a39..91b4141efd 100644
--- a/submodules/TelegramUI/Sources/AppDelegate.swift
+++ b/submodules/TelegramUI/Sources/AppDelegate.swift
@@ -501,7 +501,7 @@ final class SharedApplicationContext {
initializeAccountManagement()
- let applicationBindings = TelegramApplicationBindings(isMainApp: true, containerPath: appGroupUrl.path, appSpecificScheme: buildConfig.appSpecificUrlScheme, openUrl: { url in
+ let applicationBindings = TelegramApplicationBindings(isMainApp: true, appBundleId: baseAppBundleId, containerPath: appGroupUrl.path, appSpecificScheme: buildConfig.appSpecificUrlScheme, openUrl: { url in
var parsedUrl = URL(string: url)
if let parsed = parsedUrl {
if parsed.scheme == nil || parsed.scheme!.isEmpty {
@@ -793,7 +793,7 @@ final class SharedApplicationContext {
})
var setPresentationCall: ((PresentationCall?) -> Void)?
- let sharedContext = SharedAccountContextImpl(mainWindow: self.mainWindow, basePath: rootPath, encryptionParameters: encryptionParameters, accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings, networkArguments: networkArguments, rootPath: rootPath, legacyBasePath: legacyBasePath, legacyCache: legacyCache, apsNotificationToken: self.notificationTokenPromise.get() |> map(Optional.init), voipNotificationToken: self.voipTokenPromise.get() |> map(Optional.init), setNotificationCall: { call in
+ let sharedContext = SharedAccountContextImpl(mainWindow: self.mainWindow, sharedContainerPath: legacyBasePath, basePath: rootPath, encryptionParameters: encryptionParameters, accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings, networkArguments: networkArguments, rootPath: rootPath, legacyBasePath: legacyBasePath, legacyCache: legacyCache, apsNotificationToken: self.notificationTokenPromise.get() |> map(Optional.init), voipNotificationToken: self.voipTokenPromise.get() |> map(Optional.init), setNotificationCall: { call in
setPresentationCall?(call)
}, navigateToChat: { accountId, peerId, messageId in
self.openChatWhenReady(accountId: accountId, peerId: peerId, messageId: messageId)
diff --git a/submodules/TelegramUI/Sources/NotificationContentContext.swift b/submodules/TelegramUI/Sources/NotificationContentContext.swift
index abaf8b3c3a..200f8467b0 100644
--- a/submodules/TelegramUI/Sources/NotificationContentContext.swift
+++ b/submodules/TelegramUI/Sources/NotificationContentContext.swift
@@ -33,6 +33,7 @@ private func setupSharedLogger(rootPath: String, path: String) {
}
public struct NotificationViewControllerInitializationData {
+ public let appBundleId: String
public let appGroupPath: String
public let apiId: Int32
public let apiHash: String
@@ -41,7 +42,8 @@ public struct NotificationViewControllerInitializationData {
public let appVersion: String
public let bundleData: Data?
- public init(appGroupPath: String, apiId: Int32, apiHash: String, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?) {
+ public init(appBundleId: String, appGroupPath: String, apiId: Int32, apiHash: String, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?) {
+ self.appBundleId = appBundleId
self.appGroupPath = appGroupPath
self.apiId = apiId
self.apiHash = apiHash
@@ -103,7 +105,7 @@ public final class NotificationViewControllerImpl {
})
semaphore.wait()
- let applicationBindings = TelegramApplicationBindings(isMainApp: false, containerPath: self.initializationData.appGroupPath, appSpecificScheme: "tgapp", openUrl: { _ in
+ let applicationBindings = TelegramApplicationBindings(isMainApp: false, appBundleId: self.initializationData.appBundleId, containerPath: self.initializationData.appGroupPath, appSpecificScheme: "tgapp", openUrl: { _ in
}, openUniversalUrl: { _, completion in
completion.completion(false)
return
@@ -135,7 +137,7 @@ public final class NotificationViewControllerImpl {
return nil
})
- sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), rootPath: rootPath, legacyBasePath: nil, legacyCache: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in })
+ sharedAccountContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), rootPath: rootPath, legacyBasePath: nil, legacyCache: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in })
presentationDataPromise.set(sharedAccountContext!.presentationData)
}
diff --git a/submodules/TelegramUI/Sources/ShareExtensionContext.swift b/submodules/TelegramUI/Sources/ShareExtensionContext.swift
index e33b4a8d7f..29b1b991cd 100644
--- a/submodules/TelegramUI/Sources/ShareExtensionContext.swift
+++ b/submodules/TelegramUI/Sources/ShareExtensionContext.swift
@@ -55,6 +55,7 @@ private enum ShareAuthorizationError {
}
public struct ShareRootControllerInitializationData {
+ public let appBundleId: String
public let appGroupPath: String
public let apiId: Int32
public let apiHash: String
@@ -63,7 +64,8 @@ public struct ShareRootControllerInitializationData {
public let appVersion: String
public let bundleData: Data?
- public init(appGroupPath: String, apiId: Int32, apiHash: String, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?) {
+ public init(appBundleId: String, appGroupPath: String, apiId: Int32, apiHash: String, languagesCategory: String, encryptionParameters: (Data, Data), appVersion: String, bundleData: Data?) {
+ self.appBundleId = appBundleId
self.appGroupPath = appGroupPath
self.apiId = apiId
self.apiHash = apiHash
@@ -176,7 +178,7 @@ public class ShareRootControllerImpl {
setupSharedLogger(rootPath: rootPath, path: logsPath)
- let applicationBindings = TelegramApplicationBindings(isMainApp: false, containerPath: self.initializationData.appGroupPath, appSpecificScheme: "tg", openUrl: { _ in
+ let applicationBindings = TelegramApplicationBindings(isMainApp: false, appBundleId: self.initializationData.appBundleId, containerPath: self.initializationData.appGroupPath, appSpecificScheme: "tg", openUrl: { _ in
}, openUniversalUrl: { _, completion in
completion.completion(false)
return
@@ -230,7 +232,7 @@ public class ShareRootControllerImpl {
return nil
})
- let sharedContext = SharedAccountContextImpl(mainWindow: nil, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), rootPath: rootPath, legacyBasePath: nil, legacyCache: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in })
+ let sharedContext = SharedAccountContextImpl(mainWindow: nil, sharedContainerPath: self.initializationData.appGroupPath, basePath: rootPath, encryptionParameters: ValueBoxEncryptionParameters(forceEncryptionIfNoSet: false, key: ValueBoxEncryptionParameters.Key(data: self.initializationData.encryptionParameters.0)!, salt: ValueBoxEncryptionParameters.Salt(data: self.initializationData.encryptionParameters.1)!), accountManager: accountManager, appLockContext: appLockContext, applicationBindings: applicationBindings, initialPresentationDataAndSettings: initialPresentationDataAndSettings!, networkArguments: NetworkInitializationArguments(apiId: self.initializationData.apiId, apiHash: self.initializationData.apiHash, languagesCategory: self.initializationData.languagesCategory, appVersion: self.initializationData.appVersion, voipMaxLayer: 0, voipVersions: [], appData: .single(self.initializationData.bundleData), autolockDeadine: .single(nil), encryptionProvider: OpenSSLEncryptionProvider()), rootPath: rootPath, legacyBasePath: nil, legacyCache: nil, apsNotificationToken: .never(), voipNotificationToken: .never(), setNotificationCall: { _ in }, navigateToChat: { _, _, _ in })
presentationDataPromise.set(sharedContext.presentationData)
internalContext = InternalContext(sharedContext: sharedContext)
globalInternalContext = internalContext
diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift
index 61bed30c32..d1e543cb46 100644
--- a/submodules/TelegramUI/Sources/SharedAccountContext.swift
+++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift
@@ -55,6 +55,7 @@ private var testHasInstance = false
public final class SharedAccountContextImpl: SharedAccountContext {
public let mainWindow: Window1?
public let applicationBindings: TelegramApplicationBindings
+ public let sharedContainerPath: String
public let basePath: String
public let accountManager: AccountManager
public let appLockContext: AppLockContext
@@ -160,7 +161,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
private var spotlightDataContext: SpotlightDataContext?
private var widgetDataContext: WidgetDataContext?
- public init(mainWindow: Window1?, basePath: String, encryptionParameters: ValueBoxEncryptionParameters, accountManager: AccountManager, appLockContext: AppLockContext, applicationBindings: TelegramApplicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, networkArguments: NetworkInitializationArguments, rootPath: String, legacyBasePath: String?, legacyCache: LegacyCache?, apsNotificationToken: Signal, voipNotificationToken: Signal, setNotificationCall: @escaping (PresentationCall?) -> Void, navigateToChat: @escaping (AccountRecordId, PeerId, MessageId?) -> Void, displayUpgradeProgress: @escaping (Float?) -> Void = { _ in }) {
+ public init(mainWindow: Window1?, sharedContainerPath: String, basePath: String, encryptionParameters: ValueBoxEncryptionParameters, accountManager: AccountManager, appLockContext: AppLockContext, applicationBindings: TelegramApplicationBindings, initialPresentationDataAndSettings: InitialPresentationDataAndSettings, networkArguments: NetworkInitializationArguments, rootPath: String, legacyBasePath: String?, legacyCache: LegacyCache?, apsNotificationToken: Signal, voipNotificationToken: Signal, setNotificationCall: @escaping (PresentationCall?) -> Void, navigateToChat: @escaping (AccountRecordId, PeerId, MessageId?) -> Void, displayUpgradeProgress: @escaping (Float?) -> Void = { _ in }) {
assert(Queue.mainQueue().isCurrent())
precondition(!testHasInstance)
@@ -168,6 +169,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
self.mainWindow = mainWindow
self.applicationBindings = applicationBindings
+ self.sharedContainerPath = sharedContainerPath
self.basePath = basePath
self.accountManager = accountManager
self.navigateToChatImpl = navigateToChat
diff --git a/submodules/TelegramVoip/Sources/IpcGroupCallContext.swift b/submodules/TelegramVoip/Sources/IpcGroupCallContext.swift
new file mode 100644
index 0000000000..3820f40490
--- /dev/null
+++ b/submodules/TelegramVoip/Sources/IpcGroupCallContext.swift
@@ -0,0 +1,981 @@
+import Foundation
+import SwiftSignalKit
+import CoreMedia
+
+private struct PayloadDescription: Codable {
+ var id: UInt32
+ var timestamp: Int32
+}
+
+private struct JoinPayload: Codable {
+ var id: UInt32
+ var string: String
+}
+
+private struct JoinResponsePayload: Codable {
+ var id: UInt32
+ var string: String
+}
+
+private struct KeepaliveInfo: Codable {
+ var id: UInt32
+ var timestamp: Int32
+}
+
+private let checkInterval: Double = 0.2
+private let keepaliveTimeout: Double = 2.0
+
+private func payloadDescriptionPath(basePath: String) -> String {
+ return basePath + "/currentPayloadDescription.json"
+}
+
+private func joinPayloadPath(basePath: String) -> String {
+ return basePath + "/joinPayload.json"
+}
+
+private func joinResponsePayloadPath(basePath: String) -> String {
+ return basePath + "/joinResponsePayload.json"
+}
+
+private func keepaliveInfoPath(basePath: String) -> String {
+ return basePath + "/keepaliveInfo.json"
+}
+
+private func broadcastAppSocketPath(basePath: String) -> String {
+ return basePath + "/0"
+}
+
+public final class IpcGroupCallAppContext {
+ private let basePath: String
+ private let currentId: UInt32
+
+ private let joinPayloadPromise = Promise()
+ public var joinPayload: Signal {
+ return self.joinPayloadPromise.get()
+ }
+ private var joinPayloadCheckTimer: SwiftSignalKit.Timer?
+
+ private let isActivePromise = ValuePromise(false, ignoreRepeated: true)
+ public var isActive: Signal {
+ return self.isActivePromise.get()
+ }
+ private var keepaliveCheckTimer: SwiftSignalKit.Timer?
+
+ public init(basePath: String) {
+ self.basePath = basePath
+ self.currentId = UInt32.random(in: 0 ..< UInt32.max)
+
+ let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil)
+
+ self.sendRequest()
+ }
+
+ deinit {
+ self.joinPayloadCheckTimer?.invalidate()
+ self.keepaliveCheckTimer?.invalidate()
+ }
+
+ private func sendRequest() {
+ let timestamp = Int32(Date().timeIntervalSince1970)
+ let payloadDescription = PayloadDescription(
+ id: self.currentId,
+ timestamp: timestamp
+ )
+ guard let payloadDescriptionData = try? JSONEncoder().encode(payloadDescription) else {
+ preconditionFailure()
+ }
+ guard let _ = try? payloadDescriptionData.write(to: URL(fileURLWithPath: payloadDescriptionPath(basePath: self.basePath)), options: .atomic) else {
+ preconditionFailure()
+ }
+
+ self.receiveJoinPayload()
+ }
+
+ private func receiveJoinPayload() {
+ let joinPayloadCheckTimer = SwiftSignalKit.Timer(timeout: checkInterval, repeat: true, completion: { [weak self] in
+ self?.checkJoinPayload()
+ }, queue: .mainQueue())
+ self.joinPayloadCheckTimer = joinPayloadCheckTimer
+ joinPayloadCheckTimer.start()
+ }
+
+ private func checkJoinPayload() {
+ let filePath = joinPayloadPath(basePath: self.basePath)
+ guard let joinPayloadData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
+ return
+ }
+
+ self.joinPayloadCheckTimer?.invalidate()
+ let _ = try? FileManager.default.removeItem(atPath: filePath)
+
+ guard let joinPayload = try? JSONDecoder().decode(JoinPayload.self, from: joinPayloadData) else {
+ return
+ }
+
+ if joinPayload.id != self.currentId {
+ return
+ }
+
+ self.joinPayloadPromise.set(.single(joinPayload.string))
+ }
+
+ public func setJoinResponsePayload(_ joinResponsePayload: String) {
+ let inputJoinResponsePayload = JoinResponsePayload(
+ id: self.currentId,
+ string: joinResponsePayload
+ )
+ guard let inputJoinResponsePayloadData = try? JSONEncoder().encode(inputJoinResponsePayload) else {
+ preconditionFailure()
+ }
+ guard let _ = try? inputJoinResponsePayloadData.write(to: URL(fileURLWithPath: joinResponsePayloadPath(basePath: self.basePath)), options: .atomic) else {
+ preconditionFailure()
+ }
+
+ self.beginCheckingKeepaliveInfo()
+ }
+
+ private func beginCheckingKeepaliveInfo() {
+ let filePath = keepaliveInfoPath(basePath: self.basePath)
+ guard let keepaliveInfoData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
+ return
+ }
+ guard let keepaliveInfo = try? JSONDecoder().decode(KeepaliveInfo.self, from: keepaliveInfoData) else {
+ return
+ }
+ if keepaliveInfo.id != self.currentId {
+ self.isActivePromise.set(false)
+ return
+ }
+ let timestamp = Int32(Date().timeIntervalSince1970)
+ if keepaliveInfo.timestamp < timestamp - Int32(keepaliveTimeout) {
+ self.isActivePromise.set(false)
+ return
+ }
+
+ self.isActivePromise.set(true)
+ }
+}
+
+public final class IpcGroupCallBroadcastContext {
+ public enum Request {
+ case request
+ case failed
+ }
+
+ private let basePath: String
+
+ private var currentId: UInt32?
+
+ private var requestCheckTimer: SwiftSignalKit.Timer?
+ private let requestPromise = Promise()
+ public var request: Signal {
+ return self.requestPromise.get()
+ }
+
+ private var joinResponsePayloadCheckTimer: SwiftSignalKit.Timer?
+ private let joinResponsePayloadPromise = Promise()
+ public var joinResponsePayload: Signal {
+ return self.joinResponsePayloadPromise.get()
+ }
+
+ private var keepaliveTimer: SwiftSignalKit.Timer?
+
+ public init(basePath: String) {
+ self.basePath = basePath
+
+ let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil)
+
+ self.receiveRequest()
+ }
+
+ deinit {
+ self.requestCheckTimer?.invalidate()
+ self.joinResponsePayloadCheckTimer?.invalidate()
+ self.keepaliveTimer?.invalidate()
+ self.endActiveIndication()
+ }
+
+ private func receiveRequest() {
+ let requestCheckTimer = SwiftSignalKit.Timer(timeout: checkInterval, repeat: true, completion: { [weak self] in
+ self?.checkRequest()
+ }, queue: .mainQueue())
+ self.requestCheckTimer = requestCheckTimer
+ requestCheckTimer.start()
+ }
+
+ private func checkRequest() {
+ let filePath = payloadDescriptionPath(basePath: self.basePath)
+ guard let payloadDescriptionData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
+ return
+ }
+
+ let _ = try? FileManager.default.removeItem(atPath: filePath)
+
+ guard let payloadDescription = try? JSONDecoder().decode(PayloadDescription.self, from: payloadDescriptionData) else {
+ self.requestCheckTimer?.invalidate()
+ self.requestPromise.set(.single(.failed))
+ return
+ }
+ let timestamp = Int32(Date().timeIntervalSince1970)
+ if payloadDescription.timestamp < timestamp - 1 * 60 {
+ self.requestPromise.set(.single(.failed))
+ return
+ }
+
+ self.requestCheckTimer?.invalidate()
+
+ self.currentId = payloadDescription.id
+ self.requestPromise.set(.single(.request))
+ }
+
+ public func setJoinPayload(_ joinPayload: String) {
+ guard let currentId = self.currentId else {
+ preconditionFailure()
+ }
+ let inputPayload = JoinPayload(
+ id: currentId,
+ string: joinPayload
+ )
+ guard let inputPayloadData = try? JSONEncoder().encode(inputPayload) else {
+ preconditionFailure()
+ }
+ guard let _ = try? inputPayloadData.write(to: URL(fileURLWithPath: joinPayloadPath(basePath: self.basePath)), options: .atomic) else {
+ preconditionFailure()
+ }
+
+ self.receiveJoinResponsePayload()
+ }
+
+ private func receiveJoinResponsePayload() {
+ let joinResponsePayloadCheckTimer = SwiftSignalKit.Timer(timeout: checkInterval, repeat: true, completion: { [weak self] in
+ self?.checkJoinResponsePayload()
+ }, queue: .mainQueue())
+ self.joinResponsePayloadCheckTimer = joinResponsePayloadCheckTimer
+ joinResponsePayloadCheckTimer.start()
+ }
+
+ private func checkJoinResponsePayload() {
+ let filePath = joinResponsePayloadPath(basePath: self.basePath)
+ guard let joinResponsePayloadData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
+ return
+ }
+
+ self.joinResponsePayloadCheckTimer?.invalidate()
+ let _ = try? FileManager.default.removeItem(atPath: filePath)
+
+ guard let joinResponsePayload = try? JSONDecoder().decode(JoinResponsePayload.self, from: joinResponsePayloadData) else {
+ return
+ }
+ if joinResponsePayload.id != self.currentId {
+ return
+ }
+
+ self.joinResponsePayloadPromise.set(.single(joinResponsePayload.string))
+ }
+
+ public func beginActiveIndication() {
+ if self.keepaliveTimer != nil {
+ return
+ }
+
+ self.writeKeepaliveInfo()
+
+ let keepaliveTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
+ self?.writeKeepaliveInfo()
+ }, queue: .mainQueue())
+ self.keepaliveTimer = keepaliveTimer
+ keepaliveTimer.start()
+ }
+
+ private func writeKeepaliveInfo() {
+ guard let currentId = self.currentId else {
+ preconditionFailure()
+ }
+ let keepaliveInfo = KeepaliveInfo(
+ id: currentId,
+ timestamp: Int32(Date().timeIntervalSince1970)
+ )
+ guard let keepaliveInfoData = try? JSONEncoder().encode(keepaliveInfo) else {
+ preconditionFailure()
+ }
+ guard let _ = try? keepaliveInfoData.write(to: URL(fileURLWithPath: keepaliveInfoPath(basePath: self.basePath)), options: .atomic) else {
+ preconditionFailure()
+ }
+ }
+
+ private func endActiveIndication() {
+ let _ = try? FileManager.default.removeItem(atPath: keepaliveInfoPath(basePath: self.basePath))
+ }
+}
+
+private final class FdReadConnection {
+ private final class PendingData {
+ var data: Data
+ var offset: Int = 0
+
+ init(count: Int) {
+ self.data = Data(bytesNoCopy: malloc(count)!, count: count, deallocator: .free)
+ }
+ }
+
+ private let queue: Queue
+ let fd: Int32
+ private let didRead: ((Data) -> Void)?
+ private let channel: DispatchSourceRead
+
+ private var currendData: PendingData?
+
+ init(queue: Queue, fd: Int32, didRead: ((Data) -> Void)?) {
+ assert(queue.isCurrent())
+ self.queue = queue
+ self.fd = fd
+ self.didRead = didRead
+
+ self.channel = DispatchSource.makeReadSource(fileDescriptor: fd, queue: queue.queue)
+ self.channel.setEventHandler(handler: { [weak self] in
+ guard let strongSelf = self else {
+ return
+ }
+
+ while true {
+ if let currendData = strongSelf.currendData {
+ let offset = currendData.offset
+ let count = currendData.data.count - offset
+ let bytesRead = currendData.data.withUnsafeMutableBytes { bytes -> Int in
+ return Darwin.read(fd, bytes.baseAddress!.advanced(by: offset), min(8129, count))
+ }
+ if bytesRead <= 0 {
+ break
+ } else {
+ currendData.offset += bytesRead
+ if currendData.offset == currendData.data.count {
+ strongSelf.currendData = nil
+ strongSelf.didRead?(currendData.data)
+ }
+ }
+ } else {
+ var length: Int32 = 0
+ let bytesRead = read(fd, &length, 4)
+ if bytesRead < 0 {
+ break
+ } else {
+ assert(bytesRead == 4)
+ assert(length > 0 && length <= 30 * 1024 * 1024)
+ strongSelf.currendData = PendingData(count: Int(length))
+ }
+ }
+ }
+ })
+ self.channel.resume()
+ }
+
+ deinit {
+ assert(self.queue.isCurrent())
+ self.channel.cancel()
+ }
+}
+
+private final class FdWriteConnection {
+ private final class PendingData {
+ let data: Data
+ var didWriteHeader: Bool = false
+ var offset: Int = 0
+
+ init(data: Data) {
+ self.data = data
+ }
+ }
+
+ private let queue: Queue
+ let fd: Int32
+ private let channel: DispatchSourceWrite
+ private var isResumed = false
+
+ private let bufferSize: Int
+ private let buffer: UnsafeMutableRawPointer
+
+ private var currentData: PendingData?
+ private var nextData: Data?
+
+ init(queue: Queue, fd: Int32) {
+ assert(queue.isCurrent())
+ self.queue = queue
+ self.fd = fd
+
+ self.bufferSize = 8192
+ self.buffer = malloc(self.bufferSize)
+
+ self.channel = DispatchSource.makeWriteSource(fileDescriptor: fd, queue: queue.queue)
+ self.channel.setEventHandler(handler: { [weak self] in
+ guard let strongSelf = self else {
+ return
+ }
+
+ while true {
+ if let currentData = strongSelf.currentData {
+ if !currentData.didWriteHeader {
+ var length: Int32 = Int32(currentData.data.count)
+ let writtenBytes = Darwin.write(fd, &length, 4)
+ if writtenBytes > 0 {
+ assert(writtenBytes == 4)
+ currentData.didWriteHeader = true
+ } else {
+ strongSelf.channel.suspend()
+ strongSelf.isResumed = false
+ break
+ }
+ } else {
+ let offset = currentData.offset
+ let count = currentData.data.count - offset
+ let writtenBytes = currentData.data.withUnsafeBytes { bytes -> Int in
+ return Darwin.write(fd, bytes.baseAddress!.advanced(by: offset), min(count, strongSelf.bufferSize))
+ }
+ if writtenBytes > 0 {
+ currentData.offset += writtenBytes
+ if currentData.offset == currentData.data.count {
+ strongSelf.currentData = nil
+
+ if let nextData = strongSelf.nextData {
+ strongSelf.nextData = nil
+ strongSelf.currentData = PendingData(data: nextData)
+ } else {
+ strongSelf.channel.suspend()
+ strongSelf.isResumed = false
+ break
+ }
+ }
+ } else {
+ strongSelf.channel.suspend()
+ strongSelf.isResumed = false
+ break
+ }
+ }
+ } else {
+ strongSelf.channel.suspend()
+ strongSelf.isResumed = false
+ break
+ }
+ }
+ })
+ }
+
+ deinit {
+ assert(self.queue.isCurrent())
+
+ if !self.isResumed {
+ self.channel.resume()
+ }
+ self.channel.cancel()
+
+ free(self.buffer)
+ }
+
+ func replaceData(data: Data) {
+ if self.currentData == nil {
+ self.currentData = PendingData(data: data)
+ } else {
+ self.nextData = data
+ }
+
+ if !self.isResumed {
+ self.isResumed = true
+ self.channel.resume()
+ }
+ }
+}
+
+private final class NamedPipeReaderImpl {
+ private let queue: Queue
+ private var connection: FdReadConnection?
+
+ init(queue: Queue, path: String, didRead: @escaping (Data) -> Void) {
+ self.queue = queue
+
+ unlink(path)
+ mkfifo(path, 0o666)
+ let fd = open(path, O_RDONLY | O_NONBLOCK, S_IRUSR | S_IWUSR)
+ if fd != -1 {
+ self.connection = FdReadConnection(queue: self.queue, fd: fd, didRead: { data in
+ didRead(data)
+ })
+ }
+ }
+}
+
+private final class NamedPipeReader {
+ private let queue = Queue()
+ let impl: QueueLocalObject
+
+ init(path: String, didRead: @escaping (Data) -> Void) {
+ let queue = self.queue
+ self.impl = QueueLocalObject(queue: queue, generate: {
+ return NamedPipeReaderImpl(queue: queue, path: path, didRead: didRead)
+ })
+ }
+}
+
+private final class NamedPipeWriterImpl {
+ private let queue: Queue
+ private var connection: FdWriteConnection?
+
+ init(queue: Queue, path: String) {
+ self.queue = queue
+
+ let fd = open(path, O_WRONLY | O_NONBLOCK, S_IRUSR | S_IWUSR)
+ if fd != -1 {
+ self.connection = FdWriteConnection(queue: self.queue, fd: fd)
+ }
+ }
+
+ func replaceData(data: Data) {
+ guard let connection = self.connection else {
+ return
+ }
+ connection.replaceData(data: data)
+ }
+}
+
+private final class NamedPipeWriter {
+ private let queue = Queue()
+ private let impl: QueueLocalObject
+
+ init(path: String) {
+ let queue = self.queue
+ self.impl = QueueLocalObject(queue: queue, generate: {
+ return NamedPipeWriterImpl(queue: queue, path: path)
+ })
+ }
+
+ func replaceData(data: Data) {
+ self.impl.with { impl in
+ impl.replaceData(data: data)
+ }
+ }
+}
+
+private final class MappedFile {
+ let path: String
+ private var handle: Int32
+ private var currentSize: Int
+ private(set) var memory: UnsafeMutableRawPointer
+
+ init?(path: String, createIfNotExists: Bool) {
+ self.path = path
+
+ var flags: Int32 = O_RDWR | O_APPEND
+ if createIfNotExists {
+ flags |= O_CREAT
+ }
+ self.handle = open(path, flags, S_IRUSR | S_IWUSR)
+
+ if self.handle < 0 {
+ return nil
+ }
+
+ var value = stat()
+ stat(path, &value)
+ self.currentSize = Int(value.st_size)
+
+ self.memory = mmap(nil, self.currentSize, PROT_READ | PROT_WRITE, MAP_SHARED, self.handle, 0)
+ }
+
+ deinit {
+ munmap(self.memory, self.currentSize)
+ close(self.handle)
+ }
+
+ var size: Int {
+ get {
+ return self.currentSize
+ } set(value) {
+ if value != self.currentSize {
+ munmap(self.memory, self.currentSize)
+ ftruncate(self.handle, off_t(value))
+ self.currentSize = value
+ self.memory = mmap(nil, self.currentSize, PROT_READ | PROT_WRITE, MAP_SHARED, self.handle, 0)
+ }
+ }
+ }
+
+ func synchronize() {
+ msync(self.memory, self.currentSize, MS_ASYNC)
+ }
+
+ func write(at range: Range, from data: UnsafeRawPointer) {
+ memcpy(self.memory.advanced(by: range.lowerBound), data, range.count)
+ }
+
+ func read(at range: Range, to data: UnsafeMutableRawPointer) {
+ memcpy(data, self.memory.advanced(by: range.lowerBound), range.count)
+ }
+
+ func clear() {
+ memset(self.memory, 0, self.currentSize)
+ }
+}
+
+public final class IpcGroupCallBufferAppContext {
+ private let basePath: String
+ private let server: NamedPipeReader
+
+ private let id: UInt32
+
+ private let isActivePromise = ValuePromise(false, ignoreRepeated: true)
+ public var isActive: Signal {
+ return self.isActivePromise.get()
+ }
+ private var isActiveCheckTimer: SwiftSignalKit.Timer?
+
+ private let framesPipe = ValuePipe()
+ public var frames: Signal {
+ return self.framesPipe.signal()
+ }
+
+ private var framePollTimer: SwiftSignalKit.Timer?
+ private var mappedFile: MappedFile?
+
+ private var callActiveInfoTimer: SwiftSignalKit.Timer?
+
+ public init(basePath: String) {
+ self.basePath = basePath
+ let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil)
+
+ self.id = UInt32.random(in: 0 ..< UInt32.max)
+
+ let framesPipe = self.framesPipe
+ self.server = NamedPipeReader(path: broadcastAppSocketPath(basePath: basePath), didRead: { data in
+ //framesPipe.putNext(data)
+ })
+
+ let dataPath = broadcastAppSocketPath(basePath: basePath) + "-data-\(self.id)"
+
+ if let mappedFile = MappedFile(path: dataPath, createIfNotExists: true) {
+ self.mappedFile = mappedFile
+ if mappedFile.size < 10 * 1024 * 1024 {
+ mappedFile.size = 10 * 1024 * 1024
+ }
+ }
+
+ let framePollTimer = SwiftSignalKit.Timer(timeout: 1.0 / 30.0, repeat: true, completion: { [weak self] in
+ guard let strongSelf = self, let mappedFile = strongSelf.mappedFile else {
+ return
+ }
+
+ let data = Data(bytesNoCopy: mappedFile.memory, count: mappedFile.size, deallocator: .none)
+ if let frame = deserializePixelBuffer(data: data) {
+ strongSelf.framesPipe.putNext(frame)
+ }
+ }, queue: .mainQueue())
+ self.framePollTimer = framePollTimer
+ framePollTimer.start()
+
+ self.updateCallIsActive()
+
+ let callActiveInfoTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
+ self?.updateCallIsActive()
+ }, queue: .mainQueue())
+ self.callActiveInfoTimer = callActiveInfoTimer
+ callActiveInfoTimer.start()
+
+ let isActiveCheckTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
+ self?.updateKeepaliveInfo()
+ }, queue: .mainQueue())
+ self.isActiveCheckTimer = isActiveCheckTimer
+ isActiveCheckTimer.start()
+ }
+
+ deinit {
+ self.framePollTimer?.invalidate()
+ self.callActiveInfoTimer?.invalidate()
+ self.isActiveCheckTimer?.invalidate()
+ if let mappedFile = self.mappedFile {
+ self.mappedFile = nil
+ let _ = try? FileManager.default.removeItem(atPath: mappedFile.path)
+ }
+ }
+
+ private func updateCallIsActive() {
+ let timestamp = Int32(Date().timeIntervalSince1970)
+ let payloadDescription = PayloadDescription(
+ id: self.id,
+ timestamp: timestamp
+ )
+ guard let payloadDescriptionData = try? JSONEncoder().encode(payloadDescription) else {
+ return
+ }
+ guard let _ = try? payloadDescriptionData.write(to: URL(fileURLWithPath: payloadDescriptionPath(basePath: self.basePath)), options: .atomic) else {
+ return
+ }
+ }
+
+ private func updateKeepaliveInfo() {
+ let filePath = keepaliveInfoPath(basePath: self.basePath)
+ guard let keepaliveInfoData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
+ return
+ }
+ guard let keepaliveInfo = try? JSONDecoder().decode(KeepaliveInfo.self, from: keepaliveInfoData) else {
+ return
+ }
+ if keepaliveInfo.id != self.id {
+ self.isActivePromise.set(false)
+ return
+ }
+ let timestamp = Int32(Date().timeIntervalSince1970)
+ if keepaliveInfo.timestamp < timestamp - Int32(keepaliveTimeout) {
+ self.isActivePromise.set(false)
+ return
+ }
+
+ self.isActivePromise.set(true)
+ }
+}
+
+public final class IpcGroupCallBufferBroadcastContext {
+ public enum Status {
+ case finished
+ }
+
+ private let basePath: String
+ private let client: NamedPipeWriter
+ private var timer: SwiftSignalKit.Timer?
+
+ private let statusPromise = Promise()
+ public var status: Signal {
+ return self.statusPromise.get()
+ }
+
+ private var mappedFile: MappedFile?
+ private var currentId: UInt32?
+
+ private var callActiveInfoTimer: SwiftSignalKit.Timer?
+
+ private var keepaliveInfoTimer: SwiftSignalKit.Timer?
+
+ public init(basePath: String) {
+ self.basePath = basePath
+ let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil)
+
+ self.client = NamedPipeWriter(path: broadcastAppSocketPath(basePath: basePath))
+
+ let callActiveInfoTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
+ self?.updateCallIsActive()
+ }, queue: .mainQueue())
+ self.callActiveInfoTimer = callActiveInfoTimer
+ callActiveInfoTimer.start()
+ }
+
+ deinit {
+ self.endActiveIndication()
+
+ self.callActiveInfoTimer?.invalidate()
+ self.keepaliveInfoTimer?.invalidate()
+ }
+
+ private func updateCallIsActive() {
+ let filePath = payloadDescriptionPath(basePath: self.basePath)
+ guard let payloadDescriptionData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
+ self.statusPromise.set(.single(.finished))
+ return
+ }
+
+ guard let payloadDescription = try? JSONDecoder().decode(PayloadDescription.self, from: payloadDescriptionData) else {
+ self.statusPromise.set(.single(.finished))
+ return
+ }
+ let timestamp = Int32(Date().timeIntervalSince1970)
+ if payloadDescription.timestamp < timestamp - 4 {
+ self.statusPromise.set(.single(.finished))
+ return
+ }
+
+ if let currentId = self.currentId {
+ if currentId != payloadDescription.id {
+ self.statusPromise.set(.single(.finished))
+ }
+ } else {
+ self.currentId = payloadDescription.id
+
+ let dataPath = broadcastAppSocketPath(basePath: basePath) + "-data-\(payloadDescription.id)"
+
+ if let mappedFile = MappedFile(path: dataPath, createIfNotExists: false) {
+ self.mappedFile = mappedFile
+ if mappedFile.size < 10 * 1024 * 1024 {
+ mappedFile.size = 10 * 1024 * 1024
+ }
+ }
+
+ self.writeKeepaliveInfo()
+
+ let keepaliveInfoTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
+ self?.writeKeepaliveInfo()
+ }, queue: .mainQueue())
+ self.keepaliveInfoTimer = keepaliveInfoTimer
+ keepaliveInfoTimer.start()
+ }
+ }
+
+ public func setCurrentFrame(data: Data) {
+ //let _ = try? data.write(to: URL(fileURLWithPath: dataPath), options: [])
+
+ if let mappedFile = self.mappedFile, mappedFile.size >= data.count {
+ let _ = data.withUnsafeBytes { bytes in
+ memcpy(mappedFile.memory, bytes.baseAddress!, data.count)
+ }
+ }
+
+ //self.client.replaceData(data: data)
+ }
+
+ private func writeKeepaliveInfo() {
+ guard let currentId = self.currentId else {
+ preconditionFailure()
+ }
+ let keepaliveInfo = KeepaliveInfo(
+ id: currentId,
+ timestamp: Int32(Date().timeIntervalSince1970)
+ )
+ guard let keepaliveInfoData = try? JSONEncoder().encode(keepaliveInfo) else {
+ preconditionFailure()
+ }
+ guard let _ = try? keepaliveInfoData.write(to: URL(fileURLWithPath: keepaliveInfoPath(basePath: self.basePath)), options: .atomic) else {
+ preconditionFailure()
+ }
+ }
+
+ private func endActiveIndication() {
+ let _ = try? FileManager.default.removeItem(atPath: keepaliveInfoPath(basePath: self.basePath))
+ }
+}
+
+public func serializePixelBuffer(buffer: CVPixelBuffer) -> Data? {
+ let pixelFormat = CVPixelBufferGetPixelFormatType(buffer)
+ switch pixelFormat {
+ case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
+ let status = CVPixelBufferLockBaseAddress(buffer, .readOnly)
+ if status != kCVReturnSuccess {
+ return nil
+ }
+ defer {
+ CVPixelBufferUnlockBaseAddress(buffer, .readOnly)
+ }
+
+ let width = CVPixelBufferGetWidth(buffer)
+ let height = CVPixelBufferGetHeight(buffer)
+
+ guard let yPlane = CVPixelBufferGetBaseAddressOfPlane(buffer, 0) else {
+ return nil
+ }
+ let yStride = CVPixelBufferGetBytesPerRowOfPlane(buffer, 0)
+ let yPlaneSize = yStride * height
+
+ guard let uvPlane = CVPixelBufferGetBaseAddressOfPlane(buffer, 1) else {
+ return nil
+ }
+ let uvStride = CVPixelBufferGetBytesPerRowOfPlane(buffer, 1)
+ let uvPlaneSize = uvStride * (height / 2)
+
+ let headerSize: Int = 4 + 4 + 4 + 4 + 4
+
+ let dataSize = headerSize + yPlaneSize + uvPlaneSize
+ let resultBytes = malloc(dataSize)!
+
+ var pixelFormatValue = pixelFormat
+ memcpy(resultBytes.advanced(by: 0), &pixelFormatValue, 4)
+ var widthValue = Int32(width)
+ memcpy(resultBytes.advanced(by: 4), &widthValue, 4)
+ var heightValue = Int32(height)
+ memcpy(resultBytes.advanced(by: 4 + 4), &heightValue, 4)
+ var yStrideValue = Int32(yStride)
+ memcpy(resultBytes.advanced(by: 4 + 4 + 4), &yStrideValue, 4)
+ var uvStrideValue = Int32(uvStride)
+ memcpy(resultBytes.advanced(by: 4 + 4 + 4 + 4), &uvStrideValue, 4)
+
+ memcpy(resultBytes.advanced(by: headerSize), yPlane, yPlaneSize)
+ memcpy(resultBytes.advanced(by: headerSize + yPlaneSize), uvPlane, uvPlaneSize)
+
+ return Data(bytesNoCopy: resultBytes, count: dataSize, deallocator: .free)
+ default:
+ return nil
+ }
+}
+
+public func deserializePixelBuffer(data: Data) -> CVPixelBuffer? {
+ if data.count < 4 + 4 + 4 + 4 {
+ return nil
+ }
+ let count = data.count
+ return data.withUnsafeBytes { bytes -> CVPixelBuffer? in
+ let dataBytes = bytes.baseAddress!
+
+ var pixelFormat: UInt32 = 0
+ memcpy(&pixelFormat, dataBytes.advanced(by: 0), 4)
+
+ switch pixelFormat {
+ case kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange:
+ break
+ default:
+ return nil
+ }
+
+ var width: Int32 = 0
+ memcpy(&width, dataBytes.advanced(by: 4), 4)
+ var height: Int32 = 0
+ memcpy(&height, dataBytes.advanced(by: 4 + 4), 4)
+ var yStride: Int32 = 0
+ memcpy(&yStride, dataBytes.advanced(by: 4 + 4 + 4), 4)
+ var uvStride: Int32 = 0
+ memcpy(&uvStride, dataBytes.advanced(by: 4 + 4 + 4 + 4), 4)
+
+ if width < 0 || width > 8192 {
+ return nil
+ }
+ if height < 0 || height > 8192 {
+ return nil
+ }
+
+ let headerSize: Int = 4 + 4 + 4 + 4 + 4
+
+ let yPlaneSize = Int(yStride * height)
+ let uvPlaneSize = Int(uvStride * height / 2)
+ let dataSize = headerSize + yPlaneSize + uvPlaneSize
+
+ if dataSize > count {
+ return nil
+ }
+
+ var buffer: CVPixelBuffer? = nil
+ CVPixelBufferCreate(nil, Int(width), Int(height), pixelFormat, nil, &buffer)
+ if let buffer = buffer {
+ let status = CVPixelBufferLockBaseAddress(buffer, [])
+ if status != kCVReturnSuccess {
+ return nil
+ }
+ defer {
+ CVPixelBufferUnlockBaseAddress(buffer, [])
+ }
+
+ guard let destYPlane = CVPixelBufferGetBaseAddressOfPlane(buffer, 0) else {
+ return nil
+ }
+ let destYStride = CVPixelBufferGetBytesPerRowOfPlane(buffer, 0)
+ if destYStride != Int(yStride) {
+ return nil
+ }
+
+ guard let destUvPlane = CVPixelBufferGetBaseAddressOfPlane(buffer, 1) else {
+ return nil
+ }
+ let destUvStride = CVPixelBufferGetBytesPerRowOfPlane(buffer, 1)
+ if destUvStride != Int(uvStride) {
+ return nil
+ }
+
+ memcpy(destYPlane, dataBytes.advanced(by: headerSize), yPlaneSize)
+ memcpy(destUvPlane, dataBytes.advanced(by: headerSize + yPlaneSize), uvPlaneSize)
+
+ return buffer
+ } else {
+ return nil
+ }
+ }
+}
diff --git a/submodules/TelegramVoip/Sources/OngoingCallContext.swift b/submodules/TelegramVoip/Sources/OngoingCallContext.swift
index 98e71dc325..7606426e38 100644
--- a/submodules/TelegramVoip/Sources/OngoingCallContext.swift
+++ b/submodules/TelegramVoip/Sources/OngoingCallContext.swift
@@ -335,8 +335,12 @@ extension OngoingCallThreadLocalContext: OngoingCallThreadLocalContextProtocol {
public final class OngoingCallVideoCapturer {
internal let impl: OngoingCallThreadLocalContextVideoCapturer
- public init(keepLandscape: Bool = false) {
- self.impl = OngoingCallThreadLocalContextVideoCapturer(deviceId: "", keepLandscape: keepLandscape)
+ public init(keepLandscape: Bool = false, isCustom: Bool = false) {
+ if isCustom {
+ self.impl = OngoingCallThreadLocalContextVideoCapturer.withExternalSampleBufferProvider()
+ } else {
+ self.impl = OngoingCallThreadLocalContextVideoCapturer(deviceId: "", keepLandscape: keepLandscape)
+ }
}
public func switchVideoInput(isFront: Bool) {
@@ -383,6 +387,14 @@ public final class OngoingCallVideoCapturer {
public func setIsVideoEnabled(_ value: Bool) {
self.impl.setIsVideoEnabled(value)
}
+
+ public func injectSampleBuffer(_ sampleBuffer: CMSampleBuffer) {
+ self.impl.submitSampleBuffer(sampleBuffer)
+ }
+
+ public func injectPixelBuffer(_ pixelBuffer: CVPixelBuffer) {
+ self.impl.submitPixelBuffer(pixelBuffer)
+ }
}
extension OngoingCallThreadLocalContextWebrtc: OngoingCallThreadLocalContextProtocol {
diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h
index a57eccd559..0b389edfc9 100644
--- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h
+++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h
@@ -5,6 +5,7 @@
#if TARGET_OS_IOS
#import
+#import
#else
#import
#define UIView NSView
@@ -110,11 +111,20 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) {
- (instancetype _Nonnull)initWithDeviceId:(NSString * _Nonnull)deviceId keepLandscape:(bool)keepLandscape;
+#if TARGET_OS_IOS
++ (instancetype _Nonnull)capturerWithExternalSampleBufferProvider;
+#endif
+
- (void)switchVideoInput:(NSString * _Nonnull)deviceId;
- (void)setIsVideoEnabled:(bool)isVideoEnabled;
- (void)makeOutgoingVideoView:(void (^_Nonnull)(UIView * _Nullable))completion;
+#if TARGET_OS_IOS
+- (void)submitSampleBuffer:(CMSampleBufferRef _Nonnull)sampleBuffer;
+- (void)submitPixelBuffer:(CVPixelBufferRef _Nonnull)pixelBuffer;
+#endif
+
@end
@interface OngoingCallThreadLocalContextWebrtc : NSObject
diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm
index ddb753346f..57ac098144 100644
--- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm
+++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm
@@ -25,6 +25,9 @@
#import "group/GroupInstanceImpl.h"
#import "group/GroupInstanceCustomImpl.h"
+#import "VideoCaptureInterfaceImpl.h"
+#import "platform/darwin/CustomExternalCapturer.h"
+
@implementation OngoingCallConnectionDescriptionWebrtc
- (instancetype _Nonnull)initWithConnectionId:(int64_t)connectionId hasStun:(bool)hasStun hasTurn:(bool)hasTurn ip:(NSString * _Nonnull)ip port:(int32_t)port username:(NSString * _Nonnull)username password:(NSString * _Nonnull)password {
@@ -144,12 +147,22 @@
@interface OngoingCallThreadLocalContextVideoCapturer () {
bool _keepLandscape;
+ std::shared_ptr> _croppingBuffer;
}
@end
@implementation OngoingCallThreadLocalContextVideoCapturer
+- (instancetype _Nonnull)initWithInterface:(std::shared_ptr)interface {
+ self = [super init];
+ if (self != nil) {
+ _interface = interface;
+ _croppingBuffer = std::make_shared>();
+ }
+ return self;
+}
+
- (instancetype _Nonnull)initWithDeviceId:(NSString * _Nonnull)deviceId keepLandscape:(bool)keepLandscape {
self = [super init];
if (self != nil) {
@@ -164,10 +177,55 @@
return self;
}
+#if TARGET_OS_IOS
+
+tgcalls::VideoCaptureInterfaceObject *GetVideoCaptureAssumingSameThread(tgcalls::VideoCaptureInterface *videoCapture) {
+ return videoCapture
+ ? static_cast(videoCapture)->object()->getSyncAssumingSameThread()
+ : nullptr;
+}
+
++ (instancetype _Nonnull)capturerWithExternalSampleBufferProvider {
+ std::shared_ptr interface = tgcalls::VideoCaptureInterface::Create(tgcalls::StaticThreads::getThreads(), ":ios_custom");
+ return [[OngoingCallThreadLocalContextVideoCapturer alloc] initWithInterface:interface];
+}
+#endif
- (void)dealloc {
}
+#if TARGET_OS_IOS
+- (void)submitSampleBuffer:(CMSampleBufferRef _Nonnull)sampleBuffer {
+ if (!sampleBuffer) {
+ return;
+ }
+ tgcalls::StaticThreads::getThreads()->getMediaThread()->PostTask(RTC_FROM_HERE, [interface = _interface, sampleBuffer = CFRetain(sampleBuffer)]() {
+ auto capture = GetVideoCaptureAssumingSameThread(interface.get());
+ auto source = capture->source();
+ if (source) {
+ [CustomExternalCapturer passSampleBuffer:(CMSampleBufferRef)sampleBuffer toSource:source];
+ }
+ CFRelease(sampleBuffer);
+ });
+}
+
+- (void)submitPixelBuffer:(CVPixelBufferRef _Nonnull)pixelBuffer {
+ if (!pixelBuffer) {
+ return;
+ }
+
+ tgcalls::StaticThreads::getThreads()->getMediaThread()->PostTask(RTC_FROM_HERE, [interface = _interface, pixelBuffer = CFRetain(pixelBuffer), croppingBuffer = _croppingBuffer]() {
+ auto capture = GetVideoCaptureAssumingSameThread(interface.get());
+ auto source = capture->source();
+ if (source) {
+ [CustomExternalCapturer passPixelBuffer:(CVPixelBufferRef)pixelBuffer toSource:source croppingBuffer:*croppingBuffer];
+ }
+ CFRelease(pixelBuffer);
+ });
+}
+
+#endif
+
- (void)switchVideoInput:(NSString * _Nonnull)deviceId {
std::string resolvedId = deviceId.UTF8String;
if (_keepLandscape) {
diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls
index 94a9c7b4e4..5e0019224e 160000
--- a/submodules/TgVoipWebrtc/tgcalls
+++ b/submodules/TgVoipWebrtc/tgcalls
@@ -1 +1 @@
-Subproject commit 94a9c7b4e49c943d1ca108e35779739ad99d695a
+Subproject commit 5e0019224eaf9fb8ae432f2b39c84a591f59a5da