[WIP] Conference

This commit is contained in:
Isaac
2025-01-21 11:04:37 +04:00
parent 550fd89558
commit a4717b7906
47 changed files with 4439 additions and 816 deletions

View File

@@ -13,6 +13,7 @@ import AccountContext
import TelegramNotices
import AppBundle
import TooltipUI
import CallScreen
protocol CallControllerNodeProtocol: AnyObject {
var isMuted: Bool { get set }
@@ -41,193 +42,6 @@ protocol CallControllerNodeProtocol: AnyObject {
}
public final class CallController: ViewController {
public enum Call: Equatable {
case call(PresentationCall)
case groupCall(PresentationGroupCall)
public static func ==(lhs: Call, rhs: Call) -> Bool {
switch lhs {
case let .call(lhsCall):
if case let .call(rhsCall) = rhs {
return lhsCall === rhsCall
} else {
return false
}
case let .groupCall(lhsGroupCall):
if case let .groupCall(rhsGroupCall) = rhs {
return lhsGroupCall === rhsGroupCall
} else {
return false
}
}
}
public var context: AccountContext {
switch self {
case let .call(call):
return call.context
case let .groupCall(groupCall):
return groupCall.accountContext
}
}
public var peerId: EnginePeer.Id? {
switch self {
case let .call(call):
return call.peerId
case let .groupCall(groupCall):
return groupCall.peerId
}
}
public func requestVideo() {
switch self {
case let .call(call):
call.requestVideo()
case let .groupCall(groupCall):
groupCall.requestVideo()
}
}
public func disableVideo() {
switch self {
case let .call(call):
call.disableVideo()
case let .groupCall(groupCall):
groupCall.disableVideo()
}
}
public func disableScreencast() {
switch self {
case let .call(call):
(call as? PresentationCallImpl)?.disableScreencast()
case let .groupCall(groupCall):
groupCall.disableScreencast()
}
}
public func switchVideoCamera() {
switch self {
case let .call(call):
call.switchVideoCamera()
case let .groupCall(groupCall):
groupCall.switchVideoCamera()
}
}
public func toggleIsMuted() {
switch self {
case let .call(call):
call.toggleIsMuted()
case let .groupCall(groupCall):
groupCall.toggleIsMuted()
}
}
public func setCurrentAudioOutput(_ output: AudioSessionOutput) {
switch self {
case let .call(call):
call.setCurrentAudioOutput(output)
case let .groupCall(groupCall):
groupCall.setCurrentAudioOutput(output)
}
}
public var isMuted: Signal<Bool, NoError> {
switch self {
case let .call(call):
return call.isMuted
case let .groupCall(groupCall):
return groupCall.isMuted
}
}
public var audioLevel: Signal<Float, NoError> {
switch self {
case let .call(call):
return call.audioLevel
case let .groupCall(groupCall):
var audioLevelId: UInt32?
return groupCall.audioLevels |> map { audioLevels -> Float in
var result: Float = 0
for item in audioLevels {
if let audioLevelId {
if item.1 == audioLevelId {
result = item.2
break
}
} else {
if item.1 != 0 {
audioLevelId = item.1
result = item.2
break
}
}
}
return result
}
}
}
public var isOutgoing: Bool {
switch self {
case let .call(call):
return call.isOutgoing
case .groupCall:
return false
}
}
public func makeOutgoingVideoView(completion: @escaping (PresentationCallVideoView?) -> Void) {
switch self {
case let .call(call):
call.makeOutgoingVideoView(completion: completion)
case let .groupCall(groupCall):
groupCall.makeOutgoingVideoView(requestClone: false, completion: { a, _ in
completion(a)
})
}
}
public var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> {
switch self {
case let .call(call):
return call.audioOutputState
case let .groupCall(groupCall):
return groupCall.audioOutputState
}
}
public func debugInfo() -> Signal<(String, String), NoError> {
switch self {
case let .call(call):
return call.debugInfo()
case .groupCall:
return .single(("", ""))
}
}
public func answer() {
switch self {
case let .call(call):
call.answer()
case .groupCall:
break
}
}
public func hangUp() -> Signal<Bool, NoError> {
switch self {
case let .call(call):
return call.hangUp()
case let .groupCall(groupCall):
return groupCall.leave(terminateIfPossible: false)
}
}
}
private var controllerNode: CallControllerNodeProtocol {
return self.displayNode as! CallControllerNodeProtocol
}
@@ -242,7 +56,7 @@ public final class CallController: ViewController {
private let sharedContext: SharedAccountContext
private let account: Account
public let call: CallController.Call
public let call: PresentationCall
private let easyDebugAccess: Bool
private var presentationData: PresentationData
@@ -268,7 +82,7 @@ public final class CallController: ViewController {
public var onViewDidAppear: (() -> Void)?
public var onViewDidDisappear: (() -> Void)?
public init(sharedContext: SharedAccountContext, account: Account, call: CallController.Call, easyDebugAccess: Bool) {
public init(sharedContext: SharedAccountContext, account: Account, call: PresentationCall, easyDebugAccess: Bool) {
self.sharedContext = sharedContext
self.account = account
self.call = call
@@ -293,84 +107,10 @@ public final class CallController: ViewController {
self.supportedOrientations = ViewControllerSupportedOrientations(regularSize: .portrait, compactSize: .portrait)
switch call {
case let .call(call):
self.disposable = (call.state
|> deliverOnMainQueue).start(next: { [weak self] callState in
self?.callStateUpdated(callState)
})
case let .groupCall(groupCall):
let accountPeerId = groupCall.account.peerId
let videoEndpoints: Signal<(local: String?, remote: PresentationGroupCallRequestedVideo?), NoError> = groupCall.members
|> map { members -> (local: String?, remote: PresentationGroupCallRequestedVideo?) in
guard let members else {
return (nil, nil)
}
var local: String?
var remote: PresentationGroupCallRequestedVideo?
for participant in members.participants {
if let video = participant.requestedPresentationVideoChannel(minQuality: .thumbnail, maxQuality: .full) ?? participant.requestedVideoChannel(minQuality: .thumbnail, maxQuality: .full) {
if participant.peer.id == accountPeerId {
local = video.endpointId
} else {
if remote == nil {
remote = video
}
}
}
}
return (local, remote)
}
|> distinctUntilChanged(isEqual: { lhs, rhs in
return lhs == rhs
})
var startTimestamp: Double?
self.disposable = (combineLatest(queue: .mainQueue(),
groupCall.state,
videoEndpoints
)
|> deliverOnMainQueue).start(next: { [weak self] callState, videoEndpoints in
guard let self else {
return
}
let mappedState: PresentationCallState.State
switch callState.networkState {
case .connecting:
mappedState = .connecting(nil)
case .connected:
let timestamp = startTimestamp ?? CFAbsoluteTimeGetCurrent()
startTimestamp = timestamp
mappedState = .active(timestamp, nil, Data())
}
var mappedLocalVideoState: PresentationCallState.VideoState = .inactive
var mappedRemoteVideoState: PresentationCallState.RemoteVideoState = .inactive
if let local = videoEndpoints.local {
mappedLocalVideoState = .active(isScreencast: false, endpointId: local)
}
if let remote = videoEndpoints.remote {
mappedRemoteVideoState = .active(endpointId: remote.endpointId)
}
if case let .groupCall(groupCall) = self.call {
var requestedVideo: [PresentationGroupCallRequestedVideo] = []
if let remote = videoEndpoints.remote {
requestedVideo.append(remote)
}
groupCall.setRequestedVideoList(items: requestedVideo)
}
self.callStateUpdated(PresentationCallState(
state: mappedState,
videoState: mappedLocalVideoState,
remoteVideoState: mappedRemoteVideoState,
remoteAudioState: .active,
remoteBatteryLevel: .normal
))
})
}
self.disposable = (call.state
|> deliverOnMainQueue).start(next: { [weak self] callState in
self?.callStateUpdated(callState)
})
self.callMutedDisposable = (call.isMuted
|> deliverOnMainQueue).start(next: { [weak self] value in
@@ -605,11 +345,7 @@ public final class CallController: ViewController {
}
let callPeerView: Signal<PeerView?, NoError>
if let peerId = self.call.peerId {
callPeerView = self.account.postbox.peerView(id: peerId) |> map(Optional.init)
} else {
callPeerView = .single(nil)
}
callPeerView = self.account.postbox.peerView(id: self.call.peerId) |> map(Optional.init)
self.peerDisposable = (combineLatest(queue: .mainQueue(),
self.account.postbox.peerView(id: self.account.peerId) |> take(1),
@@ -659,6 +395,26 @@ public final class CallController: ViewController {
self.onViewDidDisappear?()
}
final class AnimateOutToGroupChat {
let incomingPeerId: EnginePeer.Id
let incomingVideoLayer: CALayer?
let incomingVideoPlaceholder: VideoSource.Output?
init(
incomingPeerId: EnginePeer.Id,
incomingVideoLayer: CALayer?,
incomingVideoPlaceholder: VideoSource.Output?
) {
self.incomingPeerId = incomingPeerId
self.incomingVideoLayer = incomingVideoLayer
self.incomingVideoPlaceholder = incomingVideoPlaceholder
}
}
func animateOutToGroupChat(completion: @escaping () -> Void) -> AnimateOutToGroupChat? {
return (self.controllerNode as? CallControllerNodeV2)?.animateOutToGroupChat(completion: completion)
}
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition)
@@ -674,7 +430,17 @@ public final class CallController: ViewController {
})
}
public func dismissWithoutAnimation() {
self.presentingViewController?.dismiss(animated: false, completion: nil)
}
private func conferenceAddParticipant() {
if "".isEmpty {
let _ = self.call.upgradeToConference(completion: { _ in
})
return
}
let controller = self.call.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(
context: self.call.context,
filter: [.onlyWriteable],
@@ -690,17 +456,13 @@ public final class CallController: ViewController {
guard let self else {
return
}
guard case let .call(call) = self.call else {
return
}
guard let call = call as? PresentationCallImpl else {
guard let call = self.call as? PresentationCallImpl else {
return
}
let _ = call.requestAddToConference(peerId: peer.id)
}
self.dismiss()
(self.call.context.sharedContext.mainWindow?.viewController as? NavigationController)?.pushViewController(controller)
self.present(controller, in: .current)
}
@objc private func backPressed() {