mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 13:35:19 +00:00
Screencast shutdown improvements
This commit is contained in:
parent
b7c9d2b233
commit
c20e99e4fc
@ -1470,6 +1470,7 @@ swift_library(
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/BuildConfig:BuildConfig",
|
||||
"//submodules/WidgetItems:WidgetItems",
|
||||
"//submodules/BroadcastUploadHelpers:BroadcastUploadHelpers",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -4,6 +4,7 @@ import CoreVideo
|
||||
import TelegramVoip
|
||||
import SwiftSignalKit
|
||||
import BuildConfig
|
||||
import BroadcastUploadHelpers
|
||||
|
||||
private func rootPathForBasePath(_ appGroupPath: String) -> String {
|
||||
return appGroupPath + "/telegram-data"
|
||||
@ -34,28 +35,29 @@ private func rootPathForBasePath(_ appGroupPath: String) -> String {
|
||||
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 finish(with reason: IpcGroupCallBufferBroadcastContext.Status.FinishReason) {
|
||||
var errorString: String?
|
||||
switch reason {
|
||||
case .callEnded:
|
||||
errorString = "You're not in a voice chat"
|
||||
case .error:
|
||||
errorString = "Finished"
|
||||
case .screencastEnded:
|
||||
break
|
||||
}
|
||||
if let errorString = errorString {
|
||||
let error = NSError(domain: "BroadcastUploadExtension", code: 1, userInfo: [
|
||||
NSLocalizedDescriptionKey: errorString
|
||||
])
|
||||
finishBroadcastWithError(error)
|
||||
} else {
|
||||
finishBroadcastGracefully(self)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
self.finish(with: .error)
|
||||
return
|
||||
}
|
||||
|
||||
@ -65,7 +67,7 @@ private func rootPathForBasePath(_ appGroupPath: String) -> String {
|
||||
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
||||
|
||||
guard let appGroupUrl = maybeAppGroupUrl else {
|
||||
self.finishWithGenericError()
|
||||
self.finish(with: .error)
|
||||
return
|
||||
}
|
||||
|
||||
@ -83,8 +85,8 @@ private func rootPathForBasePath(_ appGroupPath: String) -> String {
|
||||
return
|
||||
}
|
||||
switch status {
|
||||
case .finished:
|
||||
strongSelf.finishWithNoBroadcast()
|
||||
case let .finished(reason):
|
||||
strongSelf.finish(with: reason)
|
||||
}
|
||||
})
|
||||
|
||||
|
22
submodules/BroadcastUploadHelpers/BUILD
Normal file
22
submodules/BroadcastUploadHelpers/BUILD
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
objc_library(
|
||||
name = "BroadcastUploadHelpers",
|
||||
enable_modules = True,
|
||||
module_name = "BroadcastUploadHelpers",
|
||||
srcs = glob([
|
||||
"Sources/**/*.m",
|
||||
"Sources/**/*.h",
|
||||
]),
|
||||
hdrs = glob([
|
||||
"PublicHeaders/**/*.h",
|
||||
]),
|
||||
includes = [
|
||||
"PublicHeaders",
|
||||
],
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,8 @@
|
||||
#ifndef BroadcastUploadHelpers_h
|
||||
#define BroadcastUploadHelpers_h
|
||||
|
||||
#import <ReplayKit/ReplayKit.h>
|
||||
|
||||
void finishBroadcastGracefully(RPBroadcastSampleHandler * _Nonnull broadcastSampleHandler);
|
||||
|
||||
#endif /* BroadcastUploadHelpers_h */
|
8
submodules/BroadcastUploadHelpers/Sources/BroadcastUploadHelpers.m
Executable file
8
submodules/BroadcastUploadHelpers/Sources/BroadcastUploadHelpers.m
Executable file
@ -0,0 +1,8 @@
|
||||
#import <BroadcastUploadHelpers/BroadcastUploadHelpers.h>
|
||||
|
||||
void finishBroadcastGracefully(RPBroadcastSampleHandler * _Nonnull broadcastSampleHandler) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wnonnull"
|
||||
[broadcastSampleHandler finishBroadcastWithError:nil];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
@ -2637,6 +2637,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
accessHash: callInfo.accessHash
|
||||
).start())
|
||||
}
|
||||
|
||||
self.screencastBufferServerContext?.stopScreencast()
|
||||
}
|
||||
/*if let _ = self.screencastIpcContext {
|
||||
self.screencastIpcContext = nil
|
||||
|
@ -23,6 +23,11 @@ private struct KeepaliveInfo: Codable {
|
||||
var timestamp: Int32
|
||||
}
|
||||
|
||||
private struct CutoffPayload: Codable {
|
||||
var id: UInt32
|
||||
var timestamp: Int32
|
||||
}
|
||||
|
||||
private let checkInterval: Double = 0.2
|
||||
private let keepaliveTimeout: Double = 2.0
|
||||
|
||||
@ -42,273 +47,14 @@ private func keepaliveInfoPath(basePath: String) -> String {
|
||||
return basePath + "/keepaliveInfo.json"
|
||||
}
|
||||
|
||||
private func cutoffPayloadPath(basePath: String) -> String {
|
||||
return basePath + "/cutoffPayload.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<String>()
|
||||
public var joinPayload: Signal<String, NoError> {
|
||||
return self.joinPayloadPromise.get()
|
||||
}
|
||||
private var joinPayloadCheckTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private let isActivePromise = ValuePromise<Bool>(false, ignoreRepeated: true)
|
||||
public var isActive: Signal<Bool, NoError> {
|
||||
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<Request>()
|
||||
public var request: Signal<Request, NoError> {
|
||||
return self.requestPromise.get()
|
||||
}
|
||||
|
||||
private var joinResponsePayloadCheckTimer: SwiftSignalKit.Timer?
|
||||
private let joinResponsePayloadPromise = Promise<String>()
|
||||
public var joinResponsePayload: Signal<String, NoError> {
|
||||
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
|
||||
@ -732,11 +478,30 @@ public final class IpcGroupCallBufferAppContext {
|
||||
|
||||
self.isActivePromise.set(true)
|
||||
}
|
||||
|
||||
public func stopScreencast() {
|
||||
let timestamp = Int32(Date().timeIntervalSince1970)
|
||||
let cutoffPayload = CutoffPayload(
|
||||
id: self.id,
|
||||
timestamp: timestamp
|
||||
)
|
||||
guard let cutoffPayloadData = try? JSONEncoder().encode(cutoffPayload) else {
|
||||
return
|
||||
}
|
||||
guard let _ = try? cutoffPayloadData.write(to: URL(fileURLWithPath: cutoffPayloadPath(basePath: self.basePath)), options: .atomic) else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class IpcGroupCallBufferBroadcastContext {
|
||||
public enum Status {
|
||||
case finished
|
||||
public enum FinishReason {
|
||||
case screencastEnded
|
||||
case callEnded
|
||||
case error
|
||||
}
|
||||
case finished(FinishReason)
|
||||
}
|
||||
|
||||
private let basePath: String
|
||||
@ -752,9 +517,9 @@ public final class IpcGroupCallBufferBroadcastContext {
|
||||
private var currentId: UInt32?
|
||||
|
||||
private var callActiveInfoTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private var keepaliveInfoTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private var screencastCutoffTimer: SwiftSignalKit.Timer?
|
||||
|
||||
public init(basePath: String) {
|
||||
self.basePath = basePath
|
||||
let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil)
|
||||
@ -766,6 +531,12 @@ public final class IpcGroupCallBufferBroadcastContext {
|
||||
}, queue: .mainQueue())
|
||||
self.callActiveInfoTimer = callActiveInfoTimer
|
||||
callActiveInfoTimer.start()
|
||||
|
||||
let screencastCutoffTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: true, completion: { [weak self] in
|
||||
self?.updateScreencastCutoff()
|
||||
}, queue: .mainQueue())
|
||||
self.screencastCutoffTimer = screencastCutoffTimer
|
||||
screencastCutoffTimer.start()
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -773,28 +544,45 @@ public final class IpcGroupCallBufferBroadcastContext {
|
||||
|
||||
self.callActiveInfoTimer?.invalidate()
|
||||
self.keepaliveInfoTimer?.invalidate()
|
||||
self.screencastCutoffTimer?.invalidate()
|
||||
}
|
||||
|
||||
private func updateScreencastCutoff() {
|
||||
let filePath = cutoffPayloadPath(basePath: self.basePath)
|
||||
guard let cutoffPayloadData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let cutoffPayload = try? JSONDecoder().decode(CutoffPayload.self, from: cutoffPayloadData) else {
|
||||
return
|
||||
}
|
||||
|
||||
if let currentId = self.currentId, currentId == cutoffPayload.id {
|
||||
self.statusPromise.set(.single(.finished(.screencastEnded)))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func updateCallIsActive() {
|
||||
let filePath = payloadDescriptionPath(basePath: self.basePath)
|
||||
guard let payloadDescriptionData = try? Data(contentsOf: URL(fileURLWithPath: filePath)) else {
|
||||
self.statusPromise.set(.single(.finished))
|
||||
self.statusPromise.set(.single(.finished(.error)))
|
||||
return
|
||||
}
|
||||
|
||||
guard let payloadDescription = try? JSONDecoder().decode(PayloadDescription.self, from: payloadDescriptionData) else {
|
||||
self.statusPromise.set(.single(.finished))
|
||||
self.statusPromise.set(.single(.finished(.error)))
|
||||
return
|
||||
}
|
||||
let timestamp = Int32(Date().timeIntervalSince1970)
|
||||
if payloadDescription.timestamp < timestamp - 4 {
|
||||
self.statusPromise.set(.single(.finished))
|
||||
self.statusPromise.set(.single(.finished(.callEnded)))
|
||||
return
|
||||
}
|
||||
|
||||
if let currentId = self.currentId {
|
||||
if currentId != payloadDescription.id {
|
||||
self.statusPromise.set(.single(.finished))
|
||||
self.statusPromise.set(.single(.finished(.callEnded)))
|
||||
}
|
||||
} else {
|
||||
self.currentId = payloadDescription.id
|
||||
|
Loading…
x
Reference in New Issue
Block a user