mirror of
https://github.com/TelegramMessenger/Telegram-iOS.git
synced 2026-07-06 03:33:41 +02:00
346 lines
13 KiB
Swift
346 lines
13 KiB
Swift
import Foundation
|
|
import UIKit
|
|
import Postbox
|
|
import TelegramCore
|
|
import SyncCore
|
|
import SwiftSignalKit
|
|
import Display
|
|
import AVFoundation
|
|
import TelegramVoip
|
|
import TelegramAudio
|
|
import TelegramUIPreferences
|
|
import TelegramPresentationData
|
|
import DeviceAccess
|
|
import UniversalMediaPlayer
|
|
import AccountContext
|
|
|
|
public final class PresentationGroupCallImpl: PresentationGroupCall {
|
|
private enum InternalState {
|
|
case requesting
|
|
case active(GroupCallInfo)
|
|
case estabilished(GroupCallInfo, String, [Int32])
|
|
|
|
var callInfo: GroupCallInfo? {
|
|
switch self {
|
|
case .requesting:
|
|
return nil
|
|
case let .active(info):
|
|
return info
|
|
case let .estabilished(info, _, _):
|
|
return info
|
|
}
|
|
}
|
|
}
|
|
|
|
public let account: Account
|
|
private let audioSession: ManagedAudioSession
|
|
private let callKitIntegration: CallKitIntegration?
|
|
public var isIntegratedWithCallKit: Bool {
|
|
return self.callKitIntegration != nil
|
|
}
|
|
|
|
private let getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void)
|
|
|
|
public let internalId: CallSessionInternalId
|
|
public let peerId: PeerId
|
|
public let peer: Peer?
|
|
|
|
private var internalState: InternalState = .requesting
|
|
|
|
private var callContext: OngoingGroupCallContext?
|
|
|
|
private var sessionStateDisposable: Disposable?
|
|
|
|
private let isMutedPromise = ValuePromise<Bool>(false)
|
|
private var isMutedValue = false
|
|
public var isMuted: Signal<Bool, NoError> {
|
|
return self.isMutedPromise.get()
|
|
}
|
|
|
|
private let audioOutputStatePromise = Promise<([AudioSessionOutput], AudioSessionOutput?)>(([], nil))
|
|
private var audioOutputStateValue: ([AudioSessionOutput], AudioSessionOutput?) = ([], nil)
|
|
private var currentAudioOutputValue: AudioSessionOutput = .builtin
|
|
public var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> {
|
|
return self.audioOutputStatePromise.get()
|
|
}
|
|
|
|
private var audioSessionControl: ManagedAudioSessionControl?
|
|
private var audioSessionDisposable: Disposable?
|
|
private let audioSessionShouldBeActive = ValuePromise<Bool>(false, ignoreRepeated: true)
|
|
private var audioSessionShouldBeActiveDisposable: Disposable?
|
|
private let audioSessionActive = Promise<Bool>(false)
|
|
private var audioSessionActiveDisposable: Disposable?
|
|
private var isAudioSessionActive = false
|
|
|
|
let canBeRemoved = Promise<Bool>(false)
|
|
|
|
private let requestDisposable = MetaDisposable()
|
|
private var groupCallParticipantUpdatesDisposable: Disposable?
|
|
|
|
init(
|
|
account: Account,
|
|
audioSession: ManagedAudioSession,
|
|
callKitIntegration: CallKitIntegration?,
|
|
getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void),
|
|
internalId: CallSessionInternalId,
|
|
peerId: PeerId,
|
|
peer: Peer?
|
|
) {
|
|
self.account = account
|
|
self.audioSession = audioSession
|
|
self.callKitIntegration = callKitIntegration
|
|
self.getDeviceAccessData = getDeviceAccessData
|
|
|
|
self.internalId = internalId
|
|
self.peerId = peerId
|
|
self.peer = peer
|
|
|
|
var didReceiveAudioOutputs = false
|
|
|
|
self.audioSessionDisposable = audioSession.push(audioSessionType: .voiceCall, manualActivate: { [weak self] control in
|
|
Queue.mainQueue().async {
|
|
if let strongSelf = self {
|
|
strongSelf.updateSessionState(internalState: strongSelf.internalState, audioSessionControl: control)
|
|
}
|
|
}
|
|
}, deactivate: { [weak self] in
|
|
return Signal { subscriber in
|
|
Queue.mainQueue().async {
|
|
if let strongSelf = self {
|
|
strongSelf.updateIsAudioSessionActive(false)
|
|
strongSelf.updateSessionState(internalState: strongSelf.internalState, audioSessionControl: nil)
|
|
}
|
|
subscriber.putCompletion()
|
|
}
|
|
return EmptyDisposable
|
|
}
|
|
}, availableOutputsChanged: { [weak self] availableOutputs, currentOutput in
|
|
Queue.mainQueue().async {
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
strongSelf.audioOutputStateValue = (availableOutputs, currentOutput)
|
|
|
|
var signal: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> = .single((availableOutputs, currentOutput))
|
|
if !didReceiveAudioOutputs {
|
|
didReceiveAudioOutputs = true
|
|
if currentOutput == .speaker {
|
|
signal = .single((availableOutputs, .builtin))
|
|
|> then(
|
|
signal
|
|
|> delay(1.0, queue: Queue.mainQueue())
|
|
)
|
|
}
|
|
}
|
|
strongSelf.audioOutputStatePromise.set(signal)
|
|
}
|
|
})
|
|
|
|
self.audioSessionShouldBeActiveDisposable = (self.audioSessionShouldBeActive.get()
|
|
|> deliverOnMainQueue).start(next: { [weak self] value in
|
|
if let strongSelf = self {
|
|
if value {
|
|
if let audioSessionControl = strongSelf.audioSessionControl {
|
|
let audioSessionActive: Signal<Bool, NoError>
|
|
if let callKitIntegration = strongSelf.callKitIntegration {
|
|
audioSessionActive = callKitIntegration.audioSessionActive
|
|
|> filter { $0 }
|
|
|> timeout(2.0, queue: Queue.mainQueue(), alternate: Signal { subscriber in
|
|
if let strongSelf = self, let _ = strongSelf.audioSessionControl {
|
|
}
|
|
subscriber.putNext(true)
|
|
subscriber.putCompletion()
|
|
return EmptyDisposable
|
|
})
|
|
} else {
|
|
audioSessionControl.activate({ _ in })
|
|
audioSessionActive = .single(true)
|
|
}
|
|
strongSelf.audioSessionActive.set(audioSessionActive)
|
|
} else {
|
|
strongSelf.audioSessionActive.set(.single(false))
|
|
}
|
|
} else {
|
|
strongSelf.audioSessionActive.set(.single(false))
|
|
}
|
|
}
|
|
})
|
|
|
|
self.audioSessionActiveDisposable = (self.audioSessionActive.get()
|
|
|> deliverOnMainQueue).start(next: { [weak self] value in
|
|
if let strongSelf = self {
|
|
strongSelf.updateIsAudioSessionActive(value)
|
|
}
|
|
})
|
|
|
|
self.requestCall()
|
|
|
|
self.groupCallParticipantUpdatesDisposable = (self.account.stateManager.groupCallParticipantUpdates
|
|
|> deliverOnMainQueue).start(next: { [weak self] updates in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
if case let .estabilished(callInfo, _, _) = strongSelf.internalState {
|
|
var addedSsrc: [Int32] = []
|
|
for (callId, ssrc) in updates {
|
|
if callId == callInfo.id {
|
|
addedSsrc.append(ssrc)
|
|
}
|
|
}
|
|
if !addedSsrc.isEmpty {
|
|
strongSelf.callContext?.addSsrcs(ssrcs: addedSsrc)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
deinit {
|
|
self.audioSessionShouldBeActiveDisposable?.dispose()
|
|
self.audioSessionActiveDisposable?.dispose()
|
|
self.sessionStateDisposable?.dispose()
|
|
self.audioSessionDisposable?.dispose()
|
|
self.requestDisposable.dispose()
|
|
self.groupCallParticipantUpdatesDisposable?.dispose()
|
|
}
|
|
|
|
private func updateSessionState(internalState: InternalState, audioSessionControl: ManagedAudioSessionControl?) {
|
|
let previousControl = self.audioSessionControl
|
|
self.audioSessionControl = audioSessionControl
|
|
|
|
let previousInternalState = self.internalState
|
|
self.internalState = internalState
|
|
|
|
if let audioSessionControl = audioSessionControl, previousControl == nil {
|
|
audioSessionControl.setOutputMode(.custom(self.currentAudioOutputValue))
|
|
audioSessionControl.setup(synchronous: true)
|
|
}
|
|
|
|
self.audioSessionShouldBeActive.set(true)
|
|
|
|
switch previousInternalState {
|
|
case .active:
|
|
break
|
|
default:
|
|
if case let .active(callInfo) = internalState {
|
|
let callContext = OngoingGroupCallContext()
|
|
self.callContext = callContext
|
|
self.requestDisposable.set((callContext.joinPayload
|
|
|> take(1)
|
|
|> deliverOnMainQueue).start(next: { [weak self] joinPayload in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
strongSelf.requestDisposable.set((joinGroupCall(
|
|
account: strongSelf.account,
|
|
callId: callInfo.id,
|
|
accessHash: callInfo.accessHash,
|
|
joinPayload: joinPayload
|
|
)
|
|
|> deliverOnMainQueue).start(next: { joinCallResult in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
if let clientParams = joinCallResult.callInfo.clientParams {
|
|
strongSelf.updateSessionState(internalState: .estabilished(joinCallResult.callInfo, clientParams, joinCallResult.ssrcs), audioSessionControl: strongSelf.audioSessionControl)
|
|
}
|
|
}))
|
|
}))
|
|
}
|
|
}
|
|
|
|
switch previousInternalState {
|
|
case .estabilished:
|
|
break
|
|
default:
|
|
if case let .estabilished(_, clientParams, ssrcs) = internalState {
|
|
self.callContext?.setJoinResponse(payload: clientParams, ssrcs: ssrcs)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func updateIsAudioSessionActive(_ value: Bool) {
|
|
if self.isAudioSessionActive != value {
|
|
self.isAudioSessionActive = value
|
|
}
|
|
}
|
|
|
|
public func hangUp() -> Signal<Bool, NoError> {
|
|
return .single(true)
|
|
}
|
|
|
|
public func toggleIsMuted() {
|
|
self.setIsMuted(!self.isMutedValue)
|
|
}
|
|
|
|
public func setIsMuted(_ value: Bool) {
|
|
self.isMutedValue = value
|
|
self.isMutedPromise.set(self.isMutedValue)
|
|
self.callContext?.setIsMuted(self.isMutedValue)
|
|
}
|
|
|
|
public func setCurrentAudioOutput(_ output: AudioSessionOutput) {
|
|
guard self.currentAudioOutputValue != output else {
|
|
return
|
|
}
|
|
self.currentAudioOutputValue = output
|
|
|
|
self.audioOutputStatePromise.set(.single((self.audioOutputStateValue.0, output))
|
|
|> then(
|
|
.single(self.audioOutputStateValue)
|
|
|> delay(1.0, queue: Queue.mainQueue())
|
|
))
|
|
|
|
if let audioSessionControl = self.audioSessionControl {
|
|
audioSessionControl.setOutputMode(.custom(output))
|
|
}
|
|
}
|
|
|
|
private func requestCall() {
|
|
self.internalState = .requesting
|
|
|
|
enum CallError {
|
|
case generic
|
|
}
|
|
|
|
let account = self.account
|
|
let peerId = self.peerId
|
|
|
|
let currentCall = getCurrentGroupCall(account: account, peerId: peerId)
|
|
|> mapError { _ -> CallError in
|
|
return .generic
|
|
}
|
|
|
|
let currentOrRequestedCall = currentCall
|
|
|> mapToSignal { callInfo -> Signal<GroupCallInfo, CallError> in
|
|
if let callInfo = callInfo {
|
|
return .single(callInfo)
|
|
} else {
|
|
return createGroupCall(account: account, peerId: peerId)
|
|
|> mapError { _ -> CallError in
|
|
return .generic
|
|
}
|
|
}
|
|
}
|
|
|
|
let restartedCall = currentOrRequestedCall
|
|
|> mapToSignal { value -> Signal<GroupCallInfo, CallError> in
|
|
let stopped: Signal<GroupCallInfo, CallError> = stopGroupCall(account: account, callId: value.id, accessHash: value.accessHash)
|
|
|> mapError { _ -> CallError in
|
|
return .generic
|
|
}
|
|
|> map { _ -> GroupCallInfo in
|
|
}
|
|
|
|
return stopped
|
|
|> then(currentOrRequestedCall)
|
|
}
|
|
|
|
self.requestDisposable.set((currentOrRequestedCall
|
|
|> deliverOnMainQueue).start(next: { [weak self] value in
|
|
guard let strongSelf = self else {
|
|
return
|
|
}
|
|
strongSelf.updateSessionState(internalState: .active(value), audioSessionControl: strongSelf.audioSessionControl)
|
|
}))
|
|
}
|
|
}
|