From cc6537b811588132427435fc593ca0a4fc2ade34 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Thu, 2 Jul 2020 21:47:32 +0400 Subject: [PATCH 1/4] Temp --- submodules/TgVoipWebrtc/Impl/Manager.cpp | 8 +- submodules/TgVoipWebrtc/Impl/Manager.h | 4 + submodules/TgVoipWebrtc/Impl/MediaManager.cpp | 2 +- submodules/TgVoipWebrtc/Impl/MediaManager.h | 6 +- submodules/TgVoipWebrtc/Impl/TgVoip.h | 11 +++ submodules/TgVoipWebrtc/Impl/TgVoip.mm | 87 ++++++++++++++++++- .../Impl/VideoCaptureInterfaceImpl.h | 0 .../Impl/VideoCaptureInterfaceImpl.mm | 0 8 files changed, 111 insertions(+), 7 deletions(-) create mode 100644 submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.h create mode 100644 submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.mm diff --git a/submodules/TgVoipWebrtc/Impl/Manager.cpp b/submodules/TgVoipWebrtc/Impl/Manager.cpp index 6ffced3943..3f43b28a99 100644 --- a/submodules/TgVoipWebrtc/Impl/Manager.cpp +++ b/submodules/TgVoipWebrtc/Impl/Manager.cpp @@ -26,8 +26,7 @@ static rtc::Thread *makeMediaThread() { return value.get(); } - -static rtc::Thread *getMediaThread() { +rtc::Thread *Manager::getMediaThread() { static rtc::Thread *value = makeMediaThread(); return value; } @@ -38,6 +37,7 @@ Manager::Manager( bool enableP2P, std::vector const &rtcServers, bool isVideo, + std::shared_ptr videoCapture, std::function stateUpdated, std::function videoStateUpdated, std::function remoteVideoIsActiveUpdated, @@ -48,6 +48,7 @@ _encryptionKey(encryptionKey), _enableP2P(enableP2P), _rtcServers(rtcServers), _startWithVideo(isVideo), +_videoCapture(videoCapture), _stateUpdated(stateUpdated), _videoStateUpdated(videoStateUpdated), _remoteVideoIsActiveUpdated(remoteVideoIsActiveUpdated), @@ -111,11 +112,12 @@ void Manager::start() { ); })); bool isOutgoing = _encryptionKey.isOutgoing; - _mediaManager.reset(new ThreadLocalObject(getMediaThread(), [isOutgoing, thread = _thread, startWithVideo = _startWithVideo, weakThis]() { + _mediaManager.reset(new ThreadLocalObject(getMediaThread(), [isOutgoing, thread = _thread, startWithVideo = _startWithVideo, videoCapture = _videoCapture, weakThis]() { return new MediaManager( getMediaThread(), isOutgoing, startWithVideo, + videoCapture, [thread, weakThis](const rtc::CopyOnWriteBuffer &packet) { thread->PostTask(RTC_FROM_HERE, [weakThis, packet]() { auto strongThis = weakThis.lock(); diff --git a/submodules/TgVoipWebrtc/Impl/Manager.h b/submodules/TgVoipWebrtc/Impl/Manager.h index 79e8583937..7565cf73ea 100644 --- a/submodules/TgVoipWebrtc/Impl/Manager.h +++ b/submodules/TgVoipWebrtc/Impl/Manager.h @@ -12,12 +12,15 @@ namespace TGVOIP_NAMESPACE { class Manager : public std::enable_shared_from_this { public: + static rtc::Thread *getMediaThread(); + Manager( rtc::Thread *thread, TgVoipEncryptionKey encryptionKey, bool enableP2P, std::vector const &rtcServers, bool isVideo, + std::shared_ptr videoCapture, std::function stateUpdated, std::function videoStateUpdated, std::function remoteVideoIsActiveUpdated, @@ -40,6 +43,7 @@ private: bool _enableP2P; std::vector _rtcServers; bool _startWithVideo; + std::shared_ptr _videoCapture; std::function _stateUpdated; std::function _videoStateUpdated; std::function _remoteVideoIsActiveUpdated; diff --git a/submodules/TgVoipWebrtc/Impl/MediaManager.cpp b/submodules/TgVoipWebrtc/Impl/MediaManager.cpp index b1e22eb78f..65fe983313 100644 --- a/submodules/TgVoipWebrtc/Impl/MediaManager.cpp +++ b/submodules/TgVoipWebrtc/Impl/MediaManager.cpp @@ -164,7 +164,7 @@ static rtc::Thread *makeWorkerThread() { } -static rtc::Thread *getWorkerThread() { +static rtc::Thread *MediaManager::getWorkerThread() { static rtc::Thread *value = makeWorkerThread(); return value; } diff --git a/submodules/TgVoipWebrtc/Impl/MediaManager.h b/submodules/TgVoipWebrtc/Impl/MediaManager.h index 5e49c94ff4..9ed8b7819a 100644 --- a/submodules/TgVoipWebrtc/Impl/MediaManager.h +++ b/submodules/TgVoipWebrtc/Impl/MediaManager.h @@ -54,10 +54,13 @@ private: friend class MediaManager::NetworkInterfaceImpl; public: + static rtc::Thread *getWorkerThread(); + MediaManager( rtc::Thread *thread, bool isOutgoing, bool startWithVideo, + std::shared_ptr videoCapture, std::function packetEmitted, std::function localVideoCaptureActiveUpdated ); @@ -99,8 +102,7 @@ private: std::unique_ptr _audioChannel; std::unique_ptr _videoChannel; std::unique_ptr _videoBitrateAllocatorFactory; - rtc::scoped_refptr _nativeVideoSource; - std::unique_ptr _videoCapturer; + std::shared_ptr _videoCapture; std::shared_ptr> _currentIncomingVideoSink; std::shared_ptr> _currentOutgoingVideoSink; diff --git a/submodules/TgVoipWebrtc/Impl/TgVoip.h b/submodules/TgVoipWebrtc/Impl/TgVoip.h index c3be58baf5..463b71caef 100644 --- a/submodules/TgVoipWebrtc/Impl/TgVoip.h +++ b/submodules/TgVoipWebrtc/Impl/TgVoip.h @@ -129,6 +129,16 @@ struct TgVoipAudioDataCallbacks { std::function preprocessed; }; +class TgVoipVideoCaptureInterface { +protected: + TgVoipVideoCaptureInterface() = default; +public: + static std::shared_ptr makeInstance(); + + virtual ~TgVoipVideoCaptureInterface(); + virtual void setVideoOutput(std::shared_ptr> sink) = 0; +}; + class TgVoip { protected: TgVoip() = default; @@ -147,6 +157,7 @@ public: TgVoipNetworkType initialNetworkType, TgVoipEncryptionKey const &encryptionKey, bool isVideo, + std::shared_ptr videoCapture, std::function stateUpdated, std::function videoStateUpdated, std::function remoteVideoIsActiveUpdated, diff --git a/submodules/TgVoipWebrtc/Impl/TgVoip.mm b/submodules/TgVoipWebrtc/Impl/TgVoip.mm index 905f997955..22f5b8d94f 100644 --- a/submodules/TgVoipWebrtc/Impl/TgVoip.mm +++ b/submodules/TgVoipWebrtc/Impl/TgVoip.mm @@ -5,10 +5,19 @@ #include "rtc_base/logging.h" #include "Manager.h" +#include "MediaManager.h" #include #include +#if TARGET_OS_IPHONE + +#include "CodecsApple.h" + +#else +#error "Unsupported platform" +#endif + #import #include @@ -143,6 +152,7 @@ public: TgVoipConfig const &config, TgVoipEncryptionKey const &encryptionKey, bool isVideo, + std::shared_ptr videoCapture, TgVoipNetworkType initialNetworkType, std::function stateUpdated, std::function videoStateUpdated, @@ -160,13 +170,14 @@ public: bool enableP2P = config.enableP2P; - _manager.reset(new ThreadLocalObject(getManagerThread(), [encryptionKey = encryptionKey, enableP2P = enableP2P, isVideo, stateUpdated, videoStateUpdated, remoteVideoIsActiveUpdated, signalingDataEmitted, rtcServers](){ + _manager.reset(new ThreadLocalObject(getManagerThread(), [encryptionKey = encryptionKey, enableP2P = enableP2P, isVideo, stateUpdated, videoStateUpdated, remoteVideoIsActiveUpdated, signalingDataEmitted, rtcServers, videoCapture](){ return new Manager( getManagerThread(), encryptionKey, enableP2P, rtcServers, isVideo, + videoCapture, [stateUpdated](const TgVoipState &state) { stateUpdated(state); }, @@ -388,6 +399,7 @@ TgVoip *TgVoip::makeInstance( TgVoipNetworkType initialNetworkType, TgVoipEncryptionKey const &encryptionKey, bool isVideo, + std::shared_ptr videoCapture, std::function stateUpdated, std::function videoStateUpdated, std::function remoteVideoIsActiveUpdated, @@ -401,6 +413,7 @@ TgVoip *TgVoip::makeInstance( config, encryptionKey, isVideo, + videoCapture, initialNetworkType, stateUpdated, videoStateUpdated, @@ -411,6 +424,78 @@ TgVoip *TgVoip::makeInstance( TgVoip::~TgVoip() = default; +class TgVoipVideoCaptureInterfaceObject { +public: + TgVoipVideoCaptureInterfaceObject() { + _videoSource = makeVideoSource(Manager::getMediaThread(), MediaManager::getWorkerThread()); + //this should outlive the capturer + _videoCapturer = makeVideoCapturer(_videoSource, true, [this](bool isActive) { + if (this->_isActiveUpdated) { + this->_isActiveUpdated(isActive); + } + }); + } + + ~TgVoipVideoCaptureInterfaceObject() { + if (_currentSink != nullptr) { + _videoSource->RemoveSink(_currentSink.get()); + } + } + + void setVideoOutput(std::shared_ptr> sink) { + if (_currentSink != nullptr) { + _videoSource->RemoveSink(_currentSink.get()); + } + _currentSink = sink; + if (_currentSink != nullptr) { + _videoSource->AddOrUpdateSink(_currentSink.get(), rtc::VideoSinkWants()); + } + } + + void setIsActiveUpdated(std::function isActiveUpdated) { + _isActiveUpdated = isActiveUpdated; + } + +public: + rtc::scoped_refptr _videoSource; + std::unique_ptr _videoCapturer; + +private: + std::shared_ptr> _currentSink; + std::function _isActiveUpdated; +}; + +class TgVoipVideoCaptureInterfaceImpl : public TgVoipVideoCaptureInterface { +public: + TgVoipVideoCaptureInterfaceImpl() { + _impl.reset(new ThreadLocalObject( + Manager::getMediaThread(), + []() { + return new TgVoipVideoCaptureInterfaceObject(); + } + )); + } + + virtual ~TgVoipVideoCaptureInterfaceImpl() { + + } + + virtual void setVideoOutput(std::shared_ptr> sink) { + _impl->perform([sink](TgVoipVideoCaptureInterfaceObject *impl) { + impl->setVideoOutput(sink); + }); + } + +public: + std::unique_ptr> _impl; +}; + +std::shared_ptrTgVoipVideoCaptureInterface::makeInstance() { + return std::shared_ptr(new TgVoipVideoCaptureInterfaceImpl()); +} + +TgVoipVideoCaptureInterface::~TgVoipVideoCaptureInterface() = default; + #ifdef TGVOIP_NAMESPACE } #endif diff --git a/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.h b/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.h new file mode 100644 index 0000000000..e69de29bb2 diff --git a/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.mm b/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.mm new file mode 100644 index 0000000000..e69de29bb2 From c8c1c96f16a3977d2a6d1956a0aeb31768c6bb23 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 3 Jul 2020 00:49:34 +0400 Subject: [PATCH 2/4] Temp --- .../Sources/PresentationCall.swift | 27 ++++-- .../Sources/PresentationCallManager.swift | 50 +---------- .../Sources/CallSessionManager.swift | 5 +- .../Sources/OngoingCallContext.swift | 43 ++++------ submodules/TgVoipWebrtc/Impl/Manager.cpp | 17 +--- submodules/TgVoipWebrtc/Impl/Manager.h | 4 - submodules/TgVoipWebrtc/Impl/MediaManager.cpp | 45 ++++------ submodules/TgVoipWebrtc/Impl/MediaManager.h | 7 +- submodules/TgVoipWebrtc/Impl/TgVoip.h | 5 +- submodules/TgVoipWebrtc/Impl/TgVoip.mm | 86 +------------------ .../TgVoipWebrtc/Impl/ThreadLocalObject.h | 6 ++ .../Impl/VideoCaptureInterfaceImpl.h | 50 +++++++++++ .../Impl/VideoCaptureInterfaceImpl.mm | 76 ++++++++++++++++ .../TgVoip/OngoingCallThreadLocalContext.h | 14 ++- .../Sources/OngoingCallThreadLocalContext.mm | 71 +++++++++------ 15 files changed, 254 insertions(+), 252 deletions(-) diff --git a/submodules/TelegramCallsUI/Sources/PresentationCall.swift b/submodules/TelegramCallsUI/Sources/PresentationCall.swift index 6c4da73a80..4332312a57 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCall.swift @@ -233,7 +233,9 @@ public final class PresentationCallImpl: PresentationCall { private var droppedCall = false private var dropCallKitCallTimer: SwiftSignalKit.Timer? - init(account: Account, audioSession: ManagedAudioSession, callSessionManager: CallSessionManager, callKitIntegration: CallKitIntegration?, serializedData: String?, dataSaving: VoiceCallDataSaving, derivedState: VoipDerivedState, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), initialState: CallSession?, internalId: CallSessionInternalId, peerId: PeerId, isOutgoing: Bool, peer: Peer?, proxyServer: ProxyServerSettings?, auxiliaryServers: [CallAuxiliaryServer], currentNetworkType: NetworkType, updatedNetworkType: Signal) { + private var videoCapturer: OngoingCallVideoCapturer? + + init(account: Account, audioSession: ManagedAudioSession, callSessionManager: CallSessionManager, callKitIntegration: CallKitIntegration?, serializedData: String?, dataSaving: VoiceCallDataSaving, derivedState: VoipDerivedState, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), initialState: CallSession?, internalId: CallSessionInternalId, peerId: PeerId, isOutgoing: Bool, peer: Peer?, proxyServer: ProxyServerSettings?, auxiliaryServers: [CallAuxiliaryServer], currentNetworkType: NetworkType, updatedNetworkType: Signal, startWithVideo: Bool) { self.account = account self.audioSession = audioSession self.callSessionManager = callSessionManager @@ -259,6 +261,11 @@ public final class PresentationCallImpl: PresentationCall { self.isOutgoing = isOutgoing self.isVideo = initialState?.type == .video self.peer = peer + self.isVideo = startWithVideo + if self.isVideo { + self.videoCapturer = OngoingCallVideoCapturer() + self.statePromise.set(PresentationCallState(state: .waiting, videoState: .activeOutgoing, remoteVideoState: .inactive)) + } self.serializedData = serializedData self.dataSaving = dataSaving @@ -440,7 +447,11 @@ public final class PresentationCallImpl: PresentationCall { mappedRemoteVideoState = .active } } else { - mappedVideoState = .notAvailable + if self.isVideo { + mappedVideoState = .activeOutgoing + } else { + mappedVideoState = .notAvailable + } mappedRemoteVideoState = .inactive } @@ -523,7 +534,7 @@ public final class PresentationCallImpl: PresentationCall { if let _ = audioSessionControl, !wasActive || previousControl == nil { let logName = "\(id.id)_\(id.accessHash)" - let ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer, auxiliaryServers: auxiliaryServers, initialNetworkType: self.currentNetworkType, updatedNetworkType: self.updatedNetworkType, serializedData: self.serializedData, dataSaving: dataSaving, derivedState: self.derivedState, key: key, isOutgoing: sessionState.isOutgoing, isVideo: sessionState.type == .video, connections: connections, maxLayer: maxLayer, version: version, allowP2P: allowsP2P, audioSessionActive: self.audioSessionActive.get(), logName: logName) + let ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer, auxiliaryServers: auxiliaryServers, initialNetworkType: self.currentNetworkType, updatedNetworkType: self.updatedNetworkType, serializedData: self.serializedData, dataSaving: dataSaving, derivedState: self.derivedState, key: key, isOutgoing: sessionState.isOutgoing, video: self.videoCapturer, connections: connections, maxLayer: maxLayer, version: version, allowP2P: allowsP2P, audioSessionActive: self.audioSessionActive.get(), logName: logName) self.ongoingContext = ongoingContext self.debugInfoValue.set(ongoingContext.debugInfo()) @@ -718,10 +729,6 @@ public final class PresentationCallImpl: PresentationCall { self.ongoingContext?.setEnableVideo(value) } - public func switchVideoCamera() { - self.ongoingContext?.switchVideoCamera() - } - public func setCurrentAudioOutput(_ output: AudioSessionOutput) { guard self.currentAudioOutputValue != output else { return @@ -748,6 +755,10 @@ public final class PresentationCallImpl: PresentationCall { } public func makeOutgoingVideoView(completion: @escaping (UIView?) -> Void) { - self.ongoingContext?.makeOutgoingVideoView(completion: completion) + self.videoCapturer?.makeOutgoingVideoView(completion: completion) + } + + public func switchVideoCamera() { + self.videoCapturer?.switchCamera() } } diff --git a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift index d0a41bf7ff..c85864c2aa 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -278,52 +278,6 @@ public final class PresentationCallManagerImpl: PresentationCallManager { self.callSettingsDisposable?.dispose() } - public func injectRingingStateSynchronously(account: Account, ringingState: CallSessionRingingState, callSession: CallSession) { - if self.currentCall != nil { - return - } - - let semaphore = DispatchSemaphore(value: 0) - var data: (PreferencesView, AccountSharedDataView, Peer?)? - let _ = combineLatest( - account.postbox.preferencesView(keys: [PreferencesKeys.voipConfiguration, ApplicationSpecificPreferencesKeys.voipDerivedState, PreferencesKeys.appConfiguration]) - |> take(1), - accountManager.sharedData(keys: [SharedDataKeys.autodownloadSettings]) - |> take(1), - account.postbox.transaction { transaction -> Peer? in - return transaction.getPeer(ringingState.peerId) - } - ).start(next: { preferences, sharedData, peer in - data = (preferences, sharedData, peer) - semaphore.signal() - }) - semaphore.wait() - - if let (preferences, sharedData, maybePeer) = data, let peer = maybePeer { - let configuration = preferences.values[PreferencesKeys.voipConfiguration] as? VoipConfiguration ?? .defaultValue - let appConfiguration = preferences.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? AppConfiguration.defaultValue - let derivedState = preferences.values[ApplicationSpecificPreferencesKeys.voipDerivedState] as? VoipDerivedState ?? .default - let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings] as? AutodownloadSettings ?? .defaultSettings - - let enableCallKit = true - - let call = PresentationCallImpl(account: account, audioSession: self.audioSession, callSessionManager: account.callSessionManager, callKitIntegration: enableCallKit ? callKitIntegrationIfEnabled(self.callKitIntegration, settings: self.callSettings) : nil, serializedData: configuration.serializedData, dataSaving: effectiveDataSaving(for: self.callSettings, autodownloadSettings: autodownloadSettings), derivedState: derivedState, getDeviceAccessData: self.getDeviceAccessData, initialState: callSession, internalId: ringingState.id, peerId: ringingState.peerId, isOutgoing: false, peer: peer, proxyServer: self.proxyServer, auxiliaryServers: auxiliaryServers(appConfiguration: appConfiguration), currentNetworkType: .none, updatedNetworkType: account.networkType) - self.updateCurrentCall(call) - self.currentCallPromise.set(.single(call)) - self.hasActiveCallsPromise.set(true) - self.removeCurrentCallDisposable.set((call.canBeRemoved - |> deliverOnMainQueue).start(next: { [weak self, weak call] value in - if value, let strongSelf = self, let call = call { - if strongSelf.currentCall === call { - strongSelf.updateCurrentCall(nil) - strongSelf.currentCallPromise.set(.single(nil)) - strongSelf.hasActiveCallsPromise.set(false) - } - } - })) - } - } - private func ringingStatesUpdated(_ ringingStates: [(Account, Peer, CallSessionRingingState, Bool, NetworkType)], enableCallKit: Bool) { if let firstState = ringingStates.first { if self.currentCall == nil { @@ -338,7 +292,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings] as? AutodownloadSettings ?? .defaultSettings let appConfiguration = preferences.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? AppConfiguration.defaultValue - let call = PresentationCallImpl(account: firstState.0, audioSession: strongSelf.audioSession, callSessionManager: firstState.0.callSessionManager, callKitIntegration: enableCallKit ? callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings) : nil, serializedData: configuration.serializedData, dataSaving: effectiveDataSaving(for: strongSelf.callSettings, autodownloadSettings: autodownloadSettings), derivedState: derivedState, getDeviceAccessData: strongSelf.getDeviceAccessData, initialState: nil, internalId: firstState.2.id, peerId: firstState.2.peerId, isOutgoing: false, peer: firstState.1, proxyServer: strongSelf.proxyServer, auxiliaryServers: auxiliaryServers(appConfiguration: appConfiguration), currentNetworkType: firstState.4, updatedNetworkType: firstState.0.networkType) + let call = PresentationCallImpl(account: firstState.0, audioSession: strongSelf.audioSession, callSessionManager: firstState.0.callSessionManager, callKitIntegration: enableCallKit ? callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings) : nil, serializedData: configuration.serializedData, dataSaving: effectiveDataSaving(for: strongSelf.callSettings, autodownloadSettings: autodownloadSettings), derivedState: derivedState, getDeviceAccessData: strongSelf.getDeviceAccessData, initialState: nil, internalId: firstState.2.id, peerId: firstState.2.peerId, isOutgoing: false, peer: firstState.1, proxyServer: strongSelf.proxyServer, auxiliaryServers: auxiliaryServers(appConfiguration: appConfiguration), currentNetworkType: firstState.4, updatedNetworkType: firstState.0.networkType, startWithVideo: firstState.2.isVideo) strongSelf.updateCurrentCall(call) strongSelf.currentCallPromise.set(.single(call)) strongSelf.hasActiveCallsPromise.set(true) @@ -491,7 +445,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { let autodownloadSettings = sharedData.entries[SharedDataKeys.autodownloadSettings] as? AutodownloadSettings ?? .defaultSettings let appConfiguration = preferences.values[PreferencesKeys.appConfiguration] as? AppConfiguration ?? AppConfiguration.defaultValue - let call = PresentationCallImpl(account: account, audioSession: strongSelf.audioSession, callSessionManager: account.callSessionManager, callKitIntegration: callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings), serializedData: configuration.serializedData, dataSaving: effectiveDataSaving(for: strongSelf.callSettings, autodownloadSettings: autodownloadSettings), derivedState: derivedState, getDeviceAccessData: strongSelf.getDeviceAccessData, initialState: nil, internalId: internalId, peerId: peerId, isOutgoing: true, peer: nil, proxyServer: strongSelf.proxyServer, auxiliaryServers: auxiliaryServers(appConfiguration: appConfiguration), currentNetworkType: currentNetworkType, updatedNetworkType: account.networkType) + let call = PresentationCallImpl(account: account, audioSession: strongSelf.audioSession, callSessionManager: account.callSessionManager, callKitIntegration: callKitIntegrationIfEnabled(strongSelf.callKitIntegration, settings: strongSelf.callSettings), serializedData: configuration.serializedData, dataSaving: effectiveDataSaving(for: strongSelf.callSettings, autodownloadSettings: autodownloadSettings), derivedState: derivedState, getDeviceAccessData: strongSelf.getDeviceAccessData, initialState: nil, internalId: internalId, peerId: peerId, isOutgoing: true, peer: nil, proxyServer: strongSelf.proxyServer, auxiliaryServers: auxiliaryServers(appConfiguration: appConfiguration), currentNetworkType: currentNetworkType, updatedNetworkType: account.networkType, startWithVideo: isVideo) strongSelf.updateCurrentCall(call) strongSelf.currentCallPromise.set(.single(call)) strongSelf.hasActiveCallsPromise.set(true) diff --git a/submodules/TelegramCore/Sources/CallSessionManager.swift b/submodules/TelegramCore/Sources/CallSessionManager.swift index dae749103b..55c5657aec 100644 --- a/submodules/TelegramCore/Sources/CallSessionManager.swift +++ b/submodules/TelegramCore/Sources/CallSessionManager.swift @@ -107,9 +107,10 @@ typealias CallSessionStableId = Int64 public struct CallSessionRingingState: Equatable { public let id: CallSessionInternalId public let peerId: PeerId + public let isVideo: Bool public static func ==(lhs: CallSessionRingingState, rhs: CallSessionRingingState) -> Bool { - return lhs.id == rhs.id && lhs.peerId == rhs.peerId + return lhs.id == rhs.id && lhs.peerId == rhs.peerId && lhs.isVideo == rhs.isVideo } } @@ -365,7 +366,7 @@ private final class CallSessionManagerContext { var ringingContexts: [CallSessionRingingState] = [] for (id, context) in self.contexts { if case .ringing = context.state { - ringingContexts.append(CallSessionRingingState(id: id, peerId: context.peerId)) + ringingContexts.append(CallSessionRingingState(id: id, peerId: context.peerId, isVideo: context.type == .video)) } } return ringingContexts diff --git a/submodules/TelegramVoip/Sources/OngoingCallContext.swift b/submodules/TelegramVoip/Sources/OngoingCallContext.swift index 5f9834ebb7..e981f2db65 100644 --- a/submodules/TelegramVoip/Sources/OngoingCallContext.swift +++ b/submodules/TelegramVoip/Sources/OngoingCallContext.swift @@ -245,7 +245,6 @@ private protocol OngoingCallThreadLocalContextProtocol: class { func nativeSetNetworkType(_ type: NetworkType) func nativeSetIsMuted(_ value: Bool) func nativeSetVideoEnabled(_ value: Bool) - func nativeSwitchVideoCamera() func nativeStop(_ completion: @escaping (String?, Int64, Int64, Int64, Int64) -> Void) func nativeDebugInfo() -> String func nativeVersion() -> String @@ -292,6 +291,22 @@ extension OngoingCallThreadLocalContext: OngoingCallThreadLocalContextProtocol { } } +public final class OngoingCallVideoCapturer { + fileprivate let impl: OngoingCallThreadLocalContextVideoCapturer + + public init() { + self.impl = OngoingCallThreadLocalContextVideoCapturer() + } + + public func switchCamera() { + self.impl.switchVideoCamera() + } + + public func makeOutgoingVideoView(completion: @escaping (UIView?) -> Void) { + self.impl.makeOutgoingVideoView(completion) + } +} + extension OngoingCallThreadLocalContextWebrtc: OngoingCallThreadLocalContextProtocol { func nativeSetNetworkType(_ type: NetworkType) { self.setNetworkType(ongoingNetworkTypeForTypeWebrtc(type)) @@ -309,10 +324,6 @@ extension OngoingCallThreadLocalContextWebrtc: OngoingCallThreadLocalContextProt self.setVideoEnabled(value) } - func nativeSwitchVideoCamera() { - self.switchVideoCamera() - } - func nativeDebugInfo() -> String { return self.debugInfo() ?? "" } @@ -463,7 +474,7 @@ public final class OngoingCallContext { return result } - public init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, proxyServer: ProxyServerSettings?, auxiliaryServers: [AuxiliaryServer], initialNetworkType: NetworkType, updatedNetworkType: Signal, serializedData: String?, dataSaving: VoiceCallDataSaving, derivedState: VoipDerivedState, key: Data, isOutgoing: Bool, isVideo: Bool, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, allowP2P: Bool, audioSessionActive: Signal, logName: String) { + public init(account: Account, callSessionManager: CallSessionManager, internalId: CallSessionInternalId, proxyServer: ProxyServerSettings?, auxiliaryServers: [AuxiliaryServer], initialNetworkType: NetworkType, updatedNetworkType: Signal, serializedData: String?, dataSaving: VoiceCallDataSaving, derivedState: VoipDerivedState, key: Data, isOutgoing: Bool, video: OngoingCallVideoCapturer?, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, allowP2P: Bool, audioSessionActive: Signal, logName: String) { let _ = setupLogs OngoingCallThreadLocalContext.applyServerConfig(serializedData) //OngoingCallThreadLocalContextWebrtc.applyServerConfig(serializedData) @@ -542,9 +553,9 @@ public final class OngoingCallContext { )) } } - let context = OngoingCallThreadLocalContextWebrtc(queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, rtcServers: rtcServers, networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, isVideo: isVideo, primaryConnection: callConnectionDescriptionWebrtc(connections.primary), alternativeConnections: connections.alternatives.map(callConnectionDescriptionWebrtc), maxLayer: maxLayer, allowP2P: allowP2P, logPath: logPath, sendSignalingData: { [weak callSessionManager] data in + let context = OngoingCallThreadLocalContextWebrtc(queue: OngoingCallThreadLocalContextQueueImpl(queue: queue), proxy: voipProxyServer, rtcServers: rtcServers, networkType: ongoingNetworkTypeForTypeWebrtc(initialNetworkType), dataSaving: ongoingDataSavingForTypeWebrtc(dataSaving), derivedState: derivedState.data, key: key, isOutgoing: isOutgoing, primaryConnection: callConnectionDescriptionWebrtc(connections.primary), alternativeConnections: connections.alternatives.map(callConnectionDescriptionWebrtc), maxLayer: maxLayer, allowP2P: allowP2P, logPath: logPath, sendSignalingData: { [weak callSessionManager] data in callSessionManager?.sendSignalingData(internalId: internalId, data: data) - }) + }, videoCapturer: video?.impl) strongSelf.contextRef = Unmanaged.passRetained(OngoingCallThreadLocalContextHolder(context)) context.stateChanged = { state, videoState, remoteVideoState in @@ -696,12 +707,6 @@ public final class OngoingCallContext { } } - public func switchVideoCamera() { - self.withContext { context in - context.nativeSwitchVideoCamera() - } - } - public func debugInfo() -> Signal<(String, String), NoError> { let poll = Signal<(String, String), NoError> { subscriber in self.withContext { context in @@ -725,14 +730,4 @@ public final class OngoingCallContext { } } } - - public func makeOutgoingVideoView(completion: @escaping (UIView?) -> Void) { - self.withContext { context in - if let context = context as? OngoingCallThreadLocalContextWebrtc { - context.makeOutgoingVideoView(completion) - } else { - completion(nil) - } - } - } } diff --git a/submodules/TgVoipWebrtc/Impl/Manager.cpp b/submodules/TgVoipWebrtc/Impl/Manager.cpp index 3f43b28a99..1e7cbe0a5f 100644 --- a/submodules/TgVoipWebrtc/Impl/Manager.cpp +++ b/submodules/TgVoipWebrtc/Impl/Manager.cpp @@ -36,7 +36,6 @@ Manager::Manager( TgVoipEncryptionKey encryptionKey, bool enableP2P, std::vector const &rtcServers, - bool isVideo, std::shared_ptr videoCapture, std::function stateUpdated, std::function videoStateUpdated, @@ -47,7 +46,6 @@ _thread(thread), _encryptionKey(encryptionKey), _enableP2P(enableP2P), _rtcServers(rtcServers), -_startWithVideo(isVideo), _videoCapture(videoCapture), _stateUpdated(stateUpdated), _videoStateUpdated(videoStateUpdated), @@ -112,11 +110,10 @@ void Manager::start() { ); })); bool isOutgoing = _encryptionKey.isOutgoing; - _mediaManager.reset(new ThreadLocalObject(getMediaThread(), [isOutgoing, thread = _thread, startWithVideo = _startWithVideo, videoCapture = _videoCapture, weakThis]() { + _mediaManager.reset(new ThreadLocalObject(getMediaThread(), [isOutgoing, thread = _thread, videoCapture = _videoCapture, weakThis]() { return new MediaManager( getMediaThread(), isOutgoing, - startWithVideo, videoCapture, [thread, weakThis](const rtc::CopyOnWriteBuffer &packet) { thread->PostTask(RTC_FROM_HERE, [weakThis, packet]() { @@ -205,12 +202,6 @@ void Manager::setMuteOutgoingAudio(bool mute) { }); } -void Manager::switchVideoCamera() { - _mediaManager->perform([](MediaManager *mediaManager) { - mediaManager->switchVideoCamera(); - }); -} - void Manager::notifyIsLocalVideoActive(bool isActive) { rtc::CopyOnWriteBuffer buffer; uint8_t mode = 4; @@ -230,12 +221,6 @@ void Manager::setIncomingVideoOutput(std::shared_ptr> sink) { - _mediaManager->perform([sink](MediaManager *mediaManager) { - mediaManager->setOutgoingVideoOutput(sink); - }); -} - #ifdef TGVOIP_NAMESPACE } #endif diff --git a/submodules/TgVoipWebrtc/Impl/Manager.h b/submodules/TgVoipWebrtc/Impl/Manager.h index 7565cf73ea..ff113c4175 100644 --- a/submodules/TgVoipWebrtc/Impl/Manager.h +++ b/submodules/TgVoipWebrtc/Impl/Manager.h @@ -19,7 +19,6 @@ public: TgVoipEncryptionKey encryptionKey, bool enableP2P, std::vector const &rtcServers, - bool isVideo, std::shared_ptr videoCapture, std::function stateUpdated, std::function videoStateUpdated, @@ -32,17 +31,14 @@ public: void receiveSignalingData(const std::vector &data); void setSendVideo(bool sendVideo); void setMuteOutgoingAudio(bool mute); - void switchVideoCamera(); void notifyIsLocalVideoActive(bool isActive); void setIncomingVideoOutput(std::shared_ptr> sink); - void setOutgoingVideoOutput(std::shared_ptr> sink); private: rtc::Thread *_thread; TgVoipEncryptionKey _encryptionKey; bool _enableP2P; std::vector _rtcServers; - bool _startWithVideo; std::shared_ptr _videoCapture; std::function _stateUpdated; std::function _videoStateUpdated; diff --git a/submodules/TgVoipWebrtc/Impl/MediaManager.cpp b/submodules/TgVoipWebrtc/Impl/MediaManager.cpp index 65fe983313..5c91342c7f 100644 --- a/submodules/TgVoipWebrtc/Impl/MediaManager.cpp +++ b/submodules/TgVoipWebrtc/Impl/MediaManager.cpp @@ -19,6 +19,9 @@ #include "api/video_codecs/builtin_video_encoder_factory.h" +#include "TgVoip.h" +#include "VideoCaptureInterfaceImpl.h" + #if TARGET_OS_IPHONE #include "CodecsApple.h" @@ -164,7 +167,7 @@ static rtc::Thread *makeWorkerThread() { } -static rtc::Thread *MediaManager::getWorkerThread() { +rtc::Thread *MediaManager::getWorkerThread() { static rtc::Thread *value = makeWorkerThread(); return value; } @@ -172,7 +175,7 @@ static rtc::Thread *MediaManager::getWorkerThread() { MediaManager::MediaManager( rtc::Thread *thread, bool isOutgoing, - bool startWithVideo, + std::shared_ptr videoCapture, std::function packetEmitted, std::function localVideoCaptureActiveUpdated ) : @@ -180,7 +183,8 @@ _packetEmitted(packetEmitted), _localVideoCaptureActiveUpdated(localVideoCaptureActiveUpdated), _thread(thread), _eventLog(std::make_unique()), -_taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()) { +_taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()), +_videoCapture(videoCapture) { _ssrcAudio.incoming = isOutgoing ? ssrcAudioIncoming : ssrcAudioOutgoing; _ssrcAudio.outgoing = (!isOutgoing) ? ssrcAudioIncoming : ssrcAudioOutgoing; _ssrcAudio.fecIncoming = isOutgoing ? ssrcAudioFecIncoming : ssrcAudioFecOutgoing; @@ -199,7 +203,6 @@ _taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()) { _videoCodecs = AssignPayloadTypesAndDefaultCodecs(videoEncoderFactory->GetSupportedFormats()); _isSendingVideo = false; - _useFrontCamera = true; _audioNetworkInterface = std::unique_ptr(new MediaManager::NetworkInterfaceImpl(this, false)); _videoNetworkInterface = std::unique_ptr(new MediaManager::NetworkInterfaceImpl(this, true)); @@ -283,9 +286,9 @@ _taskQueueFactory(webrtc::CreateDefaultTaskQueueFactory()) { _videoChannel->SetInterface(_videoNetworkInterface.get(), webrtc::MediaTransportConfig()); - _nativeVideoSource = makeVideoSource(_thread, getWorkerThread()); - - if (startWithVideo) { + if (_videoCapture != nullptr) { + ((TgVoipVideoCaptureInterfaceImpl *)_videoCapture.get())->_impl->getSyncAssumingSameThread()->setIsActiveUpdated(this->_localVideoCaptureActiveUpdated); + setSendVideo(true); } } @@ -372,10 +375,6 @@ void MediaManager::setSendVideo(bool sendVideo) { codec.SetParam(cricket::kCodecParamStartBitrate, 512); codec.SetParam(cricket::kCodecParamMaxBitrate, 2500); - _videoCapturer = makeVideoCapturer(_nativeVideoSource, _useFrontCamera, [localVideoCaptureActiveUpdated = _localVideoCaptureActiveUpdated](bool isActive) { - localVideoCaptureActiveUpdated(isActive); - }); - cricket::VideoSendParameters videoSendParameters; videoSendParameters.codecs.push_back(codec); @@ -402,11 +401,15 @@ void MediaManager::setSendVideo(bool sendVideo) { videoSendStreamParams.cname = "cname"; _videoChannel->AddSendStream(videoSendStreamParams); - _videoChannel->SetVideoSend(_ssrcVideo.outgoing, NULL, _nativeVideoSource.get()); + if (_videoCapture != nullptr) { + _videoChannel->SetVideoSend(_ssrcVideo.outgoing, NULL, ((TgVoipVideoCaptureInterfaceImpl *)_videoCapture.get())->_impl->getSyncAssumingSameThread()->_videoSource.get()); + } _videoChannel->SetVideoSend(_ssrcVideo.fecOutgoing, NULL, nullptr); } else { _videoChannel->AddSendStream(cricket::StreamParams::CreateLegacy(_ssrcVideo.outgoing)); - _videoChannel->SetVideoSend(_ssrcVideo.outgoing, NULL, _nativeVideoSource.get()); + if (_videoCapture != nullptr) { + _videoChannel->SetVideoSend(_ssrcVideo.outgoing, NULL, ((TgVoipVideoCaptureInterfaceImpl *)_videoCapture.get())->_impl->getSyncAssumingSameThread()->_videoSource); + } } cricket::VideoRecvParameters videoRecvParameters; @@ -449,8 +452,6 @@ void MediaManager::setSendVideo(bool sendVideo) { _videoChannel->SetVideoSend(_ssrcVideo.outgoing, NULL, nullptr); _videoChannel->SetVideoSend(_ssrcVideo.fecOutgoing, NULL, nullptr); - _videoCapturer.reset(); - _videoChannel->RemoveRecvStream(_ssrcVideo.incoming); _videoChannel->RemoveRecvStream(_ssrcVideo.fecIncoming); _videoChannel->RemoveSendStream(_ssrcVideo.outgoing); @@ -466,25 +467,11 @@ void MediaManager::setMuteOutgoingAudio(bool mute) { _audioChannel->SetAudioSend(_ssrcAudio.outgoing, _isConnected && !_muteOutgoingAudio, nullptr, &_audioSource); } -void MediaManager::switchVideoCamera() { - if (_isSendingVideo) { - _useFrontCamera = !_useFrontCamera; - _videoCapturer = makeVideoCapturer(_nativeVideoSource, _useFrontCamera, [localVideoCaptureActiveUpdated = _localVideoCaptureActiveUpdated](bool isActive) { - localVideoCaptureActiveUpdated(isActive); - }); - } -} - void MediaManager::setIncomingVideoOutput(std::shared_ptr> sink) { _currentIncomingVideoSink = sink; _videoChannel->SetSink(_ssrcVideo.incoming, _currentIncomingVideoSink.get()); } -void MediaManager::setOutgoingVideoOutput(std::shared_ptr> sink) { - _currentOutgoingVideoSink = sink; - _nativeVideoSource->AddOrUpdateSink(_currentOutgoingVideoSink.get(), rtc::VideoSinkWants()); -} - MediaManager::NetworkInterfaceImpl::NetworkInterfaceImpl(MediaManager *mediaManager, bool isVideo) : _mediaManager(mediaManager), _isVideo(isVideo) { diff --git a/submodules/TgVoipWebrtc/Impl/MediaManager.h b/submodules/TgVoipWebrtc/Impl/MediaManager.h index 9ed8b7819a..a283d02283 100644 --- a/submodules/TgVoipWebrtc/Impl/MediaManager.h +++ b/submodules/TgVoipWebrtc/Impl/MediaManager.h @@ -7,6 +7,8 @@ #include "api/transport/field_trial_based_config.h" #include "pc/rtp_sender.h" +#include "TgVoip.h" + #include #include @@ -59,7 +61,6 @@ public: MediaManager( rtc::Thread *thread, bool isOutgoing, - bool startWithVideo, std::shared_ptr videoCapture, std::function packetEmitted, std::function localVideoCaptureActiveUpdated @@ -71,9 +72,7 @@ public: void notifyPacketSent(const rtc::SentPacket &sentPacket); void setSendVideo(bool sendVideo); void setMuteOutgoingAudio(bool mute); - void switchVideoCamera(); void setIncomingVideoOutput(std::shared_ptr> sink); - void setOutgoingVideoOutput(std::shared_ptr> sink); protected: std::function _packetEmitted; @@ -93,7 +92,6 @@ private: std::vector _videoCodecs; bool _isSendingVideo; - bool _useFrontCamera; std::unique_ptr _mediaEngine; std::unique_ptr _call; @@ -104,7 +102,6 @@ private: std::unique_ptr _videoBitrateAllocatorFactory; std::shared_ptr _videoCapture; std::shared_ptr> _currentIncomingVideoSink; - std::shared_ptr> _currentOutgoingVideoSink; std::unique_ptr _audioNetworkInterface; std::unique_ptr _videoNetworkInterface; diff --git a/submodules/TgVoipWebrtc/Impl/TgVoip.h b/submodules/TgVoipWebrtc/Impl/TgVoip.h index 463b71caef..8f21482708 100644 --- a/submodules/TgVoipWebrtc/Impl/TgVoip.h +++ b/submodules/TgVoipWebrtc/Impl/TgVoip.h @@ -136,6 +136,8 @@ public: static std::shared_ptr makeInstance(); virtual ~TgVoipVideoCaptureInterface(); + + virtual void switchCamera() = 0; virtual void setVideoOutput(std::shared_ptr> sink) = 0; }; @@ -156,7 +158,6 @@ public: std::vector const &rtcServers, TgVoipNetworkType initialNetworkType, TgVoipEncryptionKey const &encryptionKey, - bool isVideo, std::shared_ptr videoCapture, std::function stateUpdated, std::function videoStateUpdated, @@ -172,7 +173,6 @@ public: virtual void setEchoCancellationStrength(int strength) = 0; virtual void setIncomingVideoOutput(std::shared_ptr> sink) = 0; - virtual void setOutgoingVideoOutput(std::shared_ptr> sink) = 0; virtual std::string getLastError() = 0; virtual std::string getDebugInfo() = 0; @@ -182,7 +182,6 @@ public: virtual void receiveSignalingData(const std::vector &data) = 0; virtual void setSendVideo(bool sendVideo) = 0; - virtual void switchVideoCamera() = 0; virtual TgVoipFinalState stop() = 0; }; diff --git a/submodules/TgVoipWebrtc/Impl/TgVoip.mm b/submodules/TgVoipWebrtc/Impl/TgVoip.mm index 22f5b8d94f..5cfb5d75a4 100644 --- a/submodules/TgVoipWebrtc/Impl/TgVoip.mm +++ b/submodules/TgVoipWebrtc/Impl/TgVoip.mm @@ -10,6 +10,8 @@ #include #include +#include "VideoCaptureInterfaceImpl.h" + #if TARGET_OS_IPHONE #include "CodecsApple.h" @@ -151,7 +153,6 @@ public: std::vector const &rtcServers, TgVoipConfig const &config, TgVoipEncryptionKey const &encryptionKey, - bool isVideo, std::shared_ptr videoCapture, TgVoipNetworkType initialNetworkType, std::function stateUpdated, @@ -170,13 +171,12 @@ public: bool enableP2P = config.enableP2P; - _manager.reset(new ThreadLocalObject(getManagerThread(), [encryptionKey = encryptionKey, enableP2P = enableP2P, isVideo, stateUpdated, videoStateUpdated, remoteVideoIsActiveUpdated, signalingDataEmitted, rtcServers, videoCapture](){ + _manager.reset(new ThreadLocalObject(getManagerThread(), [encryptionKey = encryptionKey, enableP2P = enableP2P, stateUpdated, videoStateUpdated, remoteVideoIsActiveUpdated, signalingDataEmitted, rtcServers, videoCapture](){ return new Manager( getManagerThread(), encryptionKey, enableP2P, rtcServers, - isVideo, videoCapture, [stateUpdated](const TgVoipState &state) { stateUpdated(state); @@ -212,12 +212,6 @@ public: manager->setSendVideo(sendVideo); }); }; - - void switchVideoCamera() override { - _manager->perform([](Manager *manager) { - manager->switchVideoCamera(); - }); - } void setNetworkType(TgVoipNetworkType networkType) override { /*message::NetworkType mappedType; @@ -278,12 +272,6 @@ public: manager->setIncomingVideoOutput(sink); }); } - - void setOutgoingVideoOutput(std::shared_ptr> sink) override { - _manager->perform([sink](Manager *manager) { - manager->setOutgoingVideoOutput(sink); - }); - } void setAudioOutputGainControlEnabled(bool enabled) override { } @@ -398,7 +386,6 @@ TgVoip *TgVoip::makeInstance( std::vector const &rtcServers, TgVoipNetworkType initialNetworkType, TgVoipEncryptionKey const &encryptionKey, - bool isVideo, std::shared_ptr videoCapture, std::function stateUpdated, std::function videoStateUpdated, @@ -412,7 +399,6 @@ TgVoip *TgVoip::makeInstance( rtcServers, config, encryptionKey, - isVideo, videoCapture, initialNetworkType, stateUpdated, @@ -424,72 +410,6 @@ TgVoip *TgVoip::makeInstance( TgVoip::~TgVoip() = default; -class TgVoipVideoCaptureInterfaceObject { -public: - TgVoipVideoCaptureInterfaceObject() { - _videoSource = makeVideoSource(Manager::getMediaThread(), MediaManager::getWorkerThread()); - //this should outlive the capturer - _videoCapturer = makeVideoCapturer(_videoSource, true, [this](bool isActive) { - if (this->_isActiveUpdated) { - this->_isActiveUpdated(isActive); - } - }); - } - - ~TgVoipVideoCaptureInterfaceObject() { - if (_currentSink != nullptr) { - _videoSource->RemoveSink(_currentSink.get()); - } - } - - void setVideoOutput(std::shared_ptr> sink) { - if (_currentSink != nullptr) { - _videoSource->RemoveSink(_currentSink.get()); - } - _currentSink = sink; - if (_currentSink != nullptr) { - _videoSource->AddOrUpdateSink(_currentSink.get(), rtc::VideoSinkWants()); - } - } - - void setIsActiveUpdated(std::function isActiveUpdated) { - _isActiveUpdated = isActiveUpdated; - } - -public: - rtc::scoped_refptr _videoSource; - std::unique_ptr _videoCapturer; - -private: - std::shared_ptr> _currentSink; - std::function _isActiveUpdated; -}; - -class TgVoipVideoCaptureInterfaceImpl : public TgVoipVideoCaptureInterface { -public: - TgVoipVideoCaptureInterfaceImpl() { - _impl.reset(new ThreadLocalObject( - Manager::getMediaThread(), - []() { - return new TgVoipVideoCaptureInterfaceObject(); - } - )); - } - - virtual ~TgVoipVideoCaptureInterfaceImpl() { - - } - - virtual void setVideoOutput(std::shared_ptr> sink) { - _impl->perform([sink](TgVoipVideoCaptureInterfaceObject *impl) { - impl->setVideoOutput(sink); - }); - } - -public: - std::unique_ptr> _impl; -}; - std::shared_ptrTgVoipVideoCaptureInterface::makeInstance() { return std::shared_ptr(new TgVoipVideoCaptureInterfaceImpl()); } diff --git a/submodules/TgVoipWebrtc/Impl/ThreadLocalObject.h b/submodules/TgVoipWebrtc/Impl/ThreadLocalObject.h index fbaee62e2d..ce5007a21c 100644 --- a/submodules/TgVoipWebrtc/Impl/ThreadLocalObject.h +++ b/submodules/TgVoipWebrtc/Impl/ThreadLocalObject.h @@ -43,6 +43,12 @@ public: }); } + T *getSyncAssumingSameThread() { + assert(_thread->IsCurrent()); + assert(_valueHolder->_value != nullptr); + return _valueHolder->_value.get(); + } + private: rtc::Thread *_thread; std::shared_ptr> _valueHolder; diff --git a/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.h b/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.h index e69de29bb2..199de612a8 100644 --- a/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.h +++ b/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.h @@ -0,0 +1,50 @@ +#ifndef VIDEO_CAPTURE_INTERFACE_IMPL_H +#define VIDEO_CAPTURE_INTERFACE_IMPL_H + +#include "TgVoip.h" +#include +#include "ThreadLocalObject.h" +#include "api/media_stream_interface.h" + +#ifdef TGVOIP_NAMESPACE +namespace TGVOIP_NAMESPACE { +#endif + +class VideoCapturerInterface; + +class TgVoipVideoCaptureInterfaceObject { +public: + TgVoipVideoCaptureInterfaceObject(); + ~TgVoipVideoCaptureInterfaceObject(); + + void switchCamera(); + void setVideoOutput(std::shared_ptr> sink); + void setIsActiveUpdated(std::function isActiveUpdated); + +public: + rtc::scoped_refptr _videoSource; + std::unique_ptr _videoCapturer; + +private: + std::shared_ptr> _currentSink; + std::function _isActiveUpdated; + bool _useFrontCamera; +}; + +class TgVoipVideoCaptureInterfaceImpl : public TgVoipVideoCaptureInterface { +public: + TgVoipVideoCaptureInterfaceImpl(); + virtual ~TgVoipVideoCaptureInterfaceImpl(); + + virtual void switchCamera(); + virtual void setVideoOutput(std::shared_ptr> sink); + +public: + std::unique_ptr> _impl; +}; + +#ifdef TGVOIP_NAMESPACE +} +#endif + +#endif diff --git a/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.mm b/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.mm index e69de29bb2..4e4b269175 100644 --- a/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.mm +++ b/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.mm @@ -0,0 +1,76 @@ +#include "VideoCaptureInterfaceImpl.h" + +#include "CodecsApple.h" +#include "Manager.h" +#include "MediaManager.h" + +#ifdef TGVOIP_NAMESPACE +namespace TGVOIP_NAMESPACE { +#endif + +TgVoipVideoCaptureInterfaceObject::TgVoipVideoCaptureInterfaceObject() { + _useFrontCamera = true; + _videoSource = makeVideoSource(Manager::getMediaThread(), MediaManager::getWorkerThread()); + //this should outlive the capturer + _videoCapturer = makeVideoCapturer(_videoSource, _useFrontCamera, [this](bool isActive) { + if (this->_isActiveUpdated) { + this->_isActiveUpdated(isActive); + } + }); +} + +TgVoipVideoCaptureInterfaceObject::~TgVoipVideoCaptureInterfaceObject() { + if (_currentSink != nullptr) { + _videoSource->RemoveSink(_currentSink.get()); + } +} + +void TgVoipVideoCaptureInterfaceObject::switchCamera() { + _useFrontCamera = !_useFrontCamera; + _videoCapturer = makeVideoCapturer(_videoSource, _useFrontCamera, [this](bool isActive) { + if (this->_isActiveUpdated) { + this->_isActiveUpdated(isActive); + } + }); +} + +void TgVoipVideoCaptureInterfaceObject::setVideoOutput(std::shared_ptr> sink) { + if (_currentSink != nullptr) { + _videoSource->RemoveSink(_currentSink.get()); + } + _currentSink = sink; + if (_currentSink != nullptr) { + _videoSource->AddOrUpdateSink(_currentSink.get(), rtc::VideoSinkWants()); + } +} + +void TgVoipVideoCaptureInterfaceObject::setIsActiveUpdated(std::function isActiveUpdated) { + _isActiveUpdated = isActiveUpdated; +} + +TgVoipVideoCaptureInterfaceImpl::TgVoipVideoCaptureInterfaceImpl() { + _impl.reset(new ThreadLocalObject( + Manager::getMediaThread(), + []() { + return new TgVoipVideoCaptureInterfaceObject(); + } + )); +} + +TgVoipVideoCaptureInterfaceImpl::~TgVoipVideoCaptureInterfaceImpl() { + +} + +void TgVoipVideoCaptureInterfaceImpl::switchCamera() { + _impl->perform([](TgVoipVideoCaptureInterfaceObject *impl) { + impl->switchCamera(); + }); +} + +void TgVoipVideoCaptureInterfaceImpl::setVideoOutput(std::shared_ptr> sink) { + _impl->perform([sink](TgVoipVideoCaptureInterfaceObject *impl) { + impl->setVideoOutput(sink); + }); +} + +} diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h index 1582e4351a..edfee3acb4 100644 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h @@ -78,6 +78,16 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) { @end +@interface OngoingCallThreadLocalContextVideoCapturer : NSObject + +- (instancetype _Nonnull)init; + +- (void)switchVideoCamera; + +- (void)makeOutgoingVideoView:(void (^_Nonnull)(UIView * _Nullable))completion; + +@end + @interface OngoingCallThreadLocalContextWebrtc : NSObject + (void)setupLoggingFunction:(void (* _Nullable)(NSString * _Nullable))loggingFunction; @@ -88,7 +98,7 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) { @property (nonatomic, copy) void (^ _Nullable stateChanged)(OngoingCallStateWebrtc, OngoingCallVideoStateWebrtc, OngoingCallRemoteVideoStateWebrtc); @property (nonatomic, copy) void (^ _Nullable signalBarsChanged)(int32_t); -- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy rtcServers:(NSArray * _Nonnull)rtcServers networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing isVideo:(bool)isVideo primaryConnection:(OngoingCallConnectionDescriptionWebrtc * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^)(NSData * _Nonnull))sendSignalingData; +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy rtcServers:(NSArray * _Nonnull)rtcServers networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescriptionWebrtc * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^ _Nonnull)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer; - (void)stop:(void (^_Nullable)(NSString * _Nullable debugLog, int64_t bytesSentWifi, int64_t bytesReceivedWifi, int64_t bytesSentMobile, int64_t bytesReceivedMobile))completion; - (bool)needRate; @@ -99,10 +109,8 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) { - (void)setIsMuted:(bool)isMuted; - (void)setVideoEnabled:(bool)videoEnabled; -- (void)switchVideoCamera; - (void)setNetworkType:(OngoingCallNetworkTypeWebrtc)networkType; - (void)makeIncomingVideoView:(void (^_Nonnull)(UIView * _Nullable))completion; -- (void)makeOutgoingVideoView:(void (^_Nonnull)(UIView * _Nullable))completion; - (void)addSignalingData:(NSData * _Nonnull)data; @end diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index a10e9950be..19e78dcb29 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -21,6 +21,45 @@ using namespace TGVOIP_NAMESPACE; @end +@interface OngoingCallThreadLocalContextVideoCapturer () { + std::shared_ptr _interface; +} + +@end + +@implementation OngoingCallThreadLocalContextVideoCapturer + +- (instancetype _Nonnull)init { + self = [super init]; + if (self != nil) { + _interface = TgVoipVideoCaptureInterface::makeInstance(); + } + return self; +} + +- (void)switchVideoCamera { + _interface->switchCamera(); +} + +- (std::shared_ptr)getInterface { + return _interface; +} + +- (void)makeOutgoingVideoView:(void (^_Nonnull)(UIView * _Nullable))completion { + std::shared_ptr interface = _interface; + dispatch_async(dispatch_get_main_queue(), ^{ + VideoMetalView *remoteRenderer = [[VideoMetalView alloc] initWithFrame:CGRectZero]; + remoteRenderer.videoContentMode = UIViewContentModeScaleAspectFill; + + std::shared_ptr> sink = [remoteRenderer getSink]; + interface->setVideoOutput(sink); + + completion(remoteRenderer); + }); +} + +@end + @interface OngoingCallThreadLocalContextWebrtc () { id _queue; int32_t _contextId; @@ -36,6 +75,7 @@ using namespace TGVOIP_NAMESPACE; OngoingCallStateWebrtc _state; OngoingCallVideoStateWebrtc _videoState; OngoingCallRemoteVideoStateWebrtc _remoteVideoState; + OngoingCallThreadLocalContextVideoCapturer *_videoCapturer; int32_t _signalBars; NSData *_lastDerivedState; @@ -134,7 +174,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; return @"2.7.7"; } -- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy rtcServers:(NSArray * _Nonnull)rtcServers networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing isVideo:(bool)isVideo primaryConnection:(OngoingCallConnectionDescriptionWebrtc * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^)(NSData * _Nonnull))sendSignalingData; { +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue proxy:(VoipProxyServerWebrtc * _Nullable)proxy rtcServers:(NSArray * _Nonnull)rtcServers networkType:(OngoingCallNetworkTypeWebrtc)networkType dataSaving:(OngoingCallDataSavingWebrtc)dataSaving derivedState:(NSData * _Nonnull)derivedState key:(NSData * _Nonnull)key isOutgoing:(bool)isOutgoing primaryConnection:(OngoingCallConnectionDescriptionWebrtc * _Nonnull)primaryConnection alternativeConnections:(NSArray * _Nonnull)alternativeConnections maxLayer:(int32_t)maxLayer allowP2P:(BOOL)allowP2P logPath:(NSString * _Nonnull)logPath sendSignalingData:(void (^)(NSData * _Nonnull))sendSignalingData videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer { self = [super init]; if (self != nil) { _queue = queue; @@ -146,7 +186,8 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; _callPacketTimeout = 10.0; _networkType = networkType; _sendSignalingData = [sendSignalingData copy]; - if (isVideo) { + _videoCapturer = videoCapturer; + if (videoCapturer != nil) { _videoState = OngoingCallVideoStateActiveOutgoing; _remoteVideoState = OngoingCallRemoteVideoStateActive; } else { @@ -236,7 +277,7 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; parsedRtcServers, callControllerNetworkTypeForType(networkType), encryptionKey, - isVideo, + [_videoCapturer getInterface], [weakSelf, queue](TgVoipState state) { [queue dispatch:^{ __strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf; @@ -424,12 +465,6 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; } } -- (void)switchVideoCamera { - if (_tgVoip) { - _tgVoip->switchVideoCamera(); - } -} - - (void)setNetworkType:(OngoingCallNetworkTypeWebrtc)networkType { if (_networkType != networkType) { _networkType = networkType; @@ -457,23 +492,5 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; } } -- (void)makeOutgoingVideoView:(void (^_Nonnull)(UIView * _Nullable))completion { - if (_tgVoip) { - __weak OngoingCallThreadLocalContextWebrtc *weakSelf = self; - dispatch_async(dispatch_get_main_queue(), ^{ - VideoMetalView *remoteRenderer = [[VideoMetalView alloc] initWithFrame:CGRectZero]; - remoteRenderer.videoContentMode = UIViewContentModeScaleAspectFill; - - std::shared_ptr> sink = [remoteRenderer getSink]; - __strong OngoingCallThreadLocalContextWebrtc *strongSelf = weakSelf; - if (strongSelf) { - strongSelf->_tgVoip->setOutgoingVideoOutput(sink); - } - - completion(remoteRenderer); - }); - } -} - @end From b542dc3fd7b381de80e554286a94ccd5b8d02154 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 3 Jul 2020 21:25:36 +0400 Subject: [PATCH 3/4] Video improvements --- .../Sources/PresentationCallManager.swift | 1 + .../Display/Source/CAAnimationUtils.swift | 10 +- .../ContainedViewLayoutTransition.swift | 4 +- .../Sources/CallController.swift | 4 +- .../Sources/CallControllerButton.swift | 395 ++++++------ .../Sources/CallControllerButtonsNode.swift | 477 +++++++++----- .../Sources/CallControllerNode.swift | 595 +++++++++++++----- .../Sources/PresentationCall.swift | 15 +- .../CallAcceptButton.imageset/Contents.json | 12 + .../ic_calls_accept.pdf | Bin 0 -> 4239 bytes .../CallCameraButton.imageset/Contents.json | 12 + .../ic_calls_video.pdf | Bin 0 -> 4339 bytes .../CallDeclineButton.imageset/Contents.json | 12 + .../ic_calls_decline.pdf | Bin 0 -> 4291 bytes .../CallMuteIcon@2x.png | Bin 1108 -> 0 bytes .../CallMuteIcon@3x.png | Bin 1780 -> 0 bytes .../CallMuteButton.imageset/Contents.json | 20 +- .../CallMuteButton.imageset/ic_calls_mute.pdf | Bin 0 -> 4491 bytes .../CallPhoneIcon@2x.png | Bin 545 -> 0 bytes .../CallPhoneIcon@3x.png | Bin 844 -> 0 bytes .../CallPhoneButton.imageset/Contents.json | 22 - .../CallRouteSpeaker@2x.png | Bin 655 -> 0 bytes .../CallRouteSpeaker@3x.png | Bin 1244 -> 0 bytes .../CallRouteSpeaker.imageset/Contents.json | 20 +- .../ic_calls_speaker.pdf | Bin 0 -> 4759 bytes .../CallSpeakerIcon@2x.png | Bin 1197 -> 0 bytes .../CallSpeakerIcon@3x.png | Bin 1916 -> 0 bytes .../CallSpeakerButton.imageset/Contents.json | 20 +- .../ic_calls_speaker.pdf | Bin 0 -> 4759 bytes .../Contents.json | 2 +- .../CallSwitchCameraButton.imageset/Video.pdf | Bin 174248 -> 0 bytes .../ic_calls_cameraflip.pdf | Bin 0 -> 4333 bytes .../Call/CallTitleLogo.imageset/Contents.json | 12 + .../CallTitleLogo.imageset/ic_calls_tlogo.pdf | Bin 0 -> 4186 bytes .../Sources/OngoingCallContext.swift | 4 + submodules/TgVoipWebrtc/Impl/CodecsApple.h | 2 + submodules/TgVoipWebrtc/Impl/CodecsApple.mm | 14 + submodules/TgVoipWebrtc/Impl/TgVoip.h | 1 + .../TgVoipWebrtc/Impl/VideoCameraCapturer.h | 1 + .../TgVoipWebrtc/Impl/VideoCameraCapturer.mm | 33 +- .../Impl/VideoCaptureInterfaceImpl.h | 3 + .../Impl/VideoCaptureInterfaceImpl.mm | 14 + .../TgVoip/OngoingCallThreadLocalContext.h | 1 + .../Sources/OngoingCallThreadLocalContext.mm | 4 + 44 files changed, 1097 insertions(+), 613 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallAcceptButton.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallAcceptButton.imageset/ic_calls_accept.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallCameraButton.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallCameraButton.imageset/ic_calls_video.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallDeclineButton.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallDeclineButton.imageset/ic_calls_decline.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallMuteButton.imageset/CallMuteIcon@2x.png delete mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallMuteButton.imageset/CallMuteIcon@3x.png create mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallMuteButton.imageset/ic_calls_mute.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallPhoneButton.imageset/CallPhoneIcon@2x.png delete mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallPhoneButton.imageset/CallPhoneIcon@3x.png delete mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallPhoneButton.imageset/Contents.json delete mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallRouteSpeaker.imageset/CallRouteSpeaker@2x.png delete mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallRouteSpeaker.imageset/CallRouteSpeaker@3x.png create mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallRouteSpeaker.imageset/ic_calls_speaker.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallSpeakerButton.imageset/CallSpeakerIcon@2x.png delete mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallSpeakerButton.imageset/CallSpeakerIcon@3x.png create mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallSpeakerButton.imageset/ic_calls_speaker.pdf delete mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallSwitchCameraButton.imageset/Video.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallSwitchCameraButton.imageset/ic_calls_cameraflip.pdf create mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallTitleLogo.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Call/CallTitleLogo.imageset/ic_calls_tlogo.pdf diff --git a/submodules/AccountContext/Sources/PresentationCallManager.swift b/submodules/AccountContext/Sources/PresentationCallManager.swift index aedbdc6b8b..e1e1f40397 100644 --- a/submodules/AccountContext/Sources/PresentationCallManager.swift +++ b/submodules/AccountContext/Sources/PresentationCallManager.swift @@ -90,6 +90,7 @@ public protocol PresentationCall: class { func toggleIsMuted() func setIsMuted(_ value: Bool) func setEnableVideo(_ value: Bool) + func setOutgoingVideoIsPaused(_ isPaused: Bool) func switchVideoCamera() func setCurrentAudioOutput(_ output: AudioSessionOutput) func debugInfo() -> Signal<(String, String), NoError> diff --git a/submodules/Display/Source/CAAnimationUtils.swift b/submodules/Display/Source/CAAnimationUtils.swift index 6eb000add6..c683fdf4f8 100644 --- a/submodules/Display/Source/CAAnimationUtils.swift +++ b/submodules/Display/Source/CAAnimationUtils.swift @@ -238,14 +238,14 @@ public extension CALayer { self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "position", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) } - func animateBounds(from: CGRect, to: CGRect, duration: Double, timingFunction: String, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + func animateBounds(from: CGRect, to: CGRect, duration: Double, delay: Double = 0.0, timingFunction: String, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { if from == to && !force { if let completion = completion { completion(true) } return } - self.animate(from: NSValue(cgRect: from), to: NSValue(cgRect: to), keyPath: "bounds", timingFunction: timingFunction, duration: duration, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) + self.animate(from: NSValue(cgRect: from), to: NSValue(cgRect: to), keyPath: "bounds", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) } func animateBoundsOriginXAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { @@ -268,7 +268,7 @@ public extension CALayer { self.animateKeyframes(values: values.map { NSValue(cgPoint: $0) }, duration: duration, keyPath: "position") } - func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + func animateFrame(from: CGRect, to: CGRect, duration: Double, delay: Double = 0.0, timingFunction: String, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { if from == to && !force { if let completion = completion { completion(true) @@ -302,14 +302,14 @@ public extension CALayer { toBounds = CGRect() } - self.animatePosition(from: fromPosition, to: toPosition, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in + self.animatePosition(from: fromPosition, to: toPosition, duration: duration, delay: delay, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in if !value { interrupted = true } completedPosition = true partialCompletion() }) - self.animateBounds(from: fromBounds, to: toBounds, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in + self.animateBounds(from: fromBounds, to: toBounds, duration: duration, delay: delay, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in if !value { interrupted = true } diff --git a/submodules/Display/Source/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift index 0ce89df155..1d4c1867a6 100644 --- a/submodules/Display/Source/ContainedViewLayoutTransition.swift +++ b/submodules/Display/Source/ContainedViewLayoutTransition.swift @@ -63,7 +63,7 @@ public enum ContainedViewLayoutTransition { } public extension ContainedViewLayoutTransition { - func updateFrame(node: ASDisplayNode, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = false, completion: ((Bool) -> Void)? = nil) { + func updateFrame(node: ASDisplayNode, frame: CGRect, force: Bool = false, beginWithCurrentState: Bool = false, delay: Double = 0.0, completion: ((Bool) -> Void)? = nil) { if node.frame.equalTo(frame) && !force { completion?(true) } else { @@ -81,7 +81,7 @@ public extension ContainedViewLayoutTransition { previousFrame = node.frame } node.frame = frame - node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in + node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, delay: delay, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in if let completion = completion { completion(result) } diff --git a/submodules/TelegramCallsUI/Sources/CallController.swift b/submodules/TelegramCallsUI/Sources/CallController.swift index 02db8c1132..52089bd548 100644 --- a/submodules/TelegramCallsUI/Sources/CallController.swift +++ b/submodules/TelegramCallsUI/Sources/CallController.swift @@ -178,8 +178,8 @@ public final class CallController: ViewController { let _ = self?.call.hangUp() } - self.controllerNode.toggleVideo = { [weak self] in - let _ = self?.call.setEnableVideo(true) + self.controllerNode.setIsVideoPaused = { [weak self] isPaused in + self?.call.setOutgoingVideoIsPaused(isPaused) } self.controllerNode.back = { [weak self] in diff --git a/submodules/TelegramCallsUI/Sources/CallControllerButton.swift b/submodules/TelegramCallsUI/Sources/CallControllerButton.swift index 7a3c6b9bc1..bc7c296f6b 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerButton.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerButton.swift @@ -5,245 +5,218 @@ import AsyncDisplayKit import SwiftSignalKit import AppBundle -enum CallControllerButtonType { - case mute - case end - case accept - case speaker - case bluetooth - case switchCamera -} +private let labelFont = Font.regular(13.0) -private let buttonSize = CGSize(width: 75.0, height: 75.0) - -private func generateEmptyButtonImage(icon: UIImage?, strokeColor: UIColor?, fillColor: UIColor, knockout: Bool = false, angle: CGFloat = 0.0) -> UIImage? { - return generateImage(buttonSize, contextGenerator: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setBlendMode(.copy) - if let strokeColor = strokeColor { - context.setFillColor(strokeColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) - context.setFillColor(fillColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(x: 1.5, y: 1.5), size: CGSize(width: size.width - 3.0, height: size.height - 3.0))) - } else { - context.setFillColor(fillColor.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) - } - - if let icon = icon { - if !angle.isZero { - context.translateBy(x: size.width / 2.0, y: size.height / 2.0) - context.rotate(by: angle) - context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) - } - let imageSize = icon.size - let imageRect = CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.width - imageSize.height) / 2.0)), size: imageSize) - if knockout { - context.setBlendMode(.copy) - context.clip(to: imageRect, mask: icon.cgImage!) - context.setFillColor(UIColor.clear.cgColor) - context.fill(imageRect) - } else { - context.setBlendMode(.normal) - context.draw(icon.cgImage!, in: imageRect) +final class CallControllerButtonItemNode: HighlightTrackingButtonNode { + struct Content: Equatable { + enum Appearance: Equatable { + enum Color { + case red + case green } + + case blurred(isFilled: Bool) + case color(Color) } - }) -} - -private func generateFilledButtonImage(color: UIColor, icon: UIImage?, angle: CGFloat = 0.0) -> UIImage? { - return generateImage(buttonSize, contextGenerator: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setBlendMode(.normal) - context.setFillColor(color.cgColor) - context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) - if let icon = icon { - if !angle.isZero { - context.translateBy(x: size.width / 2.0, y: size.height / 2.0) - context.rotate(by: angle) - context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) - } - context.draw(icon.cgImage!, in: CGRect(origin: CGPoint(x: floor((size.width - icon.size.width) / 2.0), y: floor((size.height - icon.size.height) / 2.0)), size: icon.size)) + enum Image { + case camera + case mute + case flipCamera + case bluetooth + case speaker + case accept + case end } - }) -} - -private let emptyStroke = UIColor(white: 1.0, alpha: 0.8) -private let emptyHighlightedFill = UIColor(white: 1.0, alpha: 0.3) -private let invertedFill = UIColor(white: 1.0, alpha: 1.0) - -private let labelFont = Font.regular(14.5) - -final class CallControllerButtonNode: HighlightTrackingButtonNode { - private var type: CallControllerButtonType + + var appearance: Appearance + var image: Image + } - private var regularImage: UIImage? - private var highlightedImage: UIImage? - private var filledImage: UIImage? + private let contentContainer: ASDisplayNode + private let effectView: UIVisualEffectView + private let contentNode: ASImageNode + private let overlayHighlightNode: ASImageNode + private let textNode: ImmediateTextNode - private let backgroundNode: ASImageNode - private let labelNode: ASTextNode? + private let largeButtonSize: CGFloat = 72.0 - init(type: CallControllerButtonType, label: String?) { - self.type = type + private(set) var currentContent: Content? + private(set) var currentText: String = "" + + init() { + self.contentContainer = ASDisplayNode() - self.backgroundNode = ASImageNode() - self.backgroundNode.isLayerBacked = true - self.backgroundNode.displayWithoutProcessing = false - self.backgroundNode.displaysAsynchronously = false + self.effectView = UIVisualEffectView() + self.effectView.effect = UIBlurEffect(style: .light) + self.effectView.layer.cornerRadius = self.largeButtonSize / 2.0 + self.effectView.clipsToBounds = true + self.effectView.isUserInteractionEnabled = false - if let label = label { - let labelNode = ASTextNode() - labelNode.attributedText = NSAttributedString(string: label, font: labelFont, textColor: .white) - self.labelNode = labelNode - } else { - self.labelNode = nil - } + self.contentNode = ASImageNode() + self.contentNode.isUserInteractionEnabled = false - var regularImage: UIImage? - var highlightedImage: UIImage? - var filledImage: UIImage? + self.overlayHighlightNode = ASImageNode() + self.overlayHighlightNode.isUserInteractionEnabled = false + self.overlayHighlightNode.alpha = 0.0 - switch type { - case .mute: - regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallMuteButton"), strokeColor: emptyStroke, fillColor: .clear) - highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallMuteButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill) - filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallMuteButton"), strokeColor: nil, fillColor: invertedFill, knockout: true) - case .accept: - regularImage = generateFilledButtonImage(color: UIColor(rgb: 0x74db58), icon: UIImage(bundleImageName: "Call/CallPhoneButton"), angle: CGFloat.pi * 3.0 / 4.0) - highlightedImage = generateFilledButtonImage(color: UIColor(rgb: 0x74db58), icon: UIImage(bundleImageName: "Call/CallPhoneButton"), angle: CGFloat.pi * 3.0 / 4.0) - case .end: - regularImage = generateFilledButtonImage(color: UIColor(rgb: 0xd92326), icon: UIImage(bundleImageName: "Call/CallPhoneButton")) - highlightedImage = generateFilledButtonImage(color: UIColor(rgb: 0xd92326), icon: UIImage(bundleImageName: "Call/CallPhoneButton")) - case .speaker: - regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallSpeakerButton"), strokeColor: emptyStroke, fillColor: .clear) - highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallSpeakerButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill) - filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallSpeakerButton"), strokeColor: nil, fillColor: invertedFill, knockout: true) - case .bluetooth: - regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: .clear) - highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill) - filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: nil, fillColor: invertedFill, knockout: true) - case .switchCamera: - let patternImage = generateTintedImage(image: UIImage(bundleImageName: "Call/CallSwitchCameraButton"), color: .white) - regularImage = generateEmptyButtonImage(icon: patternImage, strokeColor: emptyStroke, fillColor: .clear) - highlightedImage = generateEmptyButtonImage(icon: patternImage, strokeColor: emptyStroke, fillColor: emptyHighlightedFill) - filledImage = generateEmptyButtonImage(icon: patternImage, strokeColor: nil, fillColor: invertedFill, knockout: true) - } + self.textNode = ImmediateTextNode() + self.textNode.displaysAsynchronously = false + self.textNode.isUserInteractionEnabled = false - self.regularImage = regularImage - self.highlightedImage = highlightedImage - self.filledImage = filledImage + super.init(pointerStyle: nil) - super.init() + self.addSubnode(self.contentContainer) + self.contentContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.largeButtonSize, height: self.largeButtonSize)) - self.addSubnode(self.backgroundNode) + self.addSubnode(self.textNode) - if let labelNode = self.labelNode { - self.addSubnode(labelNode) - } - - self.backgroundNode.image = regularImage - self.currentImage = regularImage + self.contentContainer.view.addSubview(self.effectView) + self.contentContainer.addSubnode(self.contentNode) + self.contentContainer.addSubnode(self.overlayHighlightNode) self.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - strongSelf.internalHighlighted = highlighted - strongSelf.updateState(highlighted: highlighted, selected: strongSelf.isSelected) + guard let strongSelf = self else { + return } - } - } - - private var internalHighlighted = false - - override var isSelected: Bool { - didSet { - self.updateState(highlighted: self.internalHighlighted, selected: self.isSelected) - } - } - - private var currentImage: UIImage? - - private func updateState(highlighted: Bool, selected: Bool) { - let image: UIImage? - if selected { - image = self.filledImage - } else if highlighted { - image = self.highlightedImage - } else { - image = self.regularImage - } - - if self.currentImage !== image { - let currentContents = self.backgroundNode.layer.contents - self.backgroundNode.layer.removeAnimation(forKey: "contents") - if let currentContents = currentContents, let image = image { - self.backgroundNode.image = image - self.backgroundNode.layer.animate(from: currentContents as AnyObject, to: image.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: image === self.currentImage || image === self.filledImage ? 0.25 : 0.15) + if highlighted { + strongSelf.overlayHighlightNode.alpha = 1.0 } else { - self.backgroundNode.image = image + strongSelf.overlayHighlightNode.alpha = 0.0 + strongSelf.overlayHighlightNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) } - self.currentImage = image } } - func updateType(_ type: CallControllerButtonType) { - if self.type == type { - return - } - self.type = type - var regularImage: UIImage? - var highlightedImage: UIImage? - var filledImage: UIImage? + func update(size: CGSize, content: Content, text: String, transition: ContainedViewLayoutTransition) { + let scaleFactor = size.width / self.largeButtonSize - switch type { - case .mute: - regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallMuteButton"), strokeColor: emptyStroke, fillColor: .clear) - highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallMuteButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill) - filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallMuteButton"), strokeColor: nil, fillColor: invertedFill, knockout: true) - case .accept: - regularImage = generateFilledButtonImage(color: UIColor(rgb: 0x74db58), icon: UIImage(bundleImageName: "Call/CallPhoneButton"), angle: CGFloat.pi * 3.0 / 4.0) - highlightedImage = generateFilledButtonImage(color: UIColor(rgb: 0x74db58), icon: UIImage(bundleImageName: "Call/CallPhoneButton"), angle: CGFloat.pi * 3.0 / 4.0) - case .end: - regularImage = generateFilledButtonImage(color: UIColor(rgb: 0xd92326), icon: UIImage(bundleImageName: "Call/CallPhoneButton")) - highlightedImage = generateFilledButtonImage(color: UIColor(rgb: 0xd92326), icon: UIImage(bundleImageName: "Call/CallPhoneButton")) - case .speaker: - regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallSpeakerButton"), strokeColor: emptyStroke, fillColor: .clear) - highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallSpeakerButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill) - filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallSpeakerButton"), strokeColor: nil, fillColor: invertedFill, knockout: true) - case .bluetooth: - regularImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: .clear) - highlightedImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: emptyStroke, fillColor: emptyHighlightedFill) - filledImage = generateEmptyButtonImage(icon: UIImage(bundleImageName: "Call/CallBluetoothButton"), strokeColor: nil, fillColor: invertedFill, knockout: true) - case .switchCamera: - let patternImage = generateTintedImage(image: UIImage(bundleImageName: "Call/CallSwitchCameraButton"), color: .white) - regularImage = generateEmptyButtonImage(icon: patternImage, strokeColor: emptyStroke, fillColor: .clear) - highlightedImage = generateEmptyButtonImage(icon: patternImage, strokeColor: emptyStroke, fillColor: emptyHighlightedFill) - filledImage = generateEmptyButtonImage(icon: patternImage, strokeColor: nil, fillColor: invertedFill, knockout: true) + self.effectView.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.largeButtonSize, height: self.largeButtonSize)) + self.contentNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.largeButtonSize, height: self.largeButtonSize)) + self.overlayHighlightNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: self.largeButtonSize, height: self.largeButtonSize)) + + if self.currentContent != content { + self.currentContent = content + + switch content.appearance { + case .blurred: + self.effectView.isHidden = false + case .color: + self.effectView.isHidden = true + } + + let contentImage = generateImage(CGSize(width: self.largeButtonSize, height: self.largeButtonSize), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + var fillColor: UIColor = .clear + var drawOverMask = false + context.setBlendMode(.normal) + var imageScale: CGFloat = 1.0 + switch content.appearance { + case let .blurred(isFilled): + if isFilled { + fillColor = .white + drawOverMask = true + context.setBlendMode(.copy) + } + let smallButtonSize: CGFloat = 60.0 + imageScale = self.largeButtonSize / smallButtonSize + case let .color(color): + switch color { + case .red: + fillColor = UIColor(rgb: 0xd92326) + case .green: + fillColor = UIColor(rgb: 0x74db58) + } + } + + context.setFillColor(fillColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + + var image: UIImage? + + switch content.image { + case .camera: + image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallCameraButton"), color: .white) + case .mute: + image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallMuteButton"), color: .white) + case .flipCamera: + image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallSwitchCameraButton"), color: .white) + case .bluetooth: + image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallBluetoothButton"), color: .white) + case .speaker: + image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallSpeakerButton"), color: .white) + case .accept: + image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallAcceptButton"), color: .white) + case .end: + image = generateTintedImage(image: UIImage(bundleImageName: "Call/CallDeclineButton"), color: .white) + } + + if let image = image { + context.translateBy(x: size.width / 2.0, y: size.height / 2.0) + context.scaleBy(x: imageScale, y: imageScale) + context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + + let imageRect = CGRect(origin: CGPoint(x: floor((size.width - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size) + if drawOverMask { + context.clip(to: imageRect, mask: image.cgImage!) + context.setBlendMode(.copy) + context.setFillColor(UIColor.clear.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + } else { + context.draw(image.cgImage!, in: imageRect) + } + } + }) + if transition.isAnimated, let contentImage = contentImage, let previousContent = self.contentNode.image { + self.contentNode.image = contentImage + self.contentNode.layer.animate(from: previousContent.cgImage!, to: contentImage.cgImage!, keyPath: "contents", timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, duration: 0.2) + } else { + self.contentNode.image = contentImage + } + + self.overlayHighlightNode.image = generateImage(CGSize(width: self.largeButtonSize, height: self.largeButtonSize), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + let fillColor: UIColor + context.setBlendMode(.normal) + switch content.appearance { + case let .blurred(isFilled): + if isFilled { + fillColor = UIColor(white: 0.0, alpha: 0.1) + } else { + fillColor = UIColor(white: 1.0, alpha: 0.2) + } + case let .color(color): + switch color { + case .red: + fillColor = UIColor(rgb: 0xd92326).withMultipliedBrightnessBy(0.2).withAlphaComponent(0.2) + case .green: + fillColor = UIColor(rgb: 0x74db58).withMultipliedBrightnessBy(0.2).withAlphaComponent(0.2) + } + } + + context.setFillColor(fillColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + }) } - self.regularImage = regularImage - self.highlightedImage = highlightedImage - self.filledImage = filledImage + transition.updatePosition(node: self.contentContainer, position: CGPoint(x: size.width / 2.0, y: size.height / 2.0)) + transition.updateSublayerTransformScale(node: self.contentContainer, scale: scaleFactor) - self.updateState(highlighted: self.isHighlighted, selected: self.isSelected) - } - - func animateRollTransition() { - self.backgroundNode.layer.animate(from: 0.0 as NSNumber, to: (-CGFloat.pi * 5 / 4) as NSNumber, keyPath: "transform.rotation.z", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.3, removeOnCompletion: false) - self.labelNode?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25, removeOnCompletion: false) - } - - override func layout() { - super.layout() - - let size = self.bounds.size - - self.backgroundNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.width)) - - if let labelNode = self.labelNode { - let labelSize = labelNode.measure(CGSize(width: 200.0, height: 100.0)) - labelNode.frame = CGRect(origin: CGPoint(x: floor((size.width - labelSize.width) / 2.0), y: 81.0), size: labelSize) + if self.currentText != text { + self.textNode.attributedText = NSAttributedString(string: text, font: labelFont, textColor: .white) } + let textSize = self.textNode.updateLayout(CGSize(width: 150.0, height: 100.0)) + let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: size.height + 5.0), size: textSize) + if self.currentText.isEmpty { + self.textNode.frame = textFrame + if transition.isAnimated { + self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + } else { + transition.updateFrameAdditiveToCenter(node: self.textNode, frame: textFrame) + } + self.currentText = text } } diff --git a/submodules/TelegramCallsUI/Sources/CallControllerButtonsNode.swift b/submodules/TelegramCallsUI/Sources/CallControllerButtonsNode.swift index 7a90fd8b3f..2b2505efa3 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerButtonsNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerButtonsNode.swift @@ -22,27 +22,65 @@ enum CallControllerButtonsMode: Equatable { } case active(speakerMode: CallControllerButtonsSpeakerMode, videoState: VideoState) - case incoming + case incoming(speakerMode: CallControllerButtonsSpeakerMode, videoState: VideoState) + case outgoingRinging(speakerMode: CallControllerButtonsSpeakerMode, videoState: VideoState) +} + +private enum ButtonDescription: Equatable { + enum Key: Hashable { + case accept + case end + case enableCamera + case switchCamera + case soundOutput + case mute + } + + enum SoundOutput { + case speaker + case bluetooth + } + + enum EndType { + case outgoing + case decline + case end + } + + case accept + case end(EndType) + case enableCamera(Bool) + case switchCamera + case soundOutput(SoundOutput) + case mute(Bool) + + var key: Key { + switch self { + case .accept: + return .accept + case .end: + return .end + case .enableCamera: + return .enableCamera + case .switchCamera: + return .switchCamera + case .soundOutput: + return .soundOutput + case .mute: + return .mute + } + } } final class CallControllerButtonsNode: ASDisplayNode { - private let acceptButton: CallControllerButtonNode - private let declineButton: CallControllerButtonNode - - private let muteButton: CallControllerButtonNode - private let endButton: CallControllerButtonNode - private let speakerButton: CallControllerButtonNode - private let swichCameraButton: CallControllerButtonNode + private var buttonNodes: [ButtonDescription.Key: CallControllerButtonItemNode] = [:] private var mode: CallControllerButtonsMode? private var validLayout: CGFloat? - var isMuted = false { - didSet { - self.muteButton.isSelected = self.isMuted - } - } + var isMuted = false + var isCameraPaused = false var accept: (() -> Void)? var mute: (() -> Void)? @@ -52,57 +90,30 @@ final class CallControllerButtonsNode: ASDisplayNode { var rotateCamera: (() -> Void)? init(strings: PresentationStrings) { - self.acceptButton = CallControllerButtonNode(type: .accept, label: strings.Call_Accept) - self.acceptButton.alpha = 0.0 - self.declineButton = CallControllerButtonNode(type: .end, label: strings.Call_Decline) - self.declineButton.alpha = 0.0 - - self.muteButton = CallControllerButtonNode(type: .mute, label: nil) - self.muteButton.alpha = 0.0 - self.endButton = CallControllerButtonNode(type: .end, label: nil) - self.endButton.alpha = 0.0 - self.speakerButton = CallControllerButtonNode(type: .speaker, label: nil) - self.speakerButton.alpha = 0.0 - self.swichCameraButton = CallControllerButtonNode(type: .switchCamera, label: nil) - self.swichCameraButton.alpha = 0.0 - super.init() - - self.addSubnode(self.acceptButton) - self.addSubnode(self.declineButton) - self.addSubnode(self.muteButton) - self.addSubnode(self.endButton) - self.addSubnode(self.speakerButton) - self.addSubnode(self.swichCameraButton) - - self.acceptButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside) - self.declineButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside) - self.muteButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside) - self.endButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside) - self.speakerButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside) - self.swichCameraButton.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside) } - func updateLayout(constrainedWidth: CGFloat, transition: ContainedViewLayoutTransition) { - let previousLayout = self.validLayout + func updateLayout(strings: PresentationStrings, constrainedWidth: CGFloat, transition: ContainedViewLayoutTransition) { self.validLayout = constrainedWidth - if let mode = self.mode, previousLayout != self.validLayout { - self.updateButtonsLayout(mode: mode, width: constrainedWidth, animated: false) + if let mode = self.mode { + self.updateButtonsLayout(strings: strings, mode: mode, width: constrainedWidth, animated: transition.isAnimated) } } - func updateMode(_ mode: CallControllerButtonsMode) { + func updateMode(strings: PresentationStrings, mode: CallControllerButtonsMode) { if self.mode != mode { let previousMode = self.mode self.mode = mode if let validLayout = self.validLayout { - self.updateButtonsLayout(mode: mode, width: validLayout, animated: previousMode != nil) + self.updateButtonsLayout(strings: strings, mode: mode, width: validLayout, animated: previousMode != nil) } } } - private func updateButtonsLayout(mode: CallControllerButtonsMode, width: CGFloat, animated: Bool) { + private var appliedMode: CallControllerButtonsMode? + + private func updateButtonsLayout(strings: PresentationStrings, mode: CallControllerButtonsMode, width: CGFloat, animated: Bool) { let transition: ContainedViewLayoutTransition if animated { transition = .animated(duration: 0.3, curve: .spring) @@ -110,147 +121,273 @@ final class CallControllerButtonsNode: ASDisplayNode { transition = .immediate } - let threeButtonSpacing: CGFloat = 28.0 - let twoButtonSpacing: CGFloat = 105.0 - let buttonSize = CGSize(width: 75.0, height: 75.0) - - let threeButtonsWidth = 3.0 * buttonSize.width + 2.0 * threeButtonSpacing - let twoButtonsWidth = 2.0 * buttonSize.width + 1.0 * twoButtonSpacing + let previousMode = self.appliedMode + self.appliedMode = mode - var origin = CGPoint(x: floor((width - threeButtonsWidth) / 2.0), y: 0.0) + var animatePositionsWithDelay = false + if let previousMode = previousMode { + switch previousMode { + case .incoming, .outgoingRinging: + if case .active = mode { + animatePositionsWithDelay = true + } + default: + break + } + } - for button in [self.muteButton, self.endButton, self.speakerButton] { - transition.updateFrame(node: button, frame: CGRect(origin: origin, size: buttonSize)) - if button === self.speakerButton { - transition.updateFrame(node: self.swichCameraButton, frame: CGRect(origin: origin, size: buttonSize)) + let minSmallButtonSideInset: CGFloat = 34.0 + let maxSmallButtonSpacing: CGFloat = 34.0 + let smallButtonSize: CGFloat = 60.0 + let topBottomSpacing: CGFloat = 84.0 + + let maxLargeButtonSpacing: CGFloat = 115.0 + let largeButtonSize: CGFloat = 72.0 + let minLargeButtonSideInset: CGFloat = minSmallButtonSideInset - 6.0 + + struct PlacedButton { + let button: ButtonDescription + let frame: CGRect + } + + var buttons: [PlacedButton] = [] + switch mode { + case .incoming(let speakerMode, let videoState), .outgoingRinging(let speakerMode, let videoState): + var topButtons: [ButtonDescription] = [] + var bottomButtons: [ButtonDescription] = [] + + let soundOutput: ButtonDescription.SoundOutput + switch speakerMode { + case .none, .builtin, .speaker: + soundOutput = .speaker + case .headphones: + soundOutput = .bluetooth + case .bluetooth: + soundOutput = .bluetooth } - origin.x += buttonSize.width + threeButtonSpacing + switch videoState { + case .active, .available: + topButtons.append(.enableCamera(!self.isCameraPaused)) + topButtons.append(.mute(self.isMuted)) + topButtons.append(.switchCamera) + case .notAvailable: + topButtons.append(.enableCamera(!self.isCameraPaused)) + topButtons.append(.mute(self.isMuted)) + topButtons.append(.soundOutput(soundOutput)) + } + + let topButtonsContentWidth = CGFloat(topButtons.count) * smallButtonSize + let topButtonsAvailableSpacingWidth = width - topButtonsContentWidth - minSmallButtonSideInset * 2.0 + let topButtonsSpacing = min(maxSmallButtonSpacing, topButtonsAvailableSpacingWidth / CGFloat(topButtons.count - 1)) + let topButtonsWidth = CGFloat(topButtons.count) * smallButtonSize + CGFloat(topButtons.count - 1) * topButtonsSpacing + var topButtonsLeftOffset = floor((width - topButtonsWidth) / 2.0) + for button in topButtons { + buttons.append(PlacedButton(button: button, frame: CGRect(origin: CGPoint(x: topButtonsLeftOffset, y: 0.0), size: CGSize(width: smallButtonSize, height: smallButtonSize)))) + topButtonsLeftOffset += smallButtonSize + topButtonsSpacing + } + + if case .incoming = mode { + bottomButtons.append(.end(.decline)) + bottomButtons.append(.accept) + } else { + bottomButtons.append(.end(.outgoing)) + } + + let bottomButtonsContentWidth = CGFloat(bottomButtons.count) * largeButtonSize + let bottomButtonsAvailableSpacingWidth = width - bottomButtonsContentWidth - minLargeButtonSideInset * 2.0 + let bottomButtonsSpacing = min(maxLargeButtonSpacing, bottomButtonsAvailableSpacingWidth / CGFloat(bottomButtons.count - 1)) + let bottomButtonsWidth = CGFloat(bottomButtons.count) * largeButtonSize + CGFloat(bottomButtons.count - 1) * bottomButtonsSpacing + var bottomButtonsLeftOffset = floor((width - bottomButtonsWidth) / 2.0) + for button in bottomButtons { + buttons.append(PlacedButton(button: button, frame: CGRect(origin: CGPoint(x: bottomButtonsLeftOffset, y: smallButtonSize + topBottomSpacing), size: CGSize(width: largeButtonSize, height: largeButtonSize)))) + bottomButtonsLeftOffset += largeButtonSize + bottomButtonsSpacing + } + case let .active(speakerMode, videoState): + var topButtons: [ButtonDescription] = [] + + let soundOutput: ButtonDescription.SoundOutput + switch speakerMode { + case .none, .builtin, .speaker: + soundOutput = .speaker + case .headphones: + soundOutput = .bluetooth + case .bluetooth: + soundOutput = .bluetooth + } + + switch videoState { + case .active, .available: + topButtons.append(.enableCamera(!self.isCameraPaused)) + topButtons.append(.mute(isMuted)) + topButtons.append(.switchCamera) + case .notAvailable: + topButtons.append(.enableCamera(!self.isCameraPaused)) + topButtons.append(.mute(isMuted)) + topButtons.append(.soundOutput(soundOutput)) + } + + topButtons.append(.end(.end)) + + let topButtonsContentWidth = CGFloat(topButtons.count) * smallButtonSize + let topButtonsAvailableSpacingWidth = width - topButtonsContentWidth - minSmallButtonSideInset * 2.0 + let topButtonsSpacing = min(maxSmallButtonSpacing, topButtonsAvailableSpacingWidth / CGFloat(topButtons.count - 1)) + let topButtonsWidth = CGFloat(topButtons.count) * smallButtonSize + CGFloat(topButtons.count - 1) * topButtonsSpacing + var topButtonsLeftOffset = floor((width - topButtonsWidth) / 2.0) + for button in topButtons { + buttons.append(PlacedButton(button: button, frame: CGRect(origin: CGPoint(x: topButtonsLeftOffset, y: smallButtonSize + topBottomSpacing), size: CGSize(width: smallButtonSize, height: smallButtonSize)))) + topButtonsLeftOffset += smallButtonSize + topButtonsSpacing + } } - origin = CGPoint(x: floor((width - twoButtonsWidth) / 2.0), y: 0.0) - for button in [self.declineButton, self.acceptButton] { - transition.updateFrame(node: button, frame: CGRect(origin: origin, size: buttonSize)) - origin.x += buttonSize.width + twoButtonSpacing + let delayIncrement = 0.015 + var validKeys: [ButtonDescription.Key] = [] + for button in buttons { + validKeys.append(button.button.key) + var buttonTransition = transition + var animateButtonIn = false + let buttonNode: CallControllerButtonItemNode + if let current = self.buttonNodes[button.button.key] { + buttonNode = current + } else { + buttonNode = CallControllerButtonItemNode() + self.buttonNodes[button.button.key] = buttonNode + self.addSubnode(buttonNode) + buttonNode.addTarget(self, action: #selector(self.buttonPressed(_:)), forControlEvents: .touchUpInside) + buttonTransition = .immediate + animateButtonIn = transition.isAnimated + } + let buttonContent: CallControllerButtonItemNode.Content + let buttonText: String + switch button.button { + case .accept: + buttonContent = CallControllerButtonItemNode.Content( + appearance: .color(.green), + image: .accept + ) + buttonText = strings.Call_Accept + case let .end(type): + buttonContent = CallControllerButtonItemNode.Content( + appearance: .color(.red), + image: .end + ) + switch type { + case .outgoing: + buttonText = "" + case .decline: + buttonText = strings.Call_Decline + case .end: + buttonText = strings.Call_End + } + case let .enableCamera(isEnabled): + buttonContent = CallControllerButtonItemNode.Content( + appearance: .blurred(isFilled: isEnabled), + image: .camera + ) + buttonText = strings.Call_Camera + case .switchCamera: + buttonContent = CallControllerButtonItemNode.Content( + appearance: .blurred(isFilled: false), + image: .flipCamera + ) + buttonText = strings.Call_Flip + case let .soundOutput(value): + let image: CallControllerButtonItemNode.Content.Image + switch value { + case .speaker: + image = .speaker + case .bluetooth: + image = .bluetooth + } + buttonContent = CallControllerButtonItemNode.Content( + appearance: .blurred(isFilled: false), + image: image + ) + buttonText = strings.Call_Speaker + case let .mute(isMuted): + buttonContent = CallControllerButtonItemNode.Content( + appearance: .blurred(isFilled: isMuted), + image: .mute + ) + buttonText = strings.Call_Mute + } + var buttonDelay = 0.0 + if animatePositionsWithDelay { + switch button.button.key { + case .enableCamera: + buttonDelay = 0.0 + case .mute: + buttonDelay = delayIncrement * 1.0 + case .switchCamera: + buttonDelay = delayIncrement * 2.0 + case .end: + buttonDelay = delayIncrement * 3.0 + default: + break + } + } + buttonTransition.updateFrame(node: buttonNode, frame: button.frame, delay: buttonDelay) + buttonNode.update(size: button.frame.size, content: buttonContent, text: buttonText, transition: buttonTransition) + if animateButtonIn { + buttonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } } - switch mode { - case .incoming: - for button in [self.declineButton, self.acceptButton] { - button.alpha = 1.0 - } - for button in [self.muteButton, self.endButton, self.speakerButton, self.swichCameraButton] { - button.alpha = 0.0 - } - case let .active(speakerMode, videoState): - for button in [self.muteButton] { - if animated && button.alpha.isZero { - button.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - } - button.alpha = 1.0 - } - switch videoState { - case .active, .available: - for button in [self.speakerButton] { - if animated && !button.alpha.isZero { - button.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - } - button.alpha = 0.0 - } - for button in [self.swichCameraButton] { - if animated && button.alpha.isZero { - button.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - } - button.alpha = 1.0 - } - case .notAvailable: - for button in [self.swichCameraButton] { - if animated && !button.alpha.isZero { - button.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - } - button.alpha = 0.0 - } - for button in [self.speakerButton] { - if animated && button.alpha.isZero { - button.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - } - button.alpha = 1.0 - } - } - var animatingAcceptButton = false - if self.endButton.alpha.isZero { - if animated { - if !self.acceptButton.alpha.isZero { - animatingAcceptButton = true - self.endButton.layer.animatePosition(from: self.acceptButton.position, to: self.endButton.position, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) - self.acceptButton.animateRollTransition() - self.endButton.layer.animate(from: (CGFloat.pi * 5 / 4) as NSNumber, to: 0.0 as NSNumber, keyPath: "transform.rotation.z", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.3) - self.acceptButton.layer.animatePosition(from: self.acceptButton.position, to: self.endButton.position, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, completion: { [weak self] _ in - if let strongSelf = self { - strongSelf.acceptButton.alpha = 0.0 - strongSelf.acceptButton.layer.removeAnimation(forKey: "position") - strongSelf.acceptButton.layer.removeAnimation(forKey: "transform.rotation.z") - } + var removedKeys: [ButtonDescription.Key] = [] + for (key, button) in self.buttonNodes { + if !validKeys.contains(key) { + removedKeys.append(key) + if animated { + if case .accept = key { + if let endButton = self.buttonNodes[.end] { + transition.updateFrame(node: button, frame: endButton.frame) + if let content = button.currentContent { + button.update(size: endButton.frame.size, content: content, text: button.currentText, transition: transition) + } + transition.updateTransformScale(node: button, scale: 0.1) + transition.updateAlpha(node: button, alpha: 0.0, completion: { [weak button] _ in + button?.removeFromSupernode() }) } - self.endButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } else { + transition.updateAlpha(node: button, alpha: 0.0, completion: { [weak button] _ in + button?.removeFromSupernode() + }) } - self.endButton.alpha = 1.0 + } else { + button.removeFromSupernode() } - - if !self.declineButton.alpha.isZero { - if animated { - self.declineButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) - } - self.declineButton.alpha = 0.0 - } - - if self.acceptButton.alpha.isZero && !animatingAcceptButton { - self.acceptButton.alpha = 0.0 - } - - self.speakerButton.isSelected = speakerMode == .speaker - self.speakerButton.isHidden = speakerMode == .none - let speakerButtonType: CallControllerButtonType - switch speakerMode { - case .none, .builtin, .speaker: - speakerButtonType = .speaker - case .headphones: - speakerButtonType = .bluetooth - case .bluetooth: - speakerButtonType = .bluetooth - } - self.speakerButton.updateType(speakerButtonType) + } + } + for key in removedKeys { + self.buttonNodes.removeValue(forKey: key) } } - @objc func buttonPressed(_ button: CallControllerButtonNode) { - if button === self.muteButton { - self.mute?() - } else if button === self.endButton || button === self.declineButton { - self.end?() - } else if button === self.speakerButton { - self.speaker?() - } else if button === self.acceptButton { - self.accept?() - } else if button === self.swichCameraButton { - self.rotateCamera?() + @objc func buttonPressed(_ button: CallControllerButtonItemNode) { + for (key, listButton) in self.buttonNodes { + if button === listButton { + switch key { + case .accept: + self.accept?() + case .end: + self.end?() + case .enableCamera: + self.toggleVideo?() + case .switchCamera: + self.rotateCamera?() + case .soundOutput: + self.speaker?() + case .mute: + self.mute?() + } + break + } } } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - let buttons = [ - self.acceptButton, - self.declineButton, - self.muteButton, - self.endButton, - self.speakerButton, - self.swichCameraButton - ] - for button in buttons { - if button.isHidden || button.alpha.isZero { - continue - } + for (_, button) in self.buttonNodes { if let result = button.view.hitTest(self.view.convert(point, to: button.view), with: event) { return result } diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift index 35a5100543..e204fea8d9 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNode.swift @@ -56,34 +56,91 @@ private final class IncomingVideoNode: ASDisplayNode { } private final class OutgoingVideoNode: ASDisplayNode { + private let videoTransformContainer: ASDisplayNode private let videoView: UIView - private let switchCameraButton: HighlightableButtonNode - private let switchCamera: () -> Void + private let buttonNode: HighlightTrackingButtonNode - init(videoView: UIView, switchCamera: @escaping () -> Void) { + private var effectView: UIVisualEffectView? + private var isBlurred: Bool = false + private var isExpanded: Bool = false + + var tapped: (() -> Void)? + + init(videoView: UIView) { + self.videoTransformContainer = ASDisplayNode() + self.videoTransformContainer.clipsToBounds = true self.videoView = videoView - self.switchCameraButton = HighlightableButtonNode() - self.switchCamera = switchCamera + self.videoView.layer.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0) + + self.buttonNode = HighlightTrackingButtonNode() super.init() - self.view.addSubview(self.videoView) - self.addSubnode(self.switchCameraButton) - self.switchCameraButton.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + self.videoTransformContainer.view.addSubview(self.videoView) + self.addSubnode(self.videoTransformContainer) + //self.addSubnode(self.buttonNode) + + self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) } - @objc private func buttonPressed() { - self.switchCamera() + @objc func buttonPressed() { + self.tapped?() } func updateLayout(size: CGSize, isExpanded: Bool, transition: ContainedViewLayoutTransition) { - transition.updateFrame(view: self.videoView, frame: CGRect(origin: CGPoint(), size: size)) - transition.updateCornerRadius(layer: self.videoView.layer, cornerRadius: isExpanded ? 0.0 : 16.0) - self.switchCameraButton.frame = CGRect(origin: CGPoint(), size: size) + let videoFrame = CGRect(origin: CGPoint(), size: size) + self.buttonNode.frame = videoFrame + self.isExpanded = isExpanded + + let previousVideoFrame = self.videoTransformContainer.frame + self.videoTransformContainer.frame = videoFrame + if transition.isAnimated && !videoFrame.height.isZero && !previousVideoFrame.height.isZero { + transition.animatePositionAdditive(node: self.videoTransformContainer, offset: CGPoint(x: previousVideoFrame.midX - videoFrame.midX, y: previousVideoFrame.midY - videoFrame.midY)) + transition.animateTransformScale(node: self.videoTransformContainer, from: previousVideoFrame.height / videoFrame.height) + } + + self.videoView.frame = videoFrame + + transition.updateCornerRadius(layer: self.videoTransformContainer.layer, cornerRadius: isExpanded ? 0.0 : 16.0) + if let effectView = self.effectView { + transition.updateCornerRadius(layer: effectView.layer, cornerRadius: isExpanded ? 0.0 : 16.0) + } + } + + func updateIsBlurred(isBlurred: Bool) { + if self.isBlurred == isBlurred { + return + } + self.isBlurred = isBlurred + + if isBlurred { + if self.effectView == nil { + let effectView = UIVisualEffectView() + effectView.clipsToBounds = true + effectView.layer.cornerRadius = self.isExpanded ? 0.0 : 16.0 + self.effectView = effectView + effectView.frame = self.videoView.frame + self.view.addSubview(effectView) + } + UIView.animate(withDuration: 0.3, animations: { + self.effectView?.effect = UIBlurEffect(style: .dark) + }) + } else if let effectView = self.effectView { + UIView.animate(withDuration: 0.3, animations: { + effectView.effect = nil + }) + } } } final class CallControllerNode: ASDisplayNode { + private enum VideoNodeCorner { + case topLeft + case topRight + case bottomLeft + case bottomRight + } + private let sharedContext: SharedAccountContext private let account: Account @@ -104,6 +161,8 @@ final class CallControllerNode: ASDisplayNode { private var incomingVideoViewRequested: Bool = false private var outgoingVideoNode: OutgoingVideoNode? private var outgoingVideoViewRequested: Bool = false + private var outgoingVideoExplicitelyFullscreen: Bool = false + private var outgoingVideoNodeCorner: VideoNodeCorner = .bottomRight private let backButtonArrowNode: ASImageNode private let backButtonNode: HighlightableButtonNode private let statusNode: CallControllerStatusNode @@ -121,6 +180,9 @@ final class CallControllerNode: ASDisplayNode { var isMuted: Bool = false { didSet { self.buttonsNode.isMuted = self.isMuted + if let (layout, navigationBarHeight) = self.validLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut)) + } } } @@ -134,12 +196,15 @@ final class CallControllerNode: ASDisplayNode { var beginAudioOuputSelection: (() -> Void)? var acceptCall: (() -> Void)? var endCall: (() -> Void)? - var toggleVideo: (() -> Void)? + var setIsVideoPaused: ((Bool) -> Void)? var back: (() -> Void)? var presentCallRating: ((CallId) -> Void)? var callEnded: ((Bool) -> Void)? var dismissedInteractively: (() -> Void)? + private var isUIHidden: Bool = false + private var isVideoPaused: Bool = false + init(sharedContext: SharedAccountContext, account: Account, presentationData: PresentationData, statusBar: StatusBar, debugInfo: Signal<(String, String), NoError>, shouldStayHiddenUntilConnection: Bool = false, easyDebugAccess: Bool, call: PresentationCall) { self.sharedContext = sharedContext self.account = account @@ -229,7 +294,17 @@ final class CallControllerNode: ASDisplayNode { } self.buttonsNode.toggleVideo = { [weak self] in - self?.toggleVideo?() + guard let strongSelf = self else { + return + } + strongSelf.isVideoPaused = !strongSelf.isVideoPaused + strongSelf.outgoingVideoNode?.updateIsBlurred(isBlurred: strongSelf.isVideoPaused) + strongSelf.buttonsNode.isCameraPaused = strongSelf.isVideoPaused + strongSelf.setIsVideoPaused?(strongSelf.isVideoPaused) + + if let (layout, navigationBarHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut)) + } } self.buttonsNode.rotateCamera = { [weak self] in @@ -302,17 +377,21 @@ final class CallControllerNode: ASDisplayNode { return } if let incomingVideoView = incomingVideoView { - strongSelf.setCurrentAudioOutput?(.speaker) let incomingVideoNode = IncomingVideoNode(videoView: incomingVideoView) strongSelf.incomingVideoNode = incomingVideoNode strongSelf.containerNode.insertSubnode(incomingVideoNode, aboveSubnode: strongSelf.dimNode) - strongSelf.statusNode.isHidden = true if let (layout, navigationBarHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.5, curve: .spring)) } } }) } + default: + break + } + + switch callState.videoState { + case .active, .activeOutgoing: if !self.outgoingVideoViewRequested { self.outgoingVideoViewRequested = true self.call.makeOutgoingVideoView(completion: { [weak self] outgoingVideoView in @@ -322,13 +401,15 @@ final class CallControllerNode: ASDisplayNode { if let outgoingVideoView = outgoingVideoView { outgoingVideoView.backgroundColor = .black outgoingVideoView.clipsToBounds = true - strongSelf.setCurrentAudioOutput?(.speaker) - let outgoingVideoNode = OutgoingVideoNode(videoView: outgoingVideoView, switchCamera: { - guard let strongSelf = self else { - return + if let audioOutputState = strongSelf.audioOutputState, let currentOutput = audioOutputState.currentOutput { + switch currentOutput { + case .speaker, .builtin: + break + default: + strongSelf.setCurrentAudioOutput?(.speaker) } - strongSelf.call.switchVideoCamera() - }) + } + let outgoingVideoNode = OutgoingVideoNode(videoView: outgoingVideoView) strongSelf.outgoingVideoNode = outgoingVideoNode if let incomingVideoNode = strongSelf.incomingVideoNode { strongSelf.containerNode.insertSubnode(outgoingVideoNode, aboveSubnode: incomingVideoNode) @@ -336,38 +417,17 @@ final class CallControllerNode: ASDisplayNode { strongSelf.containerNode.insertSubnode(outgoingVideoNode, aboveSubnode: strongSelf.dimNode) } if let (layout, navigationBarHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) + strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.4, curve: .spring)) } - } - }) - } - case .activeOutgoing: - if !self.outgoingVideoViewRequested { - self.outgoingVideoViewRequested = true - self.call.makeOutgoingVideoView(completion: { [weak self] outgoingVideoView in - guard let strongSelf = self else { - return - } - if let outgoingVideoView = outgoingVideoView { - outgoingVideoView.backgroundColor = .black - outgoingVideoView.clipsToBounds = true - outgoingVideoView.layer.cornerRadius = 16.0 - strongSelf.setCurrentAudioOutput?(.speaker) - let outgoingVideoNode = OutgoingVideoNode(videoView: outgoingVideoView, switchCamera: { + /*outgoingVideoNode.tapped = { guard let strongSelf = self else { return } - strongSelf.call.switchVideoCamera() - }) - strongSelf.outgoingVideoNode = outgoingVideoNode - if let incomingVideoNode = strongSelf.incomingVideoNode { - strongSelf.containerNode.insertSubnode(outgoingVideoNode, aboveSubnode: incomingVideoNode) - } else { - strongSelf.containerNode.insertSubnode(outgoingVideoNode, aboveSubnode: strongSelf.dimNode) - } - if let (layout, navigationBarHeight) = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) - } + strongSelf.outgoingVideoExplicitelyFullscreen = !strongSelf.outgoingVideoExplicitelyFullscreen + if let (layout, navigationBarHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.4, curve: .spring)) + } + }*/ } }) } @@ -438,7 +498,7 @@ final class CallControllerNode: ASDisplayNode { if isReconnecting { return strings.Call_StatusConnecting } else { - return strings.Call_StatusOngoing(value).0 + return value } }, timestamp) if self.keyTextData?.0 != keyVisualHash { @@ -501,43 +561,60 @@ final class CallControllerNode: ASDisplayNode { } } + private var buttonsTerminationMode: CallControllerButtonsMode? + private func updateButtonsMode() { guard let callState = self.callState else { return } + var mode: CallControllerButtonsSpeakerMode = .none + if let (availableOutputs, maybeCurrentOutput) = self.audioOutputState, let currentOutput = maybeCurrentOutput { + switch currentOutput { + case .builtin: + mode = .builtin + case .speaker: + mode = .speaker + case .headphones: + mode = .headphones + case .port: + mode = .bluetooth + } + if availableOutputs.count <= 1 { + mode = .none + } + } + let mappedVideoState: CallControllerButtonsMode.VideoState + switch callState.videoState { + case .notAvailable: + mappedVideoState = .notAvailable + case .available: + mappedVideoState = .available(true) + case .active: + mappedVideoState = .active + case .activeOutgoing: + mappedVideoState = .active + } + switch callState.state { - case .ringing: - self.buttonsNode.updateMode(.incoming) - default: - var mode: CallControllerButtonsSpeakerMode = .none - if let (availableOutputs, maybeCurrentOutput) = self.audioOutputState, let currentOutput = maybeCurrentOutput { - switch currentOutput { - case .builtin: - mode = .builtin - case .speaker: - mode = .speaker - case .headphones: - mode = .headphones - case .port: - mode = .bluetooth - } - if availableOutputs.count <= 1 { - mode = .none - } - } - let mappedVideoState: CallControllerButtonsMode.VideoState - switch callState.videoState { - case .notAvailable: - mappedVideoState = .notAvailable - case .available: - mappedVideoState = .available(true) - case .active: - mappedVideoState = .active - case .activeOutgoing: - mappedVideoState = .active - } - self.buttonsNode.updateMode(.active(speakerMode: mode, videoState: mappedVideoState)) + case .ringing: + let buttonsMode: CallControllerButtonsMode = .incoming(speakerMode: mode, videoState: mappedVideoState) + self.buttonsNode.updateMode(strings: self.presentationData.strings, mode: buttonsMode) + self.buttonsTerminationMode = buttonsMode + case .waiting, .requesting: + let buttonsMode: CallControllerButtonsMode = .outgoingRinging(speakerMode: mode, videoState: mappedVideoState) + self.buttonsNode.updateMode(strings: self.presentationData.strings, mode: buttonsMode) + self.buttonsTerminationMode = buttonsMode + case .active, .connecting, .reconnecting: + let buttonsMode: CallControllerButtonsMode = .active(speakerMode: mode, videoState: mappedVideoState) + self.buttonsNode.updateMode(strings: self.presentationData.strings, mode: buttonsMode) + self.buttonsTerminationMode = buttonsMode + case .terminating, .terminated: + if let buttonsTerminationMode = self.buttonsTerminationMode { + self.buttonsNode.updateMode(strings: self.presentationData.strings, mode: buttonsTerminationMode) + } else { + self.buttonsNode.updateMode(strings: self.presentationData.strings, mode: .active(speakerMode: mode, videoState: mappedVideoState)) + } } } @@ -568,9 +645,69 @@ final class CallControllerNode: ASDisplayNode { } } + private func calculatePreviewVideoRect(layout: ContainerViewLayout, navigationHeight: CGFloat) -> CGRect { + let buttonsHeight: CGFloat = 190.0 + let buttonsOffset: CGFloat + if layout.size.width.isEqual(to: 320.0) { + if layout.size.height.isEqual(to: 480.0) { + buttonsOffset = 60.0 + } else { + buttonsOffset = 73.0 + } + } else { + buttonsOffset = 83.0 + } + + let buttonsOriginY: CGFloat + if self.isUIHidden { + buttonsOriginY = layout.size.height + 40.0 - 80.0 + } else { + buttonsOriginY = layout.size.height - (buttonsOffset - 40.0) - buttonsHeight - layout.intrinsicInsets.bottom + } + + let previewVideoSize = layout.size.aspectFitted(CGSize(width: 200.0, height: 200.0)) + let previewVideoY: CGFloat + let previewVideoX: CGFloat + + switch self.outgoingVideoNodeCorner { + case .topLeft: + previewVideoX = 20.0 + if self.isUIHidden { + previewVideoY = layout.insets(options: .statusBar).top + 8.0 + } else { + previewVideoY = layout.insets(options: .statusBar).top + 44.0 + 8.0 + } + case .topRight: + previewVideoX = layout.size.width - previewVideoSize.width - 20.0 + if self.isUIHidden { + previewVideoY = layout.insets(options: .statusBar).top + 8.0 + } else { + previewVideoY = layout.insets(options: .statusBar).top + 44.0 + 8.0 + } + case .bottomLeft: + previewVideoX = 20.0 + if self.isUIHidden { + previewVideoY = layout.size.height - layout.intrinsicInsets.bottom - 8.0 - previewVideoSize.height + } else { + previewVideoY = buttonsOriginY + 100.0 - previewVideoSize.height + } + case .bottomRight: + previewVideoX = layout.size.width - previewVideoSize.width - 20.0 + if self.isUIHidden { + previewVideoY = layout.size.height - layout.intrinsicInsets.bottom - 8.0 - previewVideoSize.height + } else { + previewVideoY = buttonsOriginY + 100.0 - previewVideoSize.height + } + } + + return CGRect(origin: CGPoint(x: previewVideoX, y: previewVideoY), size: previewVideoSize) + } + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { self.validLayout = (layout, navigationBarHeight) + let overlayAlpha: CGFloat = self.isUIHidden ? 0.0 : 1.0 + transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(), size: layout.size)) transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) @@ -592,6 +729,9 @@ final class CallControllerNode: ASDisplayNode { } transition.updateFrame(node: self.backButtonNode, frame: CGRect(origin: CGPoint(x: 29.0, y: navigationOffset + 11.0), size: backSize)) + transition.updateAlpha(node: self.backButtonArrowNode, alpha: overlayAlpha) + transition.updateAlpha(node: self.backButtonNode, alpha: overlayAlpha) + var statusOffset: CGFloat if layout.metrics.widthClass == .regular && layout.metrics.heightClass == .regular { if layout.size.height.isEqual(to: 1366.0) { @@ -611,7 +751,7 @@ final class CallControllerNode: ASDisplayNode { statusOffset += layout.safeInsets.top - let buttonsHeight: CGFloat = 75.0 + let buttonsHeight: CGFloat = 190.0 let buttonsOffset: CGFloat if layout.size.width.isEqual(to: 320.0) { if layout.size.height.isEqual(to: 480.0) { @@ -625,36 +765,60 @@ final class CallControllerNode: ASDisplayNode { let statusHeight = self.statusNode.updateLayout(constrainedWidth: layout.size.width, transition: transition) transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: 0.0, y: statusOffset), size: CGSize(width: layout.size.width, height: statusHeight))) + transition.updateAlpha(node: self.statusNode, alpha: overlayAlpha) let videoPausedSize = self.videoPausedNode.updateLayout(CGSize(width: layout.size.width - 16.0, height: 100.0)) transition.updateFrame(node: self.videoPausedNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - videoPausedSize.width) / 2.0), y: floor((layout.size.height - videoPausedSize.height) / 2.0)), size: videoPausedSize)) - self.buttonsNode.updateLayout(constrainedWidth: layout.size.width, transition: transition) - let buttonsOriginY: CGFloat = layout.size.height - (buttonsOffset - 40.0) - buttonsHeight - layout.intrinsicInsets.bottom + self.buttonsNode.updateLayout(strings: self.presentationData.strings, constrainedWidth: layout.size.width, transition: transition) + let buttonsOriginY: CGFloat + if self.isUIHidden { + buttonsOriginY = layout.size.height + 40.0 - 80.0 + } else { + buttonsOriginY = layout.size.height - (buttonsOffset - 40.0) - buttonsHeight - layout.intrinsicInsets.bottom + } transition.updateFrame(node: self.buttonsNode, frame: CGRect(origin: CGPoint(x: 0.0, y: buttonsOriginY), size: CGSize(width: layout.size.width, height: buttonsHeight))) + transition.updateAlpha(node: self.buttonsNode, alpha: overlayAlpha) + + let fullscreenVideoFrame = CGRect(origin: CGPoint(), size: layout.size) + + let previewVideoFrame = self.calculatePreviewVideoRect(layout: layout, navigationHeight: navigationBarHeight) - var outgoingVideoTransition = transition if let incomingVideoNode = self.incomingVideoNode { - if incomingVideoNode.frame.width.isZero, let outgoingVideoNode = self.outgoingVideoNode, !outgoingVideoNode.frame.width.isZero, !transition.isAnimated { - outgoingVideoTransition = .animated(duration: 0.3, curve: .easeInOut) + var incomingVideoTransition = transition + if incomingVideoNode.frame.isEmpty { + incomingVideoTransition = .immediate } - incomingVideoNode.frame = CGRect(origin: CGPoint(), size: layout.size) - incomingVideoNode.updateLayout(size: layout.size) + if self.outgoingVideoExplicitelyFullscreen { + incomingVideoTransition.updateFrame(node: incomingVideoNode, frame: previewVideoFrame) + } else { + incomingVideoTransition.updateFrame(node: incomingVideoNode, frame: fullscreenVideoFrame) + } + incomingVideoNode.updateLayout(size: incomingVideoNode.frame.size) } if let outgoingVideoNode = self.outgoingVideoNode { + var outgoingVideoTransition = transition + if outgoingVideoNode.frame.isEmpty { + outgoingVideoTransition = .immediate + } if self.incomingVideoNode == nil { - outgoingVideoNode.frame = CGRect(origin: CGPoint(), size: layout.size) - outgoingVideoNode.updateLayout(size: layout.size, isExpanded: true, transition: transition) + outgoingVideoNode.frame = fullscreenVideoFrame + outgoingVideoNode.updateLayout(size: layout.size, isExpanded: true, transition: outgoingVideoTransition) } else { - let outgoingSize = layout.size.aspectFitted(CGSize(width: 200.0, height: 200.0)) - let outgoingFrame = CGRect(origin: CGPoint(x: layout.size.width - 16.0 - outgoingSize.width, y: buttonsOriginY - 32.0 - outgoingSize.height), size: outgoingSize) - outgoingVideoTransition.updateFrame(node: outgoingVideoNode, frame: outgoingFrame) - outgoingVideoNode.updateLayout(size: outgoingFrame.size, isExpanded: false, transition: outgoingVideoTransition) + if self.minimizedVideoDraggingPosition == nil { + if self.outgoingVideoExplicitelyFullscreen { + outgoingVideoTransition.updateFrame(node: outgoingVideoNode, frame: fullscreenVideoFrame) + } else { + outgoingVideoTransition.updateFrame(node: outgoingVideoNode, frame: previewVideoFrame) + } + outgoingVideoNode.updateLayout(size: outgoingVideoNode.frame.size, isExpanded: self.outgoingVideoExplicitelyFullscreen, transition: outgoingVideoTransition) + } } } let keyTextSize = self.keyButtonNode.frame.size transition.updateFrame(node: self.keyButtonNode, frame: CGRect(origin: CGPoint(x: layout.size.width - keyTextSize.width - 8.0, y: navigationOffset + 8.0), size: keyTextSize)) + transition.updateAlpha(node: self.keyButtonNode, alpha: overlayAlpha) if let debugNode = self.debugNode { transition.updateFrame(node: debugNode, frame: CGRect(origin: CGPoint(), size: layout.size)) @@ -700,26 +864,33 @@ final class CallControllerNode: ASDisplayNode { if let _ = self.keyPreviewNode { self.backPressed() } else { - let point = recognizer.location(in: recognizer.view) - if self.statusNode.frame.contains(point) { - if self.easyDebugAccess { - self.presentDebugNode() - } else { - let timestamp = CACurrentMediaTime() - if self.debugTapCounter.0 < timestamp - 0.75 { - self.debugTapCounter.0 = timestamp - self.debugTapCounter.1 = 0 - } - - if self.debugTapCounter.0 >= timestamp - 0.75 { - self.debugTapCounter.0 = timestamp - self.debugTapCounter.1 += 1 - } - - if self.debugTapCounter.1 >= 10 { - self.debugTapCounter.1 = 0 - + if self.incomingVideoNode != nil || self.outgoingVideoNode != nil { + self.isUIHidden = !self.isUIHidden + if let (layout, navigationBarHeight) = self.validLayout { + self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.3, curve: .easeInOut)) + } + } else { + let point = recognizer.location(in: recognizer.view) + if self.statusNode.frame.contains(point) { + if self.easyDebugAccess { self.presentDebugNode() + } else { + let timestamp = CACurrentMediaTime() + if self.debugTapCounter.0 < timestamp - 0.75 { + self.debugTapCounter.0 = timestamp + self.debugTapCounter.1 = 0 + } + + if self.debugTapCounter.0 >= timestamp - 0.75 { + self.debugTapCounter.0 = timestamp + self.debugTapCounter.1 += 1 + } + + if self.debugTapCounter.1 >= 10 { + self.debugTapCounter.1 = 0 + + self.presentDebugNode() + } } } } @@ -749,36 +920,170 @@ final class CallControllerNode: ASDisplayNode { } } - @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { - switch recognizer.state { - case .changed: - let offset = recognizer.translation(in: self.view).y - var bounds = self.bounds - bounds.origin.y = -offset - self.bounds = bounds - case .ended: - let velocity = recognizer.velocity(in: self.view).y - if abs(velocity) < 100.0 { - var bounds = self.bounds - let previous = bounds - bounds.origin = CGPoint() - self.bounds = bounds - self.layer.animateBounds(from: previous, to: bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + private var minimizedVideoInitialPosition: CGPoint? + private var minimizedVideoDraggingPosition: CGPoint? + + private func nodeLocationForPosition(layout: ContainerViewLayout, position: CGPoint, velocity: CGPoint) -> VideoNodeCorner { + let layoutInsets = UIEdgeInsets() + var result = CGPoint() + if position.x < layout.size.width / 2.0 { + result.x = 0.0 + } else { + result.x = 1.0 + } + if position.y < layoutInsets.top + (layout.size.height - layoutInsets.bottom - layoutInsets.top) / 2.0 { + result.y = 0.0 + } else { + result.y = 1.0 + } + + let currentPosition = result + + let angleEpsilon: CGFloat = 30.0 + var shouldHide = false + + if (velocity.x * velocity.x + velocity.y * velocity.y) >= 500.0 * 500.0 { + let x = velocity.x + let y = velocity.y + + var angle = atan2(y, x) * 180.0 / CGFloat.pi * -1.0 + if angle < 0.0 { + angle += 360.0 + } + + if currentPosition.x.isZero && currentPosition.y.isZero { + if ((angle > 0 && angle < 90 - angleEpsilon) || angle > 360 - angleEpsilon) { + result.x = 1.0 + result.y = 0.0 + } else if (angle > 180 + angleEpsilon && angle < 270 + angleEpsilon) { + result.x = 0.0 + result.y = 1.0 + } else if (angle > 270 + angleEpsilon && angle < 360 - angleEpsilon) { + result.x = 1.0 + result.y = 1.0 } else { - var bounds = self.bounds - let previous = bounds - bounds.origin = CGPoint(x: 0.0, y: velocity > 0.0 ? -bounds.height: bounds.height) - self.bounds = bounds - self.layer.animateBounds(from: previous, to: bounds, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, completion: { [weak self] _ in - self?.dismissedInteractively?() - }) + shouldHide = true + } + } else if !currentPosition.x.isZero && currentPosition.y.isZero { + if (angle > 90 + angleEpsilon && angle < 180 + angleEpsilon) { + result.x = 0.0 + result.y = 0.0 + } + else if (angle > 270 - angleEpsilon && angle < 360 - angleEpsilon) { + result.x = 1.0 + result.y = 1.0 + } + else if (angle > 180 + angleEpsilon && angle < 270 - angleEpsilon) { + result.x = 0.0 + result.y = 1.0 + } + else { + shouldHide = true + } + } else if currentPosition.x.isZero && !currentPosition.y.isZero { + if (angle > 90 - angleEpsilon && angle < 180 - angleEpsilon) { + result.x = 0.0 + result.y = 0.0 + } + else if (angle < angleEpsilon || angle > 270 + angleEpsilon) { + result.x = 1.0 + result.y = 1.0 + } + else if (angle > angleEpsilon && angle < 90 - angleEpsilon) { + result.x = 1.0 + result.y = 0.0 + } + else if (!shouldHide) { + shouldHide = true + } + } else if !currentPosition.x.isZero && !currentPosition.y.isZero { + if (angle > angleEpsilon && angle < 90 + angleEpsilon) { + result.x = 1.0 + result.y = 0.0 + } + else if (angle > 180 - angleEpsilon && angle < 270 - angleEpsilon) { + result.x = 0.0 + result.y = 1.0 + } + else if (angle > 90 + angleEpsilon && angle < 180 - angleEpsilon) { + result.x = 0.0 + result.y = 0.0 + } + else if (!shouldHide) { + shouldHide = true + } + } + } + + if result.x.isZero { + if result.y.isZero { + return .topLeft + } else { + return .bottomLeft + } + } else { + if result.y.isZero { + return .topRight + } else { + return .bottomRight + } + } + } + + @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) { + switch recognizer.state { + case .began: + let location = recognizer.location(in: self.view) + //let translation = recognizer.translation(in: self.view) + //location.x += translation.x + //location.y += translation.y + if let _ = self.incomingVideoNode, let outgoingVideoNode = self.outgoingVideoNode, outgoingVideoNode.frame.contains(location) { + self.minimizedVideoInitialPosition = outgoingVideoNode.position + } else { + self.minimizedVideoInitialPosition = nil + } + case .changed: + if let outgoingVideoNode = self.outgoingVideoNode, let minimizedVideoInitialPosition = self.minimizedVideoInitialPosition { + let translation = recognizer.translation(in: self.view) + let minimizedVideoDraggingPosition = CGPoint(x: minimizedVideoInitialPosition.x + translation.x, y: minimizedVideoInitialPosition.y + translation.y) + self.minimizedVideoDraggingPosition = minimizedVideoDraggingPosition + outgoingVideoNode.position = minimizedVideoDraggingPosition + } else { + let offset = recognizer.translation(in: self.view).y + var bounds = self.bounds + bounds.origin.y = -offset + self.bounds = bounds + } + case .cancelled, .ended: + if let outgoingVideoNode = self.outgoingVideoNode, let _ = self.minimizedVideoInitialPosition, let minimizedVideoDraggingPosition = self.minimizedVideoDraggingPosition { + self.minimizedVideoInitialPosition = nil + self.minimizedVideoDraggingPosition = nil + + if let (layout, navigationHeight) = self.validLayout { + self.outgoingVideoNodeCorner = self.nodeLocationForPosition(layout: layout, position: minimizedVideoDraggingPosition, velocity: recognizer.velocity(in: self.view)) + + let videoFrame = self.calculatePreviewVideoRect(layout: layout, navigationHeight: navigationHeight) + outgoingVideoNode.frame = videoFrame + outgoingVideoNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: minimizedVideoDraggingPosition.x - videoFrame.midX, y: minimizedVideoDraggingPosition.y - videoFrame.midY)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.5, delay: 0.0, initialVelocity: 0.0, damping: 110.0, removeOnCompletion: true, additive: true, completion: nil) + } + } else { + let velocity = recognizer.velocity(in: self.view).y + if abs(velocity) < 100.0 { + var bounds = self.bounds + let previous = bounds + bounds.origin = CGPoint() + self.bounds = bounds + self.layer.animateBounds(from: previous, to: bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + } else { + var bounds = self.bounds + let previous = bounds + bounds.origin = CGPoint(x: 0.0, y: velocity > 0.0 ? -bounds.height: bounds.height) + self.bounds = bounds + self.layer.animateBounds(from: previous, to: bounds, duration: 0.15, timingFunction: CAMediaTimingFunctionName.easeOut.rawValue, completion: { [weak self] _ in + self?.dismissedInteractively?() + }) + } } - case .cancelled: - var bounds = self.bounds - let previous = bounds - bounds.origin = CGPoint() - self.bounds = bounds - self.layer.animateBounds(from: previous, to: bounds, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) default: break } diff --git a/submodules/TelegramCallsUI/Sources/PresentationCall.swift b/submodules/TelegramCallsUI/Sources/PresentationCall.swift index 4332312a57..7ad6771995 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCall.swift @@ -190,7 +190,7 @@ public final class PresentationCallImpl: PresentationCall { private var sessionStateDisposable: Disposable? - private let statePromise = ValuePromise(PresentationCallState(state: .waiting, videoState: .notAvailable, remoteVideoState: .inactive), ignoreRepeated: true) + private let statePromise = ValuePromise() public var state: Signal { return self.statePromise.get() } @@ -264,7 +264,9 @@ public final class PresentationCallImpl: PresentationCall { self.isVideo = startWithVideo if self.isVideo { self.videoCapturer = OngoingCallVideoCapturer() - self.statePromise.set(PresentationCallState(state: .waiting, videoState: .activeOutgoing, remoteVideoState: .inactive)) + self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: .activeOutgoing, remoteVideoState: .inactive)) + } else { + self.statePromise.set(PresentationCallState(state: isOutgoing ? .waiting : .ringing, videoState: .notAvailable, remoteVideoState: .inactive)) } self.serializedData = serializedData @@ -457,7 +459,7 @@ public final class PresentationCallImpl: PresentationCall { switch sessionState.state { case .ringing: - presentationState = PresentationCallState(state: .ringing, videoState: .notAvailable, remoteVideoState: .inactive) + presentationState = PresentationCallState(state: .ringing, videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState) if previous == nil || previousControl == nil { if !self.reportedIncomingCall { self.reportedIncomingCall = true @@ -520,7 +522,7 @@ public final class PresentationCallImpl: PresentationCall { presentationState = PresentationCallState(state: .reconnecting(timestamp, reception, keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState) } } else { - presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: .notAvailable, remoteVideoState: .inactive) + presentationState = PresentationCallState(state: .connecting(keyVisualHash), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState) } } @@ -536,6 +538,7 @@ public final class PresentationCallImpl: PresentationCall { let ongoingContext = OngoingCallContext(account: account, callSessionManager: self.callSessionManager, internalId: self.internalId, proxyServer: proxyServer, auxiliaryServers: auxiliaryServers, initialNetworkType: self.currentNetworkType, updatedNetworkType: self.updatedNetworkType, serializedData: self.serializedData, dataSaving: dataSaving, derivedState: self.derivedState, key: key, isOutgoing: sessionState.isOutgoing, video: self.videoCapturer, connections: connections, maxLayer: maxLayer, version: version, allowP2P: allowsP2P, audioSessionActive: self.audioSessionActive.get(), logName: logName) self.ongoingContext = ongoingContext + ongoingContext.setIsMuted(self.isMutedValue) self.debugInfoValue.set(ongoingContext.debugInfo()) @@ -729,6 +732,10 @@ public final class PresentationCallImpl: PresentationCall { self.ongoingContext?.setEnableVideo(value) } + public func setOutgoingVideoIsPaused(_ isPaused: Bool) { + self.videoCapturer?.setIsVideoEnabled(!isPaused) + } + public func setCurrentAudioOutput(_ output: AudioSessionOutput) { guard self.currentAudioOutputValue != output else { return diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallAcceptButton.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call/CallAcceptButton.imageset/Contents.json new file mode 100644 index 0000000000..ac8c955846 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Call/CallAcceptButton.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_calls_accept.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallAcceptButton.imageset/ic_calls_accept.pdf b/submodules/TelegramUI/Images.xcassets/Call/CallAcceptButton.imageset/ic_calls_accept.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b8eb92df16d8a7e42eb771474268b13795644086 GIT binary patch literal 4239 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 YI>?v6sO66b4O4?Efh8pLkp|%Z02d^0pa1{> literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallMuteButton.imageset/CallMuteIcon@2x.png b/submodules/TelegramUI/Images.xcassets/Call/CallMuteButton.imageset/CallMuteIcon@2x.png deleted file mode 100644 index 07403f47ecd49cbb454aa5471d91618e438ef1be..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1108 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 diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallPhoneButton.imageset/CallPhoneIcon@3x.png b/submodules/TelegramUI/Images.xcassets/Call/CallPhoneButton.imageset/CallPhoneIcon@3x.png deleted file mode 100644 index 8237f34688c33f8d6e31e8ca8bc6db055bb6aabc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 844 zcmV-S1GD^zP)bEGeX-P4GrY%EHu(utHs8TPo6u z3^kH2N>L)z3KX=%MRZV!E;^}Y>eIy#tES@ae6t6c=X&PlefOXJ=Kub0yu5UDbaci5 zKSGHk2?Ob5l0!a4lwih8DW#Y(Q%n*0QrW;VLh$vRfIci>6Gthef+ji{z!3)M zppknNaD(hD z%3EB?Yl=xBQ0u6{9H5e3+=U+Qv!9vT)Y}v)_=7vr$6dAx)^_Gnz-Qc@F7gOdA2ya7 zZo00cueFuMj)#q-22Z4#rS^vn!NMS($^d5O*i)dg>BiIfLDqx{8PPN|S$M|6@xywP zq0#oxRdka+-VUmQ$w)Z?qs5)hQ}KUM8&4?X5@#@xhJj=fSxGE0L=#O6v8*DIWDKNX z;w+abrM?&JisU(Bd%u>0DUBfXR&?n@oYDAOCX%X2$E)p!drJYTY z(Caa4sBDzbFS$>Xmc=OI?HBWGl2#GP>zZ|G77>3Xwi64PSJ5KP0!NBG*5C10ML|CBDG`skir7ODvE`Zuju-J*p@=mKNPX4zFclxZ3An6R#9j(W zRcd~8SR+!o(xO}WgNwf^{ng?Dl;PRFtm8V+hi}vFX`~`LkIf~RC)k0RM z3;?QC``oDrMW_}^a!O$J3-j-si#O_uJ>E_UJcU=Pe74%8)aGxTPI4Mk3v6 z7$3%NuFx`p_Azbvje&HIabYiYoM)T5Gb+u4&tMC=)QvgHeb3a9x1@}ibe~XP|X7Aj)vwI(>+kc1o?*8}8oO2e^k3|enBJ;S4sIW9^ z%qT+Kr;shCmnuRW&HDH3L5K;uvj*Kli0kQYx{(pKTFP=J5drJl+h`wOV!nNc_{ADa zJA)9n{SjS^AQZFW9zwjQXlM)hi3nKh2kGMtYJpkC4}>^vspk=5*axzgiCKtZ*9{=V zl$Be(%pj_C`I+U$6q`OjL5Rzi_6Q+%`I+Uy3=weFQZ^AFezU~T4kN_1Xh_*G%Y~Uj z1nji5F@!j7C?)EM@6l!jA&8ARh7jLaU?sf}FRgWN5n`2(SuRY0rwB1DduPLH8o zL5TfCY}pC}8wd~;=7-MD!O+{e&^hM+yVzzZr$X7b86ifKV2&WfMMK$+5MzeYjS#Pr zU{)inFc;~_%y$9Fc(=EX(1^9HKX+gCj>C1*Q$ z4k0f2b)9qOMMuM&4@sKaIrF09V`Qs-r@e`w%%|AFV~8zj3?A*qO@z3SEo65dLt#%o pBW`10e~|USp3~nCi`mf(`3JSRpV?ng&gB3A002ovPDHLkV1i)&EjR!G diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallRouteSpeaker.imageset/CallRouteSpeaker@3x.png b/submodules/TelegramUI/Images.xcassets/Call/CallRouteSpeaker.imageset/CallRouteSpeaker@3x.png deleted file mode 100644 index 0026e6063dbcb732c02d6b26c4aad56e141f66fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1244 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallSpeakerButton.imageset/CallSpeakerIcon@2x.png b/submodules/TelegramUI/Images.xcassets/Call/CallSpeakerButton.imageset/CallSpeakerIcon@2x.png deleted file mode 100644 index 996959b567e71b6e8009847ee1e0d4c6864fd5a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1197 zcmV;e1XBBnP)&W%0&^xyGRf)8cQf`57)@&!(OM_o;AC(ce``8Yd@Xy@7e3iWM<7; zYmCW~@f>i1J|u4hYM%<)_>OYci7t+Bz6>0u(4r0zu;U#Cww&+Dadg~ z^s4Y!>mfLpF>I1CGc5XH02;U}4Ag!62qD4xN;XkPk*ZIZZ@v^9Ydp80EigLM$uhoN zM$K2%d$IHPyJdov$V$0Lf$xGCtg7DDs{Qwm z1i2(zKgN*9HV6V{3@?iIuIhTIsPj~{vc)Q67PqOVF5~%=s}K~-81_g+x?6SrL#tW& zuQo%zJIwppKv>hkMv7XlI$r|7HHy{M>{(ePV8(C|fTOB5S;mg@iDe6-0!v5nf$Hhu zh3U5yEr$yLY#MK*09L8m9(x7O#{it>fvCV% z+Y1b;0r*KfQ>g;r0z;z$dkcVHv=T)2WgSUHTLHioMnnZxCeFJn+B^UnwWFNHqK%FU z>;(z3Jrr#&04XgcWT9wdqn@#3s2OYGiKxJ418_!NV-)~g%FY^FI6v)0J}otW!sNiRJtlICEiC?Mq8D551Fq`ZS?hDQ`Kpr zP6*b!SmQKfWx!|JDQJu_oCKhrUOrJj6}S=AT*~?{{)DY%+}ydSuyp*rDt2p_{#c1r zF=6^+y`*o^U`ybgS$eM{OJ#>TK+z$V4(zVa!-s)-j?K4%4NM7+S#%YI$F zu+sVeMg3UM7S^EV=JU9C%~$%eqB34sc~YGnHE}aafiv_s|2O*wPpc1$szc8Z00000 LNkvXXu0mjfX_+vg diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallSpeakerButton.imageset/CallSpeakerIcon@3x.png b/submodules/TelegramUI/Images.xcassets/Call/CallSpeakerButton.imageset/CallSpeakerIcon@3x.png deleted file mode 100644 index 345c9a8f3b0aa529535a48be49ad4fe55c10a1c4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1916 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallSwitchCameraButton.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call/CallSwitchCameraButton.imageset/Contents.json index 0dd1dc8086..389dd744c8 100644 --- a/submodules/TelegramUI/Images.xcassets/Call/CallSwitchCameraButton.imageset/Contents.json +++ b/submodules/TelegramUI/Images.xcassets/Call/CallSwitchCameraButton.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "Video.pdf", + "filename" : "ic_calls_cameraflip.pdf", "idiom" : "universal" } ], diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallSwitchCameraButton.imageset/Video.pdf b/submodules/TelegramUI/Images.xcassets/Call/CallSwitchCameraButton.imageset/Video.pdf deleted file mode 100644 index 71f35844b314811796ff54eae77d534f60fe58d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 174248 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

joXpotMGH#dfcjK@tN##A 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 bJ?xH7@AK;4L4O|?F9$yg4UMF-6w3bqfh2nP diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallSwitchCameraButton.imageset/ic_calls_cameraflip.pdf b/submodules/TelegramUI/Images.xcassets/Call/CallSwitchCameraButton.imageset/ic_calls_cameraflip.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4f0db952ed9eb776c9e9c9f15dbd67db99dc011e GIT binary patch literal 4333 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 literal 0 HcmV?d00001 diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallTitleLogo.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call/CallTitleLogo.imageset/Contents.json new file mode 100644 index 0000000000..9796eb0aa2 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Call/CallTitleLogo.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "ic_calls_tlogo.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Call/CallTitleLogo.imageset/ic_calls_tlogo.pdf b/submodules/TelegramUI/Images.xcassets/Call/CallTitleLogo.imageset/ic_calls_tlogo.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6da8161ecb7273485822bfe806536ccb5e417c2a GIT binary patch literal 4186 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 V_M!LkN1+i)FlDfWgr1Q;_+K2+G_C*u literal 0 HcmV?d00001 diff --git a/submodules/TelegramVoip/Sources/OngoingCallContext.swift b/submodules/TelegramVoip/Sources/OngoingCallContext.swift index e981f2db65..c37830a41b 100644 --- a/submodules/TelegramVoip/Sources/OngoingCallContext.swift +++ b/submodules/TelegramVoip/Sources/OngoingCallContext.swift @@ -305,6 +305,10 @@ public final class OngoingCallVideoCapturer { public func makeOutgoingVideoView(completion: @escaping (UIView?) -> Void) { self.impl.makeOutgoingVideoView(completion) } + + public func setIsVideoEnabled(_ value: Bool) { + self.impl.setIsVideoEnabled(value) + } } extension OngoingCallThreadLocalContextWebrtc: OngoingCallThreadLocalContextProtocol { diff --git a/submodules/TgVoipWebrtc/Impl/CodecsApple.h b/submodules/TgVoipWebrtc/Impl/CodecsApple.h index 442df44018..a09d444731 100644 --- a/submodules/TgVoipWebrtc/Impl/CodecsApple.h +++ b/submodules/TgVoipWebrtc/Impl/CodecsApple.h @@ -13,6 +13,8 @@ namespace TGVOIP_NAMESPACE { class VideoCapturerInterface { public: virtual ~VideoCapturerInterface(); + + virtual void setIsEnabled(bool isEnabled) = 0; }; void configurePlatformAudio(); diff --git a/submodules/TgVoipWebrtc/Impl/CodecsApple.mm b/submodules/TgVoipWebrtc/Impl/CodecsApple.mm index 71e4d06a24..e3fad7c4e8 100644 --- a/submodules/TgVoipWebrtc/Impl/CodecsApple.mm +++ b/submodules/TgVoipWebrtc/Impl/CodecsApple.mm @@ -112,6 +112,10 @@ [_videoCapturer stopCapture]; } +- (void)setIsEnabled:(bool)isEnabled { + [_videoCapturer setIsEnabled:isEnabled]; +} + @end @interface VideoCapturerInterfaceImplHolder : NSObject @@ -153,6 +157,16 @@ public: }); } + virtual void setIsEnabled(bool isEnabled) { + VideoCapturerInterfaceImplHolder *implReference = _implReference; + dispatch_async(dispatch_get_main_queue(), ^{ + if (implReference.reference != nil) { + VideoCapturerInterfaceImplReference *reference = (__bridge VideoCapturerInterfaceImplReference *)implReference.reference; + [reference setIsEnabled:isEnabled]; + } + }); + } + private: rtc::scoped_refptr _source; VideoCapturerInterfaceImplHolder *_implReference; diff --git a/submodules/TgVoipWebrtc/Impl/TgVoip.h b/submodules/TgVoipWebrtc/Impl/TgVoip.h index 8f21482708..ccd7a485d2 100644 --- a/submodules/TgVoipWebrtc/Impl/TgVoip.h +++ b/submodules/TgVoipWebrtc/Impl/TgVoip.h @@ -138,6 +138,7 @@ public: virtual ~TgVoipVideoCaptureInterface(); virtual void switchCamera() = 0; + virtual void setIsVideoEnabled(bool isVideoEnabled) = 0; virtual void setVideoOutput(std::shared_ptr> sink) = 0; }; diff --git a/submodules/TgVoipWebrtc/Impl/VideoCameraCapturer.h b/submodules/TgVoipWebrtc/Impl/VideoCameraCapturer.h index 5c8c24e9c2..f9f3e63f09 100644 --- a/submodules/TgVoipWebrtc/Impl/VideoCameraCapturer.h +++ b/submodules/TgVoipWebrtc/Impl/VideoCameraCapturer.h @@ -17,6 +17,7 @@ - (void)startCaptureWithDevice:(AVCaptureDevice *)device format:(AVCaptureDeviceFormat *)format fps:(NSInteger)fps; - (void)stopCapture; +- (void)setIsEnabled:(bool)isEnabled; @end diff --git a/submodules/TgVoipWebrtc/Impl/VideoCameraCapturer.mm b/submodules/TgVoipWebrtc/Impl/VideoCameraCapturer.mm index 723145155f..8149f87c45 100644 --- a/submodules/TgVoipWebrtc/Impl/VideoCameraCapturer.mm +++ b/submodules/TgVoipWebrtc/Impl/VideoCameraCapturer.mm @@ -39,6 +39,9 @@ static webrtc::ObjCVideoTrackSource *getObjCVideoSource(const rtc::scoped_refptr UIDeviceOrientation _orientation; void (^_isActiveUpdated)(bool); + bool _isActiveValue; + bool _inForegroundValue; + bool _isPaused; } @end @@ -49,6 +52,9 @@ static webrtc::ObjCVideoTrackSource *getObjCVideoSource(const rtc::scoped_refptr self = [super init]; if (self != nil) { _source = source; + _isActiveValue = true; + _inForegroundValue = true; + _isPaused = false; _isActiveUpdated = [isActiveUpdated copy]; if (![self setupCaptureSession:[[AVCaptureSession alloc] init]]) { @@ -124,6 +130,11 @@ static webrtc::ObjCVideoTrackSource *getObjCVideoSource(const rtc::scoped_refptr [self stopCaptureWithCompletionHandler:nil]; } +- (void)setIsEnabled:(bool)isEnabled { + _isPaused = !isEnabled; + [self updateIsActiveValue]; +} + - (void)startCaptureWithDevice:(AVCaptureDevice *)device format:(AVCaptureDeviceFormat *)format fps:(NSInteger)fps @@ -253,7 +264,9 @@ static webrtc::ObjCVideoTrackSource *getObjCVideoSource(const rtc::scoped_refptr RTCVideoFrame *videoFrame = [[RTCVideoFrame alloc] initWithBuffer:rtcPixelBuffer rotation:_rotation timeStampNs:timeStampNs]; - getObjCVideoSource(_source)->OnCapturedFrame(videoFrame); + if (!_isPaused) { + getObjCVideoSource(_source)->OnCapturedFrame(videoFrame); + } } - (void)captureOutput:(AVCaptureOutput *)captureOutput @@ -316,15 +329,23 @@ static webrtc::ObjCVideoTrackSource *getObjCVideoSource(const rtc::scoped_refptr _hasRetriedOnFatalError = NO; }]; - if (_isActiveUpdated) { - _isActiveUpdated(true); - } + _inForegroundValue = true; + [self updateIsActiveValue]; } - (void)handleCaptureSessionDidStopRunning:(NSNotification *)notification { RTCLog(@"Capture session stopped."); - if (_isActiveUpdated) { - _isActiveUpdated(false); + _inForegroundValue = false; + [self updateIsActiveValue]; +} + +- (void)updateIsActiveValue { + bool isActive = _inForegroundValue && !_isPaused; + if (isActive != _isActiveValue) { + _isActiveValue = isActive; + if (_isActiveUpdated) { + _isActiveUpdated(_isActiveValue); + } } } diff --git a/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.h b/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.h index 199de612a8..324c61b181 100644 --- a/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.h +++ b/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.h @@ -18,6 +18,7 @@ public: ~TgVoipVideoCaptureInterfaceObject(); void switchCamera(); + void setIsVideoEnabled(bool isVideoEnabled); void setVideoOutput(std::shared_ptr> sink); void setIsActiveUpdated(std::function isActiveUpdated); @@ -29,6 +30,7 @@ private: std::shared_ptr> _currentSink; std::function _isActiveUpdated; bool _useFrontCamera; + bool _isVideoEnabled; }; class TgVoipVideoCaptureInterfaceImpl : public TgVoipVideoCaptureInterface { @@ -37,6 +39,7 @@ public: virtual ~TgVoipVideoCaptureInterfaceImpl(); virtual void switchCamera(); + virtual void setIsVideoEnabled(bool isVideoEnabled); virtual void setVideoOutput(std::shared_ptr> sink); public: diff --git a/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.mm b/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.mm index 4e4b269175..e66e8c4a7a 100644 --- a/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.mm +++ b/submodules/TgVoipWebrtc/Impl/VideoCaptureInterfaceImpl.mm @@ -10,6 +10,7 @@ namespace TGVOIP_NAMESPACE { TgVoipVideoCaptureInterfaceObject::TgVoipVideoCaptureInterfaceObject() { _useFrontCamera = true; + _isVideoEnabled = true; _videoSource = makeVideoSource(Manager::getMediaThread(), MediaManager::getWorkerThread()); //this should outlive the capturer _videoCapturer = makeVideoCapturer(_videoSource, _useFrontCamera, [this](bool isActive) { @@ -33,6 +34,13 @@ void TgVoipVideoCaptureInterfaceObject::switchCamera() { } }); } + +void TgVoipVideoCaptureInterfaceObject::setIsVideoEnabled(bool isVideoEnabled) { + if (_isVideoEnabled != isVideoEnabled) { + _isVideoEnabled = isVideoEnabled; + _videoCapturer->setIsEnabled(isVideoEnabled); + } +} void TgVoipVideoCaptureInterfaceObject::setVideoOutput(std::shared_ptr> sink) { if (_currentSink != nullptr) { @@ -66,6 +74,12 @@ void TgVoipVideoCaptureInterfaceImpl::switchCamera() { impl->switchCamera(); }); } + +void TgVoipVideoCaptureInterfaceImpl::setIsVideoEnabled(bool isVideoEnabled) { + _impl->perform([isVideoEnabled](TgVoipVideoCaptureInterfaceObject *impl) { + impl->setIsVideoEnabled(isVideoEnabled); + }); +} void TgVoipVideoCaptureInterfaceImpl::setVideoOutput(std::shared_ptr> sink) { _impl->perform([sink](TgVoipVideoCaptureInterfaceObject *impl) { diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h index edfee3acb4..b15f82773f 100644 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoip/OngoingCallThreadLocalContext.h @@ -83,6 +83,7 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) { - (instancetype _Nonnull)init; - (void)switchVideoCamera; +- (void)setIsVideoEnabled:(bool)isVideoEnabled; - (void)makeOutgoingVideoView:(void (^_Nonnull)(UIView * _Nullable))completion; diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index 19e78dcb29..833505792b 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -41,6 +41,10 @@ using namespace TGVOIP_NAMESPACE; _interface->switchCamera(); } +- (void)setIsVideoEnabled:(bool)isVideoEnabled { + _interface->setIsVideoEnabled(isVideoEnabled); +} + - (std::shared_ptr)getInterface { return _interface; } From 9c5305c4e344dd2785b1fd7656409fc777e9b643 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 3 Jul 2020 23:36:17 +0400 Subject: [PATCH 4/4] Fix call buttons --- .../Sources/CallControllerButtonsNode.swift | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/submodules/TelegramCallsUI/Sources/CallControllerButtonsNode.swift b/submodules/TelegramCallsUI/Sources/CallControllerButtonsNode.swift index 2b2505efa3..769d077f3c 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerButtonsNode.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerButtonsNode.swift @@ -37,6 +37,7 @@ private enum ButtonDescription: Equatable { } enum SoundOutput { + case builtin case speaker case bluetooth } @@ -158,7 +159,9 @@ final class CallControllerButtonsNode: ASDisplayNode { let soundOutput: ButtonDescription.SoundOutput switch speakerMode { - case .none, .builtin, .speaker: + case .none, .builtin: + soundOutput = .builtin + case .speaker: soundOutput = .speaker case .headphones: soundOutput = .bluetooth @@ -172,7 +175,6 @@ final class CallControllerButtonsNode: ASDisplayNode { topButtons.append(.mute(self.isMuted)) topButtons.append(.switchCamera) case .notAvailable: - topButtons.append(.enableCamera(!self.isCameraPaused)) topButtons.append(.mute(self.isMuted)) topButtons.append(.soundOutput(soundOutput)) } @@ -208,10 +210,12 @@ final class CallControllerButtonsNode: ASDisplayNode { let soundOutput: ButtonDescription.SoundOutput switch speakerMode { - case .none, .builtin, .speaker: + case .none, .builtin: + soundOutput = .builtin + case .speaker: soundOutput = .speaker case .headphones: - soundOutput = .bluetooth + soundOutput = .builtin case .bluetooth: soundOutput = .bluetooth } @@ -222,7 +226,6 @@ final class CallControllerButtonsNode: ASDisplayNode { topButtons.append(.mute(isMuted)) topButtons.append(.switchCamera) case .notAvailable: - topButtons.append(.enableCamera(!self.isCameraPaused)) topButtons.append(.mute(isMuted)) topButtons.append(.soundOutput(soundOutput)) } @@ -293,14 +296,18 @@ final class CallControllerButtonsNode: ASDisplayNode { buttonText = strings.Call_Flip case let .soundOutput(value): let image: CallControllerButtonItemNode.Content.Image + var isFilled = false switch value { + case .builtin: + image = .speaker case .speaker: image = .speaker + isFilled = true case .bluetooth: image = .bluetooth } buttonContent = CallControllerButtonItemNode.Content( - appearance: .blurred(isFilled: false), + appearance: .blurred(isFilled: isFilled), image: image ) buttonText = strings.Call_Speaker