diff --git a/Conference/Stories/ConferenceCall/ConferenceCallViewController.swift b/Conference/Stories/ConferenceCall/ConferenceCallViewController.swift index dc3c07e..7bb1e86 100644 --- a/Conference/Stories/ConferenceCall/ConferenceCallViewController.swift +++ b/Conference/Stories/ConferenceCall/ConferenceCallViewController.swift @@ -27,21 +27,75 @@ final class ConferenceCallViewController: UIViewController, AudioDeviceSelecting super.viewDidLoad() muteButton.state = .initial(model: CallOptionButtonModels.mute) - muteButton.touchUpHandler = muteHandler(_:) + muteButton.touchUpHandler = { [weak self] button in + guard let self = self else { return } + do { + try self.manageConference.mute(!self.muted) + self.muted.toggle() + button.state = self.muted ? .selected : .normal + } catch (let error) { + AlertHelper.showError(message: error.localizedDescription, on: self) + } + } chooseAudioButton.state = .initial(model: CallOptionButtonModels.chooseAudio) - chooseAudioButton.touchUpHandler = chooseAudioHandler(_:) + chooseAudioButton.touchUpHandler = { [weak self] button in + self?.showAudioDevicesActionSheet(sourceView: button) + } switchCameraButton.state = .initial(model: CallOptionButtonModels.switchCamera) - switchCameraButton.touchUpHandler = switchCameraHandler(_:) + switchCameraButton.touchUpHandler = { [weak self] _ in + self?.manageConference.switchCamera() + } videoButton.state = .initial(model: CallOptionButtonModels.video) - videoButton.touchUpHandler = videoHandler(_:) + videoButton.touchUpHandler = { [weak self] button in + guard let self = self else { return } + let previousState = button.state + button.state = .unavailable + self.manageConference.sendVideo(!self.video) { [weak self] error in + guard let self = self else { return } + if let error = error { + AlertHelper.showError(message: error.localizedDescription, on: self) + button.state = previousState + } else { + self.video.toggle() + self.conferenceView.hideVideoRenderer(!self.video, for: myId) + button.state = self.video ? .normal : .selected + } + } + } exitButton.state = .initial(model: CallOptionButtonModels.exit) - exitButton.touchUpHandler = exitHandler(_:) + exitButton.touchUpHandler = { [weak self] button in + guard let self = self else { return } + button.state = .unavailable + self.leftConference = true + self.leaveConference.execute { error in + if let error = error { + AlertHelper.showError(message: error.localizedDescription, on: self) + self.leftConference = false + button.state = .normal + } else { + self.dismiss(animated: true) + } + } + } - manageConference.conferenceDisconnectedHandler = disconnectedHandler(error:) + manageConference.conferenceDisconnectedHandler = { [weak self] error in + guard let self = self, !self.leftConference else { return } + self.leaveConference.execute { error in + if let error = error { + print("Got an error while leaving conference - \(error.localizedDescription)") + } + } + AlertHelper.showAlert( + title: "Disconnected", + message: "You've been disconnected \(error != nil ? error!.localizedDescription : "")", + actions: [UIAlertAction(title: "Close", style: .default) { _ in self.dismiss(animated: true) }], + on: self + ) + } manageConference.userAddedHandler = conferenceView.addParticipant(withID:displayName:) manageConference.userUpdatedHandler = conferenceView.updateParticipant(withID:displayName:) @@ -54,69 +108,6 @@ final class ConferenceCallViewController: UIViewController, AudioDeviceSelecting conferenceView.updateParticipant(withID: myId, displayName: "\(name ?? "") (you)") } - private func disconnectedHandler(error: Error?) { - if (self.leftConference) { return } - leaveConference.execute { error in - if let error = error { - print("Got an error while leaving conference - \(error.localizedDescription)") - } - } - AlertHelper.showAlert( - title: "Disconnected", - message: "You've been disconnected \(error != nil ? error!.localizedDescription : "")", - actions: [UIAlertAction(title: "Close", style: .default) { _ in self.dismiss(animated: true) }], - on: self - ) - } - - private func muteHandler(_ button: CallOptionButton) { - do { - try manageConference.mute(!muted) - muted.toggle() - button.state = muted ? .selected : .normal - } catch (let error) { - AlertHelper.showError(message: error.localizedDescription, on: self) - } - } - - private func chooseAudioHandler(_ button: CallOptionButton) { - showAudioDevicesActionSheet(sourceView: button) - } - - private func switchCameraHandler(_ button: CallOptionButton) { - manageConference.switchCamera() - } - - private func videoHandler(_ button: CallOptionButton) { - let previousState = button.state - button.state = .unavailable - manageConference.sendVideo(!video) { [weak self] error in - guard let self = self else { return } - if let error = error { - AlertHelper.showError(message: error.localizedDescription, on: self) - button.state = previousState - } else { - self.video.toggle() - self.conferenceView.hideVideoRenderer(!self.video, for: myId) - button.state = self.video ? .normal : .selected - } - } - } - - private func exitHandler(_ button: CallOptionButton) { - button.state = .unavailable - leftConference = true - leaveConference.execute { error in - if let error = error { - AlertHelper.showError(message: error.localizedDescription, on: self) - self.leftConference = false - button.state = .normal - } else { - self.dismiss(animated: true) - } - } - } - private func makeFormattedString(from device: VIAudioDevice, isCurrent: Bool) -> String { let formattedString = String(describing: device).replacingOccurrences(of: "VIAudioDevice", with: "") return isCurrent ? "\(formattedString) (Current)" : formattedString diff --git a/InAppScreenSharing/AppDelegate.swift b/InAppScreenSharing/AppDelegate.swift new file mode 100644 index 0000000..5a4f87e --- /dev/null +++ b/InAppScreenSharing/AppDelegate.swift @@ -0,0 +1,52 @@ +/* +* Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. +*/ + +import UIKit +import VoxImplantSDK + +fileprivate let client: VIClient = VIClient(delegateQueue: DispatchQueue.main) +fileprivate let authService: AuthService = AuthService(client) +fileprivate let callManager: CallManager = CallManager(client, authService) +fileprivate let storyAssembler: StoryAssembler = StoryAssembler(authService: authService, callManager: callManager) + +@UIApplicationMain +final class AppDelegate: UIResponder, UIApplicationDelegate, Loggable { + var window: UIWindow? + var appName: String { "InAppScreenSharing" } + + override init() { + super.init() + + configureDefaultLogging() + } + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + UIApplication.shared.isIdleTimerDisabled = true + + window = UIWindow(frame: UIScreen.main.bounds) + window?.rootViewController = storyAssembler.login + window?.makeKeyAndVisible() + + return true + } + + // MARK: - AppLifeCycleDelegate - + func applicationWillResignActive(_ application: UIApplication) { + (window?.rootViewController?.toppestViewController as? AppLifeCycleDelegate)?.applicationWillResignActive(application) + UIApplication.shared.isIdleTimerDisabled = false + } + + func applicationDidEnterBackground(_ application: UIApplication) { + (window?.rootViewController?.toppestViewController as? AppLifeCycleDelegate)?.applicationDidEnterBackground(application) + } + + func applicationWillEnterForeground(_ application: UIApplication) { + (window?.rootViewController?.toppestViewController as? AppLifeCycleDelegate)?.applicationWillEnterForeground(application) + } + + func applicationDidBecomeActive(_ application: UIApplication) { + (window?.rootViewController?.toppestViewController as? AppLifeCycleDelegate)?.applicationDidBecomeActive(application) + } +} + diff --git a/InAppScreenSharing/README.md b/InAppScreenSharing/README.md new file mode 100644 index 0000000..301df10 --- /dev/null +++ b/InAppScreenSharing/README.md @@ -0,0 +1,99 @@ +# Voximplant InApp Screen Sharing Demo (iOS) + +This demo demonstrates basic in-app screen sharing functionality of the Voximplant iOS SDK. The application supports video calls between this iOS app and other apps that use any Voximplant SDK. + +#### Features +The application is able to: +- log in to the Voximplant Cloud +- auto login using access tokens +- make an video call +- receive an incoming call +- switch camera during a call +- enable/disable video during a call +- enable/disable screen sharing during a call +- auto reconnect/relogin + + +## Getting started + +To get started, you'll need to [register](https://voximplant.com) a free Voximplant developer account. + +You'll need the following: +- Voximplant application +- two Voximplant users +- VoxEngine scenario +- routing setup +- VoIP services certificate for push notifications. Follow [this tutorial](https://voximplant.com/docs/references/iossdk/push-notifications-for-ios) to upload the certificate to the Voximplant Control Panel + +### Automatic +We've implemented a special template to enable you to quickly use the demo – just +install [SDK tutorial](https://manage.voximplant.com/marketplace/sdk_tutorial) from our marketplace: +![marketplace](Screenshots/market.png) + +### Manual + +You can set up it manually using our [quickstart guide](https://voximplant.com/docs/references/articles/quickstart) and tutorials + +#### VoxEngine scenario example: + ``` + require(Modules.PushService); + VoxEngine.addEventListener(AppEvents.CallAlerting, (e) => { + const newCall = VoxEngine.callUserDirect( + e.call, + e.destination, + e.callerid, + e.displayName, + null + ); + VoxEngine.easyProcess(e.call, newCall, ()=>{}, true); + }); + ``` + +## Installing + +1. Clone this repo + +1. Run `$ pod install` in the repo folder + +1. Open the `Swift.xcworkspace` workspace + +1. Target InAppScreenSharing and build the project using Xcode + +## Usage + +### User login +![login](Screenshots/login.png) + +Log in using: +* Voximplant user name in the format `user@app.account` +* password + +See the following classes for code details: +* [AuthService.swift](Services/AuthService.swift) +* [LoginViewController.swift](Stories/Login/LoginViewController.swift) + +### Make or receive calls +![call](Screenshots/call.png) + +Enter a Voximplant user name to the input field and press "Call" button to make a call. + +See the following classes for code details: +- [CallManager.swift](Services/CallManager.swift) +- [MainViewController.swift](Stories/Main/MainViewController.swift) +- [IncomingCallViewController.swift](Stories/Main/IncomingCallViewController.swift) + +### Call controls +![inCall](Screenshots/inCall.png) + +Enable/disable video or screen sharing during a call. + +See the following classes for code details: +- [CallViewController.swift](Stories/Call/CallViewController.swift) +* [CallManager.swift](Services/CallManager.swift) + + +## Useful links +1. [Getting started](https://voximplant.com/docs/introduction) +2. [Voximplant iOS SDK reference](https://voximplant.com/docs/references/iossdk) +3. [Installing the Voximplant iOS SDK](https://voximplant.com/docs/introduction/integration/adding_sdks/installing/ios_sdk) +4. [HowTo's](https://voximplant.com/docs/howtos) diff --git a/InAppScreenSharing/Resources/Info.plist b/InAppScreenSharing/Resources/Info.plist new file mode 100644 index 0000000..8163ac4 --- /dev/null +++ b/InAppScreenSharing/Resources/Info.plist @@ -0,0 +1,60 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSRequiresIPhoneOS + + NSCameraUsageDescription + Camera is needed for video calls + NSMicrophoneUsageDescription + Microphone is needed for calls + UIBackgroundModes + + audio + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresFullScreen + + UIStatusBarStyle + UIStatusBarStyleLightContent + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/InAppScreenSharing/Screenshots/call.png b/InAppScreenSharing/Screenshots/call.png new file mode 100644 index 0000000..9fa34ec Binary files /dev/null and b/InAppScreenSharing/Screenshots/call.png differ diff --git a/InAppScreenSharing/Screenshots/inCall.png b/InAppScreenSharing/Screenshots/inCall.png new file mode 100644 index 0000000..baa62c8 Binary files /dev/null and b/InAppScreenSharing/Screenshots/inCall.png differ diff --git a/InAppScreenSharing/Screenshots/login.png b/InAppScreenSharing/Screenshots/login.png new file mode 100644 index 0000000..fdd2ad8 Binary files /dev/null and b/InAppScreenSharing/Screenshots/login.png differ diff --git a/ScreenSharing/Screenshots/market.png b/InAppScreenSharing/Screenshots/market.png similarity index 100% rename from ScreenSharing/Screenshots/market.png rename to InAppScreenSharing/Screenshots/market.png diff --git a/InAppScreenSharing/Services/AuthService.swift b/InAppScreenSharing/Services/AuthService.swift new file mode 100644 index 0000000..1b7a109 --- /dev/null +++ b/InAppScreenSharing/Services/AuthService.swift @@ -0,0 +1,167 @@ +/* +* Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. +*/ + +import VoxImplantSDK + +final class AuthService: NSObject, VIClientSessionDelegate { + private typealias ConnectCompletion = (Error?) -> Void + private typealias DisconnectCompletion = () -> Void + typealias LoginCompletion = (Error?) -> Void + typealias LogoutCompletion = () -> Void + + private let client: VIClient + private var connectCompletion: ConnectCompletion? + private var disconnectCompletion: DisconnectCompletion? + var possibleToLogin: Bool { Tokens.areExist && !Tokens.areExpired } + + init(_ client: VIClient) { + self.client = client + super.init() + client.sessionDelegate = self + } + + @UserDefault("lastFullUsername") + var loggedInUser: String? + var loggedInUserDisplayName: String? + var isLoggedIn: Bool { state == .loggedIn } + private var state: VIClientState { client.clientState } + + func login(user: String, password: String, _ completion: @escaping LoginCompletion) { + connect() { [weak self] error in + if let error = error { + completion(error) + return + } + + self?.client.login(withUser: user, password: password, + success: { (displayUserName: String, tokens: VIAuthParams) in + Tokens.update(with: tokens) + self?.loggedInUser = user + self?.loggedInUserDisplayName = displayUserName + completion(nil) + }, + failure: { (error: Error) in + completion(error) + } + ) + } + } + + func loginWithAccessToken(_ completion: @escaping LoginCompletion) { + guard let user = self.loggedInUser else { + let error = AuthError.loginDataNotFound + completion(error) + return + } + + if client.clientState == .loggedIn, + loggedInUserDisplayName != nil, + !Tokens.areExpired + { + completion(nil) + return + } + + connect() { [weak self] error in + if let error = error { + completion(error) + return + } + + self?.updateAccessTokenIfNeeded(for: user) { + [weak self] + (result: Result) in + + switch result { + case let .failure(error): + completion(error) + return + + case let .success(accessKey): + self?.client.login(withUser: user, token: accessKey.token, + success: { (displayUserName: String, tokens: VIAuthParams) in + Tokens.update(with: tokens) + self?.loggedInUser = user + self?.loggedInUserDisplayName = displayUserName + completion(nil) + }, + failure: { (error: Error) in + completion(error) + } + ) + } + } + } + } + + func logout(_ completion: @escaping LogoutCompletion) { + Tokens.clear() + self.loggedInUser = nil + self.loggedInUserDisplayName = nil + self.disconnect(completion) + } + + private func updateAccessTokenIfNeeded( + for user: String, + _ completion: @escaping (Result)->Void + ) { + guard let accessToken = Tokens.access, + let refreshToken = Tokens.refresh else { + completion(.failure(AuthError.loginDataNotFound)) + return + } + + if accessToken.isExpired { + client.refreshToken(withUser: user, token: refreshToken.token) + { (authParams: VIAuthParams?, error: Error?) in + guard let tokens = authParams + else { + completion(.failure(error!)) + return + } + Tokens.update(with: tokens) + completion(.success(Tokens.access!)) + } + } else { + completion(.success(accessToken)) + } + } + + private func connect(_ completion: @escaping ConnectCompletion) { + if client.clientState == .disconnected || + client.clientState == .connecting + { + connectCompletion = completion + client.connect() + + } else { + completion(nil) + } + } + + private func disconnect(_ completion: @escaping DisconnectCompletion) { + if client.clientState == .disconnected { + completion() + } else { + disconnectCompletion = completion + client.disconnect() + } + } + + // MARK: - VIClientSessionDelegate - + func clientSessionDidConnect(_ client: VIClient) { + connectCompletion?(nil) + connectCompletion = nil + } + + func client(_ client: VIClient, sessionDidFailConnectWithError error: Error) { + connectCompletion?(error) + connectCompletion = nil + } + + func clientSessionDidDisconnect(_ client: VIClient) { + disconnectCompletion?() + disconnectCompletion = nil + } +} diff --git a/InAppScreenSharing/Services/CallManager.swift b/InAppScreenSharing/Services/CallManager.swift new file mode 100644 index 0000000..5fe2524 --- /dev/null +++ b/InAppScreenSharing/Services/CallManager.swift @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + */ + +import VoxImplantSDK + +final class CallManager: + NSObject, + VIClientCallManagerDelegate, + VIAudioManagerDelegate, + VICallDelegate, + VIEndpointDelegate +{ + typealias VideoStreamAdded = (_ local: Bool, (VIVideoRendererView) -> Void) -> Void + typealias VideoStreamRemoved = (_ local: Bool) -> Void + + struct CallWrapper { + fileprivate let call: VICall + let callee: String + var displayName: String? + var state: CallState = .connecting + let direction: CallDirection + var sendingVideo: Bool = true + var sharingScreen: Bool = false + + enum CallDirection { + case incoming + case outgoing + } + + enum CallState: Equatable { + case connecting + case connected + case ended (reason: CallEndReason) + + enum CallEndReason: Equatable { + case disconnected + case failed (message: String) + } + } + } + + private let client: VIClient + private let authService: AuthService + + // Voximplant SDK supports multiple calls at the same time, however + // this demo app demonstrates only one managed call at the moment, + // so it rejects new incoming call if there is already a call. + private(set) var managedCallWrapper: CallWrapper? { + willSet { + if managedCallWrapper?.call != newValue?.call { + managedCallWrapper?.call.remove(self) + } + } + didSet { + if let newValue = managedCallWrapper { + callObserver?(newValue) + } + if managedCallWrapper?.call != oldValue?.call { + managedCallWrapper?.call.add(self) + } + } + } + var hasManagedCall: Bool { managedCallWrapper != nil } + private var hasNoManagedCalls: Bool { !hasManagedCall } + + var callObserver: ((CallWrapper) -> Void)? + var didReceiveIncomingCall: (() -> Void)? + var videoStreamAddedHandler: VideoStreamAdded? + var videoStreamRemovedHandler: VideoStreamRemoved? + + private let callSettings: VICallSettings = { + let settings = VICallSettings() + settings.videoFlags = VIVideoFlags.videoFlags(receiveVideo: true, sendVideo: true) + return settings + }() + + required init(_ client: VIClient, _ authService: AuthService) { + self.client = client + self.authService = authService + + super.init() + + VIAudioManager.shared().delegate = self + self.client.callManagerDelegate = self + } + + func makeOutgoingCall(to contact: String) throws { + guard authService.isLoggedIn else { throw AuthError.notLoggedIn } + guard hasNoManagedCalls else { throw CallError.alreadyManagingACall } + + if let call = client.call(contact, settings: callSettings) { + managedCallWrapper = CallWrapper(call: call, callee: contact, direction: .outgoing) + } else { + throw CallError.internalError + } + } + + func startOutgoingCall() throws { + guard let call = managedCallWrapper?.call else { throw CallError.hasNoActiveCall } + guard authService.isLoggedIn else { throw AuthError.notLoggedIn } + + if headphonesNotConnected { + self.selectIfAvailable(.speaker, from: VIAudioManager.shared().availableAudioDevices()) + } + call.start() + } + + func makeIncomingCallActive() throws { + guard let call = managedCallWrapper?.call else { throw CallError.hasNoActiveCall } + guard authService.isLoggedIn else { throw AuthError.notLoggedIn } + + if headphonesNotConnected { + selectIfAvailable(.speaker, from: VIAudioManager.shared().availableAudioDevices()) + } + call.answer(with: callSettings) + } + + func changeSendVideo(_ completion: ((Error?) -> Void)? = nil) { + guard let wrapper = managedCallWrapper else { completion?(CallError.hasNoActiveCall); return } + + wrapper.call.setSendVideo(!wrapper.sendingVideo) { [weak self] error in + if let error = error { + completion?(error) + return + } + + self?.managedCallWrapper?.sendingVideo.toggle() + self?.managedCallWrapper?.sharingScreen = false + completion?(nil) + } + } + + func changeShareScreen(_ completion: ((Error?) -> Void)? = nil) { + guard let wrapper = managedCallWrapper else { completion?(CallError.hasNoActiveCall); return } + + if wrapper.sharingScreen { + wrapper.call.setSendVideo(wrapper.sendingVideo) { [weak self] error in + if let error = error { + completion?(error) + return + } + self?.managedCallWrapper?.sharingScreen = false + } + } else { + wrapper.call.startInAppScreenSharing { [weak self] error in + if let error = error { + completion?(error) + return + } + self?.managedCallWrapper?.sharingScreen = true + } + } + } + + func endCall() throws { + guard let call = managedCallWrapper?.call else { throw CallError.hasNoActiveCall } + + call.hangup(withHeaders: nil) + } + + // MARK: - VIClientCallManagerDelegate - + func client(_ client: VIClient, + didReceiveIncomingCall call: VICall, + withIncomingVideo video: Bool, + headers: [AnyHashable: Any]? + ) { + if hasManagedCall { + call.reject(with: .busy, headers: nil) + } else { + managedCallWrapper = CallWrapper(call: call, callee: call.endpoints.first?.user ?? "", displayName: call.endpoints.first?.userDisplayName, direction: .incoming) + didReceiveIncomingCall?() + } + } + + // MARK: - VICallDelegate - + func call(_ call: VICall, + didConnectWithHeaders headers: [AnyHashable : Any]? + ) { + if call.callId == managedCallWrapper?.call.callId { + managedCallWrapper?.displayName = call.endpoints.first?.userDisplayName ?? call.endpoints.first?.user + managedCallWrapper?.state = .connected + } + } + + func call(_ call: VICall, + didDisconnectWithHeaders headers: [AnyHashable: Any]?, + answeredElsewhere: NSNumber + ) { + if call.callId == managedCallWrapper?.call.callId { + managedCallWrapper?.state = .ended(reason: .disconnected) + managedCallWrapper = nil + } + } + + func call(_ call: VICall, + didFailWithError error: Error, + headers: [AnyHashable : Any]? + ) { + if call.callId == managedCallWrapper?.call.callId { + managedCallWrapper?.state = .ended(reason: .failed(message: error.localizedDescription)) + managedCallWrapper = nil + } + } + + func call(_ call: VICall, didAddLocalVideoStream videoStream: VIVideoStream) { + if videoStream.type == .screenSharing { return } + videoStreamAddedHandler?(true) { renderer in + videoStream.addRenderer(renderer) + } + } + + func call(_ call: VICall, didRemoveLocalVideoStream videoStream: VIVideoStream) { + videoStreamRemovedHandler?(true) + videoStream.removeAllRenderers() + } + + func call(_ call: VICall, didAdd endpoint: VIEndpoint) { + endpoint.delegate = self + } + + // MARK: - VIEndpointDelegate - + func endpoint(_ endpoint: VIEndpoint, didAddRemoteVideoStream videoStream: VIVideoStream) { + videoStreamAddedHandler?(false) { renderer in + videoStream.addRenderer(renderer) + } + } + + func endpoint(_ endpoint: VIEndpoint, didRemoveRemoteVideoStream videoStream: VIVideoStream) { + videoStreamRemovedHandler?(false) + videoStream.removeAllRenderers() + } + + // MARK: - VIAudioManagerDelegate - + func audioDeviceChanged(_ audioDevice: VIAudioDevice) { } + + func audioDeviceUnavailable(_ audioDevice: VIAudioDevice) { } + + func audioDevicesListChanged(_ availableAudioDevices: Set) { + if headphonesNotConnected { + selectIfAvailable(.speaker, from: availableAudioDevices) + } + } + + // MARK: - Private - + private var headphonesNotConnected: Bool { + !VIAudioManager.shared().availableAudioDevices().contains { $0.type == .wired || $0.type == .bluetooth } + } + + private func selectIfAvailable(_ audioDeviceType: VIAudioDeviceType, from audioDevices: Set) { + if let device = audioDevices.first(where: { $0.type == audioDeviceType }) { + VIAudioManager.shared().select(device) + } + } +} diff --git a/ScreenSharing/Stories/Call/CallVideoView/CallVideoView.swift b/InAppScreenSharing/Stories/Call/CallVideoView/CallVideoView.swift similarity index 100% rename from ScreenSharing/Stories/Call/CallVideoView/CallVideoView.swift rename to InAppScreenSharing/Stories/Call/CallVideoView/CallVideoView.swift diff --git a/ScreenSharing/Stories/Call/CallVideoView/CallVideoView.xib b/InAppScreenSharing/Stories/Call/CallVideoView/CallVideoView.xib similarity index 100% rename from ScreenSharing/Stories/Call/CallVideoView/CallVideoView.xib rename to InAppScreenSharing/Stories/Call/CallVideoView/CallVideoView.xib diff --git a/InAppScreenSharing/Stories/Call/CallViewController.swift b/InAppScreenSharing/Stories/Call/CallViewController.swift new file mode 100644 index 0000000..b309102 --- /dev/null +++ b/InAppScreenSharing/Stories/Call/CallViewController.swift @@ -0,0 +1,167 @@ +/* +* Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. +*/ + +import UIKit +import VoxImplantSDK +import ReplayKit + +final class CallViewController: + UIViewController, + RPScreenRecorderDelegate +{ + @IBOutlet private weak var videoButton: CallOptionButton! + @IBOutlet private weak var sharingButton: CallOptionButton! + @IBOutlet private weak var hangupButton: CallOptionButton! + @IBOutlet private weak var localVideoStreamView: CallVideoView! + @IBOutlet private weak var magneticView: EdgeMagneticView! + @IBOutlet private weak var remoteVideoStreamView: CallVideoView! + @IBOutlet private weak var callStateLabel: UILabel! + + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + .all + } + + var callManager: CallManager! // DI + var storyAssembler: StoryAssembler! // DI + + override func viewDidLoad() { + super.viewDidLoad() + + RPScreenRecorder.shared().delegate = self + + callManager.callObserver = { [weak self] call in + guard let self = self else { return } + if case .ended (let reason) = call.state { + if case .disconnected = reason { + self.dismiss(animated: true) + } + if case .failed (let message) = reason { + weak var presentingViewController = self.presentingViewController + self.dismiss(animated: true) { + presentingViewController?.present( + self.storyAssembler.callFailed( + callee: call.callee, + displayName: call.displayName ?? call.callee, + reason: message + ), + animated: true) + } + return + } + } + + self.sharingButton.state = call.state == .connected + ? call.sharingScreen ? .selected : .normal + : .unavailable + self.videoButton.state = call.state == .connected + ? call.sendingVideo ? .normal : .selected + : .unavailable + + self.localVideoStreamView.streamEnabled = call.sendingVideo && !call.sharingScreen + self.callStateLabel.text = call.state == .connected ? "Call in progress" : "Connecting..." + } + + videoButton.state = .initial(model: CallOptionButtonModels.camera) + videoButton.touchUpHandler = { [weak self] button in + Log.d("Changing sendVideo") + button.state = .unavailable + self?.sharingButton.state = .unavailable + + self?.callManager.changeSendVideo { [weak self] error in + if let error = error { + Log.e("setSendVideo error \(error.localizedDescription)") + AlertHelper.showError(message: error.localizedDescription, on: self) + } + } + } + + sharingButton.state = .initial(model: CallOptionButtonModels.screen) + sharingButton.touchUpHandler = { [weak self] button in + Log.d("Changing sharing") + button.state = .unavailable + self?.videoButton.state = .unavailable + self?.callManager.changeShareScreen { [weak self] error in + if let error = error { + Log.e("setSharing error \(error.localizedDescription)") + AlertHelper.showError(message: error.localizedDescription, on: self) + } + } + } + + hangupButton.state = .initial(model: CallOptionButtonModels.hangup) + hangupButton.touchUpHandler = { [weak self] button in + Log.d("Call hangup called") + button.state = .unavailable + do { + try self?.callManager.endCall() + } catch (let error) { + Log.e(error.localizedDescription) + } + self?.dismiss(animated: true) + } + + callManager.videoStreamAddedHandler = { [weak self] local, completion in + guard let self = self else { return } + if local { + self.localVideoStreamView.streamEnabled = true + completion(VIVideoRendererView(containerView: self.localVideoStreamView.streamView)) + } else { + self.remoteVideoStreamView.streamEnabled = true + completion(VIVideoRendererView(containerView: self.remoteVideoStreamView.streamView)) + } + } + callManager.videoStreamRemovedHandler = { [weak self] local in + (local ? self?.localVideoStreamView : self?.remoteVideoStreamView)?.streamEnabled = false + } + + localVideoStreamView.showImage = false + + guard let callDirection = callManager.managedCallWrapper?.direction else { + dismiss(animated: true) + return + } + + do { + try callDirection == .outgoing + ? callManager.startOutgoingCall() + : callManager.makeIncomingCallActive() + } catch (let error) { + Log.e(" \(callDirection) call start failed with error \(error.localizedDescription)") + dismiss(animated: true) + } + } + + @IBAction private func localVideoStreamTapped(_ sender: UITapGestureRecognizer) { + VICameraManager.shared().useBackCamera.toggle() + } + + @IBAction private func localVideoStreamDragged(_ sender: UIPanGestureRecognizer) { + switch sender.state { + case .changed: + let translation = sender.translation(in: self.view) + localVideoStreamView.center = CGPoint(x: localVideoStreamView.center.x + translation.x, + y: localVideoStreamView.center.y + translation.y) + sender.setTranslation(CGPoint.zero, in: self.view) + case .ended: + magneticView.rearrangeInnerView() + default: + break + } + } + + // MARK: - RPScreenRecorderDelegate + func screenRecorder( + _ screenRecorder: RPScreenRecorder, + didStopRecordingWith previewViewController: RPPreviewViewController?, + error: Error? + ) { + AlertHelper.showAlert(title: "Broadcast", message: "Broadcast has been ended \(error.debugDescription)", on: self) + } + + private enum CallOptionButtonModels { + static let screen = CallOptionButtonModel(image: UIImage(named: "screenSharing"), text: "Screen") + static let camera = CallOptionButtonModel(image: UIImage(named: "videoOn"), imageSelected: UIImage(named: "videoOff"), text: "Camera") + static let hangup = CallOptionButtonModel(image: UIImage(named: "hangup"), imageTint: #colorLiteral(red: 1, green: 0.02352941176, blue: 0.2549019608, alpha: 1), text: "Hangup") + } +} diff --git a/ScreenSharing/Stories/CallFailed/CallFailedViewController.swift b/InAppScreenSharing/Stories/CallFailed/CallFailedViewController.swift similarity index 100% rename from ScreenSharing/Stories/CallFailed/CallFailedViewController.swift rename to InAppScreenSharing/Stories/CallFailed/CallFailedViewController.swift diff --git a/ScreenSharing/Stories/IncomingCall/IncomingCallViewController.swift b/InAppScreenSharing/Stories/IncomingCall/IncomingCallViewController.swift similarity index 76% rename from ScreenSharing/Stories/IncomingCall/IncomingCallViewController.swift rename to InAppScreenSharing/Stories/IncomingCall/IncomingCallViewController.swift index f50463e..7410d17 100644 --- a/ScreenSharing/Stories/IncomingCall/IncomingCallViewController.swift +++ b/InAppScreenSharing/Stories/IncomingCall/IncomingCallViewController.swift @@ -29,19 +29,22 @@ final class IncomingCallViewController: UIViewController, ErrorHandling { incomingCallView.acceptHandler = { [weak self] in Log.d("Call accepted from incomingCall view") - PermissionsHelper.requestRecordPermissions(includingVideo: true) { [weak self] error in - if let error = error { - self?.handleError(error) - return - } - - if let self = self { - weak var presentingViewController = self.presentingViewController - self.dismiss(animated: true) { - presentingViewController?.present(self.storyAssembler.call, animated: true) + PermissionsHelper.requestRecordPermissions( + includingVideo: true, + completion: { [weak self] error in + if let error = error { + self?.handleError(error) + return + } + + if let self = self { + weak var presentingViewController = self.presentingViewController + self.dismiss(animated: true) { + presentingViewController?.present(self.storyAssembler.call, animated: true) + } } } - } + ) } callManager.callObserver = { [weak self] call in diff --git a/ScreenSharing/Stories/Login/LoginViewController.swift b/InAppScreenSharing/Stories/Login/LoginViewController.swift similarity index 100% rename from ScreenSharing/Stories/Login/LoginViewController.swift rename to InAppScreenSharing/Stories/Login/LoginViewController.swift diff --git a/InAppScreenSharing/Stories/Main/MainViewController.swift b/InAppScreenSharing/Stories/Main/MainViewController.swift new file mode 100644 index 0000000..e1e2921 --- /dev/null +++ b/InAppScreenSharing/Stories/Main/MainViewController.swift @@ -0,0 +1,113 @@ +/* +* Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. +*/ + +import UIKit + +final class MainViewController: + UIViewController, + LoadingShowable, + ErrorHandling, + AppLifeCycleDelegate +{ + @IBOutlet private var mainView: DefaultMainView! + + var authService: AuthService! // DI + var callManager: CallManager! // DI + var storyAssembler: StoryAssembler! // DI + + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { .all } + + override func viewDidLoad() { + super.viewDidLoad() + + if let displayName = authService.loggedInUserDisplayName { + mainView.setDisplayName(text: "Logged in as \(displayName)") + } + + mainView.callTouchHandler = { username in + Log.d("Calling \(String(describing: username))") + PermissionsHelper.requestRecordPermissions( + includingVideo: true, + completion: { [weak self] error in + if let error = error { + self?.handleError(error) + return + } + + let beginCall = { [weak self] in + guard let self = self else { return } + do { + try self.callManager.makeOutgoingCall(to: username ?? "") + self.view.endEditing(true) + self.present(self.storyAssembler.call, animated: true) + } catch (let error) { + Log.e(error.localizedDescription) + AlertHelper.showError(message: error.localizedDescription, on: self) + } + } + + guard let self = self else { return } + + if !self.authService.isLoggedIn { + self.reconnect(onSuccess: beginCall) + } else { + beginCall() + } + } + ) + } + + mainView.logoutTouchHandler = { [weak self] in + self?.authService.logout { [weak self] in + self?.dismiss(animated: true) + } + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + callManager.didReceiveIncomingCall = { [weak self] in + guard let self = self else { return } + + self.view.endEditing(true) + self.present(self.storyAssembler.incomingCall, animated: true) + } + } + + private func reconnect(onSuccess: (() -> Void)? = nil) { + Log.d("Reconnecting") + showLoading(title: "Reconnecting", details: "Please wait...") + authService.loginWithAccessToken { [weak self] error in + guard let self = self else { return } + self.hideProgress() + + if let error = error { + AlertHelper.showAlert( + title: "Connection error", + message: error.localizedDescription, + actions: [ + UIAlertAction(title: "Try again", style: .default) { + _ in self.reconnect() + }, + UIAlertAction(title: "Logout", style: .destructive) { + _ in self.authService.logout { [weak self] in + self?.dismiss(animated: true) + } + }, + ], + defaultAction: false) + } else { + onSuccess?() + } + } + } + + // MARK: - AppLifeCycleDelegate - + func applicationDidBecomeActive(_ application: UIApplication) { + if (!authService.isLoggedIn) { + reconnect() + } + } +} diff --git a/InAppScreenSharing/Stories/StoryAssembler.swift b/InAppScreenSharing/Stories/StoryAssembler.swift new file mode 100644 index 0000000..8e4bc8f --- /dev/null +++ b/InAppScreenSharing/Stories/StoryAssembler.swift @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. + */ + +import VoxImplantSDK + +final class StoryAssembler { + private let authService: AuthService + private let callManager: CallManager + + required init(authService: AuthService, callManager: CallManager) { + self.authService = authService + self.callManager = callManager + } + + var login: LoginViewController { + let controller = Storyboard.main.instantiateViewController(of: LoginViewController.self) + controller.authService = authService + controller.storyAssembler = self + return controller + } + + var main: MainViewController { + let controller = Storyboard.main.instantiateViewController(of: MainViewController.self) + controller.authService = authService + controller.callManager = callManager + controller.storyAssembler = self + return controller + } + + var call: CallViewController { + let controller = Storyboard.call.instantiateViewController(of: CallViewController.self) + controller.callManager = callManager + controller.storyAssembler = self + return controller + } + + var incomingCall: IncomingCallViewController { + let controller = Storyboard.call.instantiateViewController(of: IncomingCallViewController.self) + controller.callManager = callManager + controller.storyAssembler = self + return controller + } + + func callFailed(callee: String, displayName: String, reason: String) -> CallFailedViewController { + let controller = Storyboard.call.instantiateViewController(of: CallFailedViewController.self) + controller.user = callee + controller.displayName = displayName + controller.failReason = reason + controller.callManager = callManager + controller.storyAssembler = self + return controller + } +} diff --git a/InAppScreenSharing/Stories/Storyboards/Base.lproj/Main.storyboard b/InAppScreenSharing/Stories/Storyboards/Base.lproj/Main.storyboard new file mode 100644 index 0000000..9c2eda7 --- /dev/null +++ b/InAppScreenSharing/Stories/Storyboards/Base.lproj/Main.storyboard @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/InAppScreenSharing/Stories/Storyboards/Call.storyboard b/InAppScreenSharing/Stories/Storyboards/Call.storyboard new file mode 100644 index 0000000..15d95f7 --- /dev/null +++ b/InAppScreenSharing/Stories/Storyboards/Call.storyboard @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/InAppScreenSharing/Stories/Storyboards/Storyboard.swift b/InAppScreenSharing/Stories/Storyboards/Storyboard.swift new file mode 100644 index 0000000..6adfec5 --- /dev/null +++ b/InAppScreenSharing/Stories/Storyboards/Storyboard.swift @@ -0,0 +1,10 @@ +/* +* Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. +*/ + +import UIKit + +enum Storyboard { + static let main: UIStoryboard = UIStoryboard(name: "Main", bundle: nil) + static let call: UIStoryboard = UIStoryboard(name: "Call", bundle: nil) +} diff --git a/Podfile b/Podfile index 50902c4..00bc4c2 100644 --- a/Podfile +++ b/Podfile @@ -9,8 +9,8 @@ def common_pods end def voximplant - pod 'CocoaLumberjack/Swift', '~> 3.5' - pod 'VoxImplantSDK/CocoaLumberjackLogger', '2.32.1' + pod 'CocoaLumberjack/Swift', '~> 3.5' + pod 'VoxImplantSDK/CocoaLumberjackLogger', '2.33.0' end target 'Voximplant Demo' do @@ -43,6 +43,15 @@ target 'ScreenSharing' do voximplant end +target 'InAppScreenSharing' do + common_pods + voximplant +end + +target 'ScreenSharingUploadAppex' do + voximplant +end + post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| diff --git a/README.md b/README.md index 84725b4..1f45395 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,9 @@ The demo demonstrates [CallKit](https://developer.apple.com/documentation/callki > VideoCallKit demo application is implemented to meet new requirements. Please use it to test push notification on iOS 13. ## [ScreenSharing demo](ScreenSharing) +The demo demonstrates basic screen sharing functionality of the Voximplant iOS SDK. + +## [InAppScreenSharing demo](InAppScreenSharing) The demo demonstrates basic in-app screen sharing functionality of the Voximplant iOS SDK. ## [Voximplant demo](VoximplantDemo) diff --git a/ScreenSharing/AppDelegate.swift b/ScreenSharing/AppDelegate.swift index 1d6cf2b..ebf8723 100644 --- a/ScreenSharing/AppDelegate.swift +++ b/ScreenSharing/AppDelegate.swift @@ -5,9 +5,17 @@ import UIKit import VoxImplantSDK +extension UserDefaults { + static var main: UserDefaults { + // App Group UserDefaults needed for communication between the app and the appex + return UserDefaults(suiteName: "group.com.voximplant.demos")! + } +} + fileprivate let client: VIClient = VIClient(delegateQueue: DispatchQueue.main) fileprivate let authService: AuthService = AuthService(client) -fileprivate let callManager: CallManager = CallManager(client, authService) +fileprivate let darwinNotificationService = DarwinNotificationCenter() +fileprivate let callManager: CallManager = CallManager(client, authService, darwinNotificationService) fileprivate let storyAssembler: StoryAssembler = StoryAssembler(authService: authService, callManager: callManager) @UIApplicationMain diff --git a/ScreenSharing/README.md b/ScreenSharing/README.md index 1d69918..a939679 100644 --- a/ScreenSharing/README.md +++ b/ScreenSharing/README.md @@ -1,14 +1,12 @@ -# Voximplant Screen Sharing Kit Demo (iOS) +# Voximplant Screen Sharing Demo (iOS) -This demo demonstrates basic in-app screen sharing functionality of the Voximplant iOS SDK. The application supports video calls between this iOS app and other apps that use any Voximplant SDK. +This demo demonstrates basic screen sharing functionality of the Voximplant iOS SDK. The application supports video calls between this iOS app and other apps that use any Voximplant SDK. #### Features The application is able to: - log in to the Voximplant Cloud - auto login using access tokens -- make an video call -- receive an incoming call -- switch camera during a call +- make an video conference call - enable/disable video during a call - enable/disable screen sharing during a call - auto reconnect/relogin @@ -23,12 +21,6 @@ You'll need the following: - two Voximplant users - VoxEngine scenario - routing setup -- VoIP services certificate for push notifications. Follow [this tutorial](https://voximplant.com/docs/references/iossdk/push-notifications-for-ios) to upload the certificate to the Voximplant Control Panel - -### Automatic -We've implemented a special template to enable you to quickly use the demo – just -install [SDK tutorial](https://manage.voximplant.com/marketplace/sdk_tutorial) from our marketplace: -![marketplace](Screenshots/market.png) ### Manual @@ -36,16 +28,69 @@ You can set up it manually using our [quickstart guide](https://voximplant.com/d #### VoxEngine scenario example: ``` - require(Modules.PushService); - VoxEngine.addEventListener(AppEvents.CallAlerting, (e) => { - const newCall = VoxEngine.callUserDirect( - e.call, - e.destination, - e.callerid, - e.displayName, - null - ); - VoxEngine.easyProcess(e.call, newCall, ()=>{}, true); + var MediaStatistic = true; + require("conference"); + require("recorder"); + var conf; + var partsCounter = 0; + var recorder; + function checkForTermination() { + if (partsCounter === 0) { + conf.stop(); + conf = null; + setTimeout(VoxEngine.terminate, 2000); + } + } + VoxEngine.addEventListener(AppEvents.Started, function (e) { + conf = VoxEngine.createConference({hd_audio:true}); + conf.addEventListener(ConferenceEvents.Stopped, function(e) { + Logger.write("stopped"); + }); + conf.addEventListener(ConferenceEvents.Started, function(e) { + Logger.write("started id=" + e.conference.getId()); + }); + conf.addEventListener(ConferenceEvents.EndpointAdded, function(e) { + ++partsCounter; + Logger.write("endpoint added pc = " + partsCounter); + }); + conf.addEventListener(ConferenceEvents.EndpointRemoved, function(e) { + --partsCounter; + Logger.write("endpoint removed pc = " + partsCounter); + if (partsCounter == 0) { + setTimeout(checkForTermination, 1000*10); // wait for 10 ceconds + } + }); + }); + VoxEngine.addEventListener(AppEvents.CallAlerting, function (e) { + e.call.answer(); + partsCounter = partsCounter + 1; + const endpoint = conf.add({ + call: e.call, + mode: "FORWARD", + direction: "BOTH", scheme: e.scheme + }); + Logger.write(`New endpoint was added ID: ${endpoint.id}`); + const endpoint1 = conf.add({ + call: e.call, + mode: "FORWARD", + direction: "BOTH", scheme: e.scheme + }); + Logger.write(`New endpoint was added ID: ${endpoint1.id}`); + function checkForTermination() { + if (partsCounter === 0) { + conf.stop(); + conf = null; + } + } + function participantDisconnected() { + partsCounter = partsCounter - 1; + if (partsCounter === 0) { + setTimeout(checkForTermination, 1000 * 10); // wait for 10 ceconds + } + } + e.call.addEventListener(CallEvents.Disconnected, function (e) { + participantDisconnected(); + }); }); ``` @@ -70,17 +115,16 @@ Log in using: See the following classes for code details: * [AuthService.swift](Services/AuthService.swift) -* [LoginViewController.swift](Stories/Login/LoginViewController.swift) +* [LoginViewController.swift](Stories/LoginViewController.swift) -### Make or receive calls +### Make calls ![call](Screenshots/call.png) -Enter a Voximplant user name to the input field and press "Call" button to make a call. +Enter "myconf" to the input field and press "Call" button to join a conference. See the following classes for code details: - [CallManager.swift](Services/CallManager.swift) -- [MainViewController.swift](Stories/Main/MainViewController.swift) -- [IncomingCallViewController.swift](Stories/Main/IncomingCallViewController.swift) +- [MainViewController.swift](Stories/MainViewController.swift) ### Call controls ![inCall](Screenshots/inCall.png) @@ -88,12 +132,30 @@ See the following classes for code details: Enable/disable video or screen sharing during a call. See the following classes for code details: -- [CallViewController.swift](Stories/Call/CallViewController.swift) +- [CallViewController.swift](Stories/CallViewController.swift) * [CallManager.swift](Services/CallManager.swift) + +## Broadcasting Architecture + +Screen capture happens within an broadcast upload app extension. +look: [SampleHandler.swift](../ScreenSharingUploadAppex/SampleHandler) + +Every time user tries to begin screen sharing, extension receives ‘broadcastStarted’ method call, in which +app extension will try to auth into Voximplant Cloud +(using locally stored auth tokens in app group User Defaults) and to begin another call which is used for delivery screen frames. + +If an error occurs while doing so, broadcast upload app extension process will be ended and user will be notified about it with an alert. + +CFNotificationCenter ([DarwinNotificationsService.swift](Services/DarwinNotificationsService.swift)) is used to send messages between the app and extension. + +> Please note, ios app extensions are highly restricted and has only +50mb ram limit. Due to this reason it is possible only use hardware accelerated encoding (which is h264) and is not possible to use software accelerated (vp8). + ## Useful links 1. [Getting started](https://voximplant.com/docs/introduction) 2. [Voximplant iOS SDK reference](https://voximplant.com/docs/references/iossdk) 3. [Installing the Voximplant iOS SDK](https://voximplant.com/docs/introduction/integration/adding_sdks/installing/ios_sdk) 4. [HowTo's](https://voximplant.com/docs/howtos) +5. [About ReplayKit on WWDC](https://developer.apple.com/videos/play/wwdc2018/601/) diff --git a/ScreenSharing/Resources/Assets.xcassets/Contents.json b/ScreenSharing/Resources/Assets.xcassets/Contents.json deleted file mode 100644 index 73c0059..0000000 --- a/ScreenSharing/Resources/Assets.xcassets/Contents.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ScreenSharing/Resources/Info.plist b/ScreenSharing/Resources/Info.plist index 2b06f11..8163ac4 100644 --- a/ScreenSharing/Resources/Info.plist +++ b/ScreenSharing/Resources/Info.plist @@ -36,19 +36,22 @@ armv7 + UIRequiresFullScreen + UIStatusBarStyle UIStatusBarStyleLightContent UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft UISupportedInterfaceOrientations~ipad + UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown - UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance diff --git a/ScreenSharing/ScreenSharing.entitlements b/ScreenSharing/ScreenSharing.entitlements new file mode 100644 index 0000000..9ba9aa6 --- /dev/null +++ b/ScreenSharing/ScreenSharing.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.voximplant.demos + + + diff --git a/ScreenSharing/Screenshots/call.png b/ScreenSharing/Screenshots/call.png index 9fa34ec..deea986 100644 Binary files a/ScreenSharing/Screenshots/call.png and b/ScreenSharing/Screenshots/call.png differ diff --git a/ScreenSharing/Screenshots/inCall.png b/ScreenSharing/Screenshots/inCall.png index baa62c8..88f82f2 100644 Binary files a/ScreenSharing/Screenshots/inCall.png and b/ScreenSharing/Screenshots/inCall.png differ diff --git a/ScreenSharing/Services/AuthService.swift b/ScreenSharing/Services/AuthService.swift index 1b7a109..9c5cd38 100644 --- a/ScreenSharing/Services/AuthService.swift +++ b/ScreenSharing/Services/AuthService.swift @@ -4,6 +4,7 @@ import VoxImplantSDK +// This class is common for ScreenSharing app and ScreenSharingUploadAppex final class AuthService: NSObject, VIClientSessionDelegate { private typealias ConnectCompletion = (Error?) -> Void private typealias DisconnectCompletion = () -> Void @@ -20,7 +21,7 @@ final class AuthService: NSObject, VIClientSessionDelegate { super.init() client.sessionDelegate = self } - + @UserDefault("lastFullUsername") var loggedInUser: String? var loggedInUserDisplayName: String? @@ -69,11 +70,9 @@ final class AuthService: NSObject, VIClientSessionDelegate { return } - self?.updateAccessTokenIfNeeded(for: user) { - [weak self] - (result: Result) in - + self?.updateAccessTokenIfNeeded(for: user) { [weak self] result in switch result { + case let .failure(error): completion(error) return diff --git a/ScreenSharing/Services/CallManager.swift b/ScreenSharing/Services/CallManager.swift index 5fe2524..7dd1e6b 100644 --- a/ScreenSharing/Services/CallManager.swift +++ b/ScreenSharing/Services/CallManager.swift @@ -3,6 +3,9 @@ */ import VoxImplantSDK +import ReplayKit + +let myId = "me" final class CallManager: NSObject, @@ -11,23 +14,20 @@ final class CallManager: VICallDelegate, VIEndpointDelegate { - typealias VideoStreamAdded = (_ local: Bool, (VIVideoRendererView) -> Void) -> Void - typealias VideoStreamRemoved = (_ local: Bool) -> Void + typealias UserAdded = (String, String?) -> Void + typealias UserUpdated = (String, String?) -> Void + typealias UserRemoved = (String) -> Void + typealias VideoStreamAdded = (String, (VIVideoRendererView?) -> Void) -> Void + typealias VideoStreamRemoved = (String) -> Void struct CallWrapper { fileprivate let call: VICall let callee: String var displayName: String? var state: CallState = .connecting - let direction: CallDirection var sendingVideo: Bool = true var sharingScreen: Bool = false - enum CallDirection { - case incoming - case outgoing - } - enum CallState: Equatable { case connecting case connected @@ -42,14 +42,19 @@ final class CallManager: private let client: VIClient private let authService: AuthService + private let notificationCenter: DarwinNotificationCenter + + @UserDefault("activecall") + private var managedCallee: String? // Voximplant SDK supports multiple calls at the same time, however // this demo app demonstrates only one managed call at the moment, // so it rejects new incoming call if there is already a call. private(set) var managedCallWrapper: CallWrapper? { - willSet { + willSet { if managedCallWrapper?.call != newValue?.call { managedCallWrapper?.call.remove(self) + self.managedCallee = newValue?.callee } } didSet { @@ -64,23 +69,58 @@ final class CallManager: var hasManagedCall: Bool { managedCallWrapper != nil } private var hasNoManagedCalls: Bool { !hasManagedCall } + var endpointAddedHandler: UserAdded? + var endpointUpdatedHandler: UserUpdated? + var endpointRemovedHandler: UserRemoved? + var localVideoStreamAddedHandler: VideoStreamAdded? + var localVideoStreamRemovedHandler: VideoStreamRemoved? + var remoteVideoStreamAddedHandler: VideoStreamAdded? + var remoteVideoStreamRemovedHandler: VideoStreamRemoved? var callObserver: ((CallWrapper) -> Void)? - var didReceiveIncomingCall: (() -> Void)? - var videoStreamAddedHandler: VideoStreamAdded? - var videoStreamRemovedHandler: VideoStreamRemoved? private let callSettings: VICallSettings = { let settings = VICallSettings() + settings.preferredVideoCodec = .H264 settings.videoFlags = VIVideoFlags.videoFlags(receiveVideo: true, sendVideo: true) return settings }() - required init(_ client: VIClient, _ authService: AuthService) { + init(_ client: VIClient, + _ authService: AuthService, + _ notificationCenter: DarwinNotificationCenter + ) { self.client = client self.authService = authService + self.notificationCenter = notificationCenter super.init() + self.notificationCenter.registerForNotification(.broadcastEnded) + self.notificationCenter.broadcastEndedHandler = { + if let call = self.managedCallWrapper, call.sharingScreen { + self.managedCallWrapper?.sharingScreen = false + } + } + + self.notificationCenter.registerForNotification(.broadcastStarted) + self.notificationCenter.broadcastStartedHandler = { + if let call = self.managedCallWrapper, !call.sharingScreen { + self.managedCallWrapper?.sharingScreen = true + } + } + + self.notificationCenter.registerForNotification(.broadcastCallEnded) + self.notificationCenter.broadcastCallEndedHandler = { + if let call = self.managedCallWrapper, call.sharingScreen { + self.managedCallWrapper?.sharingScreen = false + AlertHelper.showAlert( + title: "Broadcast", + message: "Broadcast has been ended", + defaultAction: true + ) + } + } + VIAudioManager.shared().delegate = self self.client.callManagerDelegate = self } @@ -89,8 +129,8 @@ final class CallManager: guard authService.isLoggedIn else { throw AuthError.notLoggedIn } guard hasNoManagedCalls else { throw CallError.alreadyManagingACall } - if let call = client.call(contact, settings: callSettings) { - managedCallWrapper = CallWrapper(call: call, callee: contact, direction: .outgoing) + if let call = client.callConference(contact, settings: callSettings) { + managedCallWrapper = CallWrapper(call: call, callee: contact) } else { throw CallError.internalError } @@ -106,16 +146,6 @@ final class CallManager: call.start() } - func makeIncomingCallActive() throws { - guard let call = managedCallWrapper?.call else { throw CallError.hasNoActiveCall } - guard authService.isLoggedIn else { throw AuthError.notLoggedIn } - - if headphonesNotConnected { - selectIfAvailable(.speaker, from: VIAudioManager.shared().availableAudioDevices()) - } - call.answer(with: callSettings) - } - func changeSendVideo(_ completion: ((Error?) -> Void)? = nil) { guard let wrapper = managedCallWrapper else { completion?(CallError.hasNoActiveCall); return } @@ -131,28 +161,6 @@ final class CallManager: } } - func changeShareScreen(_ completion: ((Error?) -> Void)? = nil) { - guard let wrapper = managedCallWrapper else { completion?(CallError.hasNoActiveCall); return } - - if wrapper.sharingScreen { - wrapper.call.setSendVideo(wrapper.sendingVideo) { [weak self] error in - if let error = error { - completion?(error) - return - } - self?.managedCallWrapper?.sharingScreen = false - } - } else { - wrapper.call.startInAppScreenSharing { [weak self] error in - if let error = error { - completion?(error) - return - } - self?.managedCallWrapper?.sharingScreen = true - } - } - } - func endCall() throws { guard let call = managedCallWrapper?.call else { throw CallError.hasNoActiveCall } @@ -165,12 +173,8 @@ final class CallManager: withIncomingVideo video: Bool, headers: [AnyHashable: Any]? ) { - if hasManagedCall { - call.reject(with: .busy, headers: nil) - } else { - managedCallWrapper = CallWrapper(call: call, callee: call.endpoints.first?.user ?? "", displayName: call.endpoints.first?.userDisplayName, direction: .incoming) - didReceiveIncomingCall?() - } + // Incoming calls are not supported in this demo + call.reject(with: .busy, headers: nil) } // MARK: - VICallDelegate - @@ -191,6 +195,7 @@ final class CallManager: managedCallWrapper?.state = .ended(reason: .disconnected) managedCallWrapper = nil } + notificationCenter.sendNotification(.callEnded) } func call(_ call: VICall, @@ -201,33 +206,53 @@ final class CallManager: managedCallWrapper?.state = .ended(reason: .failed(message: error.localizedDescription)) managedCallWrapper = nil } + notificationCenter.sendNotification(.callEnded) } func call(_ call: VICall, didAddLocalVideoStream videoStream: VIVideoStream) { if videoStream.type == .screenSharing { return } - videoStreamAddedHandler?(true) { renderer in - videoStream.addRenderer(renderer) + localVideoStreamAddedHandler?(myId) { renderer in + if let renderer = renderer { + videoStream.addRenderer(renderer) + } } } func call(_ call: VICall, didRemoveLocalVideoStream videoStream: VIVideoStream) { - videoStreamRemovedHandler?(true) + localVideoStreamRemovedHandler?(myId) videoStream.removeAllRenderers() } func call(_ call: VICall, didAdd endpoint: VIEndpoint) { + if endpoint.endpointId == call.callId { + return + } endpoint.delegate = self + endpointAddedHandler?(endpoint.endpointId, endpoint.userDisplayName ?? endpoint.user) } // MARK: - VIEndpointDelegate - + func endpointInfoDidUpdate(_ endpoint: VIEndpoint) { + if endpoint.endpointId == managedCallWrapper?.call.callId { + return + } + endpointUpdatedHandler?(endpoint.endpointId, endpoint.userDisplayName ?? endpoint.user) + } + + func endpointDidRemove(_ endpoint: VIEndpoint) { + endpointRemovedHandler?(endpoint.endpointId) + } + func endpoint(_ endpoint: VIEndpoint, didAddRemoteVideoStream videoStream: VIVideoStream) { - videoStreamAddedHandler?(false) { renderer in - videoStream.addRenderer(renderer) + remoteVideoStreamAddedHandler?(endpoint.endpointId) { renderer in + if let renderer = renderer { + videoStream.addRenderer(renderer) + } } } func endpoint(_ endpoint: VIEndpoint, didRemoveRemoteVideoStream videoStream: VIVideoStream) { - videoStreamRemovedHandler?(false) + remoteVideoStreamRemovedHandler?(endpoint.endpointId) videoStream.removeAllRenderers() } diff --git a/ScreenSharing/Services/DarwinNotificationsService.swift b/ScreenSharing/Services/DarwinNotificationsService.swift new file mode 100644 index 0000000..24fedfd --- /dev/null +++ b/ScreenSharing/Services/DarwinNotificationsService.swift @@ -0,0 +1,72 @@ +/* +* Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. +*/ + +import Foundation + +enum Notification: String { + case broadcastStarted = "broadcastStarted" + case broadcastEnded = "broadcastEnded" + case broadcastCallEnded = "broadcastCallEnded" + case callEnded = "callEnded" +} + +final class DarwinNotificationCenter { + private var observer: UnsafeRawPointer { + UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque()) + } + + var broadcastEndedHandler: (() -> Void)? + var broadcastStartedHandler: (() -> Void)? + var broadcastCallEndedHandler: (() -> Void)? + var callEndedHandler: (() -> Void)? + + func sendNotification(_ notification: Notification) { + CFNotificationCenterPostNotification( + CFNotificationCenterGetDarwinNotifyCenter(), + CFNotificationName(rawValue: notification.rawValue as CFString), + nil, nil, true + ) + } + + func registerForNotification(_ notification: Notification) { + CFNotificationCenterAddObserver( + CFNotificationCenterGetDarwinNotifyCenter(), + observer, + { _, observer, name, _, _ in + if let observer = observer, let name = name { + let mySelf = Unmanaged + .fromOpaque(observer).takeUnretainedValue() + mySelf.didReceiveNotification(name.rawValue as String) + } + }, + notification.rawValue as CFString, nil, .deliverImmediately + ) + } + + func unregisterFromNotification(_ notification: Notification) { + CFNotificationCenterRemoveObserver( + CFNotificationCenterGetDarwinNotifyCenter(), + observer, + CFNotificationName(rawValue: notification.rawValue as CFString), + nil + ) + } + + private func didReceiveNotification(_ name: String) { + Log.i("DarwinNotificationCenter received notification \(name)") + let notification = Notification(rawValue: name) + switch notification { + case .broadcastEnded: + broadcastEndedHandler?() + case .broadcastCallEnded: + broadcastCallEndedHandler?() + case .callEnded: + callEndedHandler?() + case .broadcastStarted: + broadcastStartedHandler?() + case .none: + break + } + } +} diff --git a/ScreenSharing/Stories/Call/CallViewController.swift b/ScreenSharing/Stories/Call/CallViewController.swift deleted file mode 100644 index 824789f..0000000 --- a/ScreenSharing/Stories/Call/CallViewController.swift +++ /dev/null @@ -1,170 +0,0 @@ -/* -* Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. -*/ - -import UIKit -import VoxImplantSDK - -final class CallViewController: - UIViewController -{ - @IBOutlet private weak var videoButton: CallOptionButton! - @IBOutlet private weak var sharingButton: CallOptionButton! - @IBOutlet private weak var hangupButton: CallOptionButton! - @IBOutlet private weak var localVideoStreamView: CallVideoView! - @IBOutlet private weak var magneticView: EdgeMagneticView! - @IBOutlet private weak var remoteVideoStreamView: CallVideoView! - @IBOutlet private weak var callStateLabel: UILabel! - - var callManager: CallManager! // DI - var storyAssembler: StoryAssembler! // DI - - override func viewDidLoad() { - super.viewDidLoad() - - callManager.callObserver = updateContent(with:) - - videoButton.state = .initial(model: CallOptionButtonModels.camera) - videoButton.touchUpHandler = videoHandler(_:) - - sharingButton.state = .initial(model: CallOptionButtonModels.screen) - sharingButton.touchUpHandler = sharingHandler(_:) - - hangupButton.state = .initial(model: CallOptionButtonModels.hangup) - hangupButton.touchUpHandler = hangupHandler(_:) - - localVideoStreamView.showImage = false - - callManager.videoStreamAddedHandler = videoStreamAdded(_:_:) - callManager.videoStreamRemovedHandler = videoStreamRemoved(_:) - - guard let callDirection = callManager.managedCallWrapper?.direction else { - dismiss(animated: true) - return - } - - do { - try callDirection == .outgoing - ? callManager.startOutgoingCall() - : callManager.makeIncomingCallActive() - } catch (let error) { - Log.e(" \(callDirection) call start failed with error \(error.localizedDescription)") - dismiss(animated: true) - } - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - if let call = callManager.managedCallWrapper { - updateContent(with: call) - } - } - - @IBAction private func localVideoStreamTapped(_ sender: UITapGestureRecognizer) { - VICameraManager.shared().useBackCamera.toggle() - } - - @IBAction private func localVideoStreamDragged(_ sender: UIPanGestureRecognizer) { - switch sender.state { - case .changed: - let translation = sender.translation(in: self.view) - localVideoStreamView.center = CGPoint(x: localVideoStreamView.center.x + translation.x, - y: localVideoStreamView.center.y + translation.y) - sender.setTranslation(CGPoint.zero, in: self.view) - case .ended: - magneticView.rearrangeInnerView() - default: - break - } - } - - func videoStreamAdded(_ local: Bool, _ completion: (VIVideoRendererView) -> Void) { - if local { - localVideoStreamView.streamEnabled = true - completion(VIVideoRendererView(containerView: localVideoStreamView.streamView)) - } else { - remoteVideoStreamView.streamEnabled = true - completion(VIVideoRendererView(containerView: remoteVideoStreamView.streamView)) - } - } - - func videoStreamRemoved(_ local: Bool) { - (local ? localVideoStreamView : remoteVideoStreamView).streamEnabled = false - } - - private func updateContent(with call: CallManager.CallWrapper) { - if case .ended (let reason) = call.state { - if case .disconnected = reason { - self.dismiss(animated: true) - } - if case .failed (let message) = reason { - weak var presentingViewController = self.presentingViewController - self.dismiss(animated: true) { - presentingViewController?.present( - self.storyAssembler.callFailed( - callee: call.callee, - displayName: call.displayName ?? call.callee, - reason: message - ), - animated: true) - } - return - } - } - - sharingButton.state = call.state == .connected - ? call.sharingScreen ? .selected : .normal - : .unavailable - videoButton.state = call.state == .connected - ? call.sendingVideo ? .normal : .selected - : .unavailable - - localVideoStreamView.streamEnabled = call.sendingVideo && !call.sharingScreen - - callStateLabel.text = call.state == .connected ? "Call in progress" : "Connecting..." - } - - private func videoHandler(_ button: CallOptionButton) { - Log.d("Changing sendVideo") - button.state = .unavailable - sharingButton.state = .unavailable - - callManager.changeSendVideo { [weak self] error in - if let error = error { - Log.e("setSendVideo error \(error.localizedDescription)") - AlertHelper.showError(message: error.localizedDescription, on: self) - } - } - } - - private func sharingHandler(_ button: CallOptionButton) { - Log.d("Changing sharing") - button.state = .unavailable - videoButton.state = .unavailable - - callManager.changeShareScreen { [weak self] error in - if let error = error { - Log.e("setSharing error \(error.localizedDescription)") - AlertHelper.showError(message: error.localizedDescription, on: self) - } - } - } - - private func hangupHandler(_ button: CallOptionButton) { - Log.d("Call hangup called") - button.state = .unavailable - do { - try callManager.endCall() - } catch (let error) { - Log.e(error.localizedDescription) - } - dismiss(animated: true) - } - - private enum CallOptionButtonModels { - static let screen = CallOptionButtonModel(image: UIImage(named: "screenSharing"), text: "Screen") - static let camera = CallOptionButtonModel(image: UIImage(named: "videoOn"), imageSelected: UIImage(named: "videoOff"), text: "Camera") - static let hangup = CallOptionButtonModel(image: UIImage(named: "hangup"), imageTint: #colorLiteral(red: 1, green: 0.02352941176, blue: 0.2549019608, alpha: 1), text: "Hangup") - } -} diff --git a/ScreenSharing/Stories/CallFailedViewController.swift b/ScreenSharing/Stories/CallFailedViewController.swift new file mode 100644 index 0000000..fae2d50 --- /dev/null +++ b/ScreenSharing/Stories/CallFailedViewController.swift @@ -0,0 +1,55 @@ +/* +* Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. +*/ + +import UIKit + +final class CallFailedViewController: UIViewController { + @IBOutlet private var callFailedView: DefaultCallFailedView! + + var callManager: CallManager! // DI + var storyAssembler: StoryAssembler! // DI + var failReason: String! // DI + var user: String! // DI + var displayName: String! // DI + + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { .portrait } + + override func viewDidLoad() { + super.viewDidLoad() + + callFailedView.displayName = displayName + callFailedView.reason = failReason + + callFailedView.cancelHandler = { [weak self] in + self?.dismiss(animated: true) + } + + callFailedView.callBackHandler = { [weak self] in + guard let self = self else { return } + + do { + try self.callManager.makeOutgoingCall(to: self.user) + + weak var presentingViewController = self.presentingViewController + self.dismiss(animated: true) { + presentingViewController?.present(self.storyAssembler.call, animated: true) + } + + } catch (let error) { + Log.e("Error during call back \(error.localizedDescription)") + AlertHelper.showAlert( + title: "Error", + message: error.localizedDescription, + actions: [ + UIAlertAction(title: "Dismiss", style: .cancel) { _ in + self.dismiss(animated: true) + } + ], + defaultAction: false, + on: self + ) + } + } + } +} diff --git a/ScreenSharing/Stories/CallViewController.swift b/ScreenSharing/Stories/CallViewController.swift new file mode 100644 index 0000000..149a1d8 --- /dev/null +++ b/ScreenSharing/Stories/CallViewController.swift @@ -0,0 +1,146 @@ +/* +* Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. +*/ + +import UIKit +import VoxImplantSDK +import ReplayKit + +final class CallViewController: UIViewController { + @IBOutlet private weak var conferenceView: ConferenceView! + @IBOutlet private weak var videoButton: CallOptionButton! + @IBOutlet private weak var sharingButton: CallOptionButton! + @IBOutlet private weak var hangupButton: CallOptionButton! + + var myDisplayName: String! // DI + var callManager: CallManager! // DI + var storyAssembler: StoryAssembler! // DI + + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + .all + } + + private var screenSharingButtonSubview: UIImageView? + + override func viewDidLoad() { + super.viewDidLoad() + + callManager.callObserver = { [weak self] call in + guard let self = self else { return } + if case .ended (let reason) = call.state { + if case .disconnected = reason { + self.dismiss(animated: true) + } + if case .failed (let message) = reason { + weak var presentingViewController = self.presentingViewController + self.dismiss(animated: true) { + presentingViewController?.present( + self.storyAssembler.callFailed( + callee: call.callee, + displayName: call.displayName ?? call.callee, + reason: message + ), + animated: true) + } + return + } + } + + if #available(iOS 12.0, *) { + self.screenSharingButtonSubview?.tintColor = call.sharingScreen ? #colorLiteral(red: 0.9607843137, green: 0.2941176471, blue: 0.368627451, alpha: 1) : .white + } else { + self.sharingButton.state = call.state == .connected + ? call.sharingScreen ? .selected : .normal + : .unavailable + } + self.videoButton.state = call.state == .connected + ? call.sendingVideo ? .normal : .selected + : .unavailable + } + + videoButton.state = .initial(model: CallOptionButtonModels.camera) + videoButton.touchUpHandler = { [weak self] button in + Log.d("Changing sendVideo") + let previousState = button.state + button.state = .unavailable + + self?.callManager.changeSendVideo { [weak self] error in + if let error = error { + Log.e("setSendVideo error \(error.localizedDescription)") + AlertHelper.showError(message: error.localizedDescription, on: self) + } + button.state = previousState + } + } + + if #available(iOS 12.0, *) { + sharingButton.state = .initial(model: CallOptionButtonModels.screen) + } else { + sharingButton.state = .initial(model: CallOptionButtonModels.screenOld) + } + + sharingButton.touchUpHandler = { _ in + if #available(iOS 12.0, *) { + // nothing, besause used handler of RPSystemBroadcastPickerView, which created in self.init() method + } else if #available(iOS 11.0, *) { + AlertHelper.showAlert(title: "On iOS 11 enabling screensharing", message: "Open the Control Panel (swipe up from bottom) -> hold down on the Record Button until options appeared -> select ScreenSharingUploadAppex -> start broadcast. If can't find Record Button in Control Panel go to the Settings -> Control Center -> Customize Controls and add Screen Recording to the Control Panel.", defaultAction: true) + } + } + + if #available(iOS 12.0, *) { + let broadcastPicker = RPSystemBroadcastPickerView(frame: CGRect(x: 0, y: 0, width: 46, height: 46)) + broadcastPicker.preferredExtension = "com.voximplant.demos.ScreenSharing.ScreenSharingUploadAppex" + if let button = broadcastPicker.subviews.first as? UIButton { + button.imageView?.tintColor = UIColor.white + screenSharingButtonSubview = button.imageView + } + sharingButton.addSubview(broadcastPicker) + } + + hangupButton.state = .initial(model: CallOptionButtonModels.hangup) + hangupButton.touchUpHandler = { [weak self] button in + Log.d("Call hangup called") + button.state = .unavailable + do { + try self?.callManager.endCall() + } catch (let error) { + Log.e(error.localizedDescription) + } + self?.dismiss(animated: true) + } + + callManager.endpointAddedHandler = conferenceView.addParticipant(withID:displayName:) + callManager.endpointUpdatedHandler = conferenceView.updateParticipant(withID:displayName:) + callManager.endpointRemovedHandler = conferenceView.removeParticipant(withId:) + + callManager.localVideoStreamAddedHandler = conferenceView.prepareVideoRendererForStream(participantID:completion:) + callManager.remoteVideoStreamAddedHandler = conferenceView.prepareVideoRendererForStream(participantID:completion:) + callManager.remoteVideoStreamAddedHandler = conferenceView.prepareVideoRendererForStream(participantID:completion:) + callManager.remoteVideoStreamRemovedHandler = conferenceView.removeVideoStream(participantID:) + + do { + try callManager.startOutgoingCall() + conferenceView.addParticipant(withID: myId, displayName: "\(myDisplayName ?? "") (you)") + } catch (let error) { + Log.e("Call start failed with error \(error.localizedDescription)") + dismiss(animated: true) + } + } + + deinit { + callManager.endpointAddedHandler = nil + callManager.endpointUpdatedHandler = nil + callManager.endpointRemovedHandler = nil + callManager.localVideoStreamAddedHandler = nil + callManager.remoteVideoStreamAddedHandler = nil + callManager.remoteVideoStreamAddedHandler = nil + callManager.remoteVideoStreamRemovedHandler = nil + } + + private enum CallOptionButtonModels { + static let screenOld = CallOptionButtonModel(image: UIImage(named: "screenSharing"), text: "Screen") + static let screen = CallOptionButtonModel(image: nil, text: "Screen") + static let camera = CallOptionButtonModel(image: UIImage(named: "videoOn"), imageSelected: UIImage(named: "videoOff"), text: "Camera") + static let hangup = CallOptionButtonModel(image: UIImage(named: "hangup"), imageTint: #colorLiteral(red: 1, green: 0.02352941176, blue: 0.2549019608, alpha: 1), text: "Hangup") + } +} diff --git a/ScreenSharing/Stories/LoginViewController.swift b/ScreenSharing/Stories/LoginViewController.swift new file mode 100644 index 0000000..1a36e49 --- /dev/null +++ b/ScreenSharing/Stories/LoginViewController.swift @@ -0,0 +1,48 @@ +/* +* Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. +*/ + +import UIKit + +final class LoginViewController: UIViewController, LoadingShowable { + @IBOutlet private var loginView: DefaultLoginView! + + var authService: AuthService! // DI + var storyAssembler: StoryAssembler! // DI + + override func viewDidLoad() { + super.viewDidLoad() + + loginView.setTitle(text: "Screen sharing demo") + + let loginHandler: AuthService.LoginCompletion = { [weak self] error in + guard let self = self else { return } + self.hideProgress() + if let error = error { + AlertHelper.showError(message: error.localizedDescription, on: self) + } else { + self.present(self.storyAssembler.main, animated: true) + } + } + + loginView.loginTouchHandler = { [weak self] username, password in + Log.d("Manually Logging in with password") + self?.showLoading(title: "Connecting", details: "Please wait...") + self?.authService.login(user: username.appendingVoxDomain, password: password, loginHandler) + } + + if authService.possibleToLogin { + Log.d("Automatically Logging in with token") + self.showLoading(title: "Connecting", details: "Please wait...") + self.authService.loginWithAccessToken(loginHandler) + } + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + loginView.username = authService.loggedInUser?.replacingOccurrences(of: ".voximplant.com", with: "") + loginView.password = "" + } +} + diff --git a/ScreenSharing/Stories/Main/MainViewController.swift b/ScreenSharing/Stories/MainViewController.swift similarity index 85% rename from ScreenSharing/Stories/Main/MainViewController.swift rename to ScreenSharing/Stories/MainViewController.swift index 254d922..d0d00ee 100644 --- a/ScreenSharing/Stories/Main/MainViewController.swift +++ b/ScreenSharing/Stories/MainViewController.swift @@ -16,6 +16,10 @@ final class MainViewController: var callManager: CallManager! // DI var storyAssembler: StoryAssembler! // DI + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + .all + } + override func viewDidLoad() { super.viewDidLoad() @@ -25,7 +29,7 @@ final class MainViewController: mainView.callTouchHandler = { username in Log.d("Calling \(String(describing: username))") - PermissionsHelper.requestRecordPermissions(includingVideo: true) { [weak self] error in + PermissionsHelper.requestRecordPermissions(includingVideo: true, completion: { [weak self] error in if let error = error { self?.handleError(error) return @@ -35,8 +39,10 @@ final class MainViewController: guard let self = self else { return } do { try self.callManager.makeOutgoingCall(to: username ?? "") - self.view.endEditing(true) - self.present(self.storyAssembler.call, animated: true) + DispatchQueue.main.async { + self.view.endEditing(true) + self.present(self.storyAssembler.call, animated: true) + } } catch (let error) { Log.e(error.localizedDescription) AlertHelper.showError(message: error.localizedDescription, on: self) @@ -50,7 +56,7 @@ final class MainViewController: } else { beginCall() } - } + }) } mainView.logoutTouchHandler = { [weak self] in @@ -60,17 +66,6 @@ final class MainViewController: } } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - callManager.didReceiveIncomingCall = { [weak self] in - guard let self = self else { return } - - self.view.endEditing(true) - self.present(self.storyAssembler.incomingCall, animated: true) - } - } - private func reconnect(onSuccess: (() -> Void)? = nil) { Log.d("Reconnecting") showLoading(title: "Reconnecting", details: "Please wait...") diff --git a/ScreenSharing/Stories/StoryAssembler.swift b/ScreenSharing/Stories/StoryAssembler.swift index 8e4bc8f..be4042b 100644 --- a/ScreenSharing/Stories/StoryAssembler.swift +++ b/ScreenSharing/Stories/StoryAssembler.swift @@ -31,13 +31,7 @@ final class StoryAssembler { var call: CallViewController { let controller = Storyboard.call.instantiateViewController(of: CallViewController.self) controller.callManager = callManager - controller.storyAssembler = self - return controller - } - - var incomingCall: IncomingCallViewController { - let controller = Storyboard.call.instantiateViewController(of: IncomingCallViewController.self) - controller.callManager = callManager + controller.myDisplayName = authService.loggedInUserDisplayName controller.storyAssembler = self return controller } diff --git a/ScreenSharing/Stories/Storyboards/Call.storyboard b/ScreenSharing/Stories/Storyboards/Call.storyboard index 15d95f7..8f27e9d 100644 --- a/ScreenSharing/Stories/Storyboards/Call.storyboard +++ b/ScreenSharing/Stories/Storyboards/Call.storyboard @@ -1,6 +1,6 @@ - + @@ -8,90 +8,27 @@ - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - + - + - + @@ -108,7 +45,7 @@ - + @@ -140,62 +77,42 @@ - - - - - - + + + - - - + - - - - + + - + + - - - + + + + - - - - - - - - - - - - - + - - - @@ -219,7 +136,7 @@ - + diff --git a/ScreenSharingUploadAppex/BroadcastError.swift b/ScreenSharingUploadAppex/BroadcastError.swift new file mode 100644 index 0000000..beb2b2e --- /dev/null +++ b/ScreenSharingUploadAppex/BroadcastError.swift @@ -0,0 +1,33 @@ +/* +* Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. +*/ + +import Foundation + +fileprivate let errorDomain = "ScreenSharingErrorDomain" + +enum BroadcastError { + static let noCall = NSError( + domain: "ScreenSharingErrorDomain", + code: 10000, + userInfo: [NSLocalizedFailureReasonErrorKey: "Call is not started"] + ) + + static let nilOnCallInitialisation = NSError( + domain: errorDomain, + code: 10001, + userInfo: [NSLocalizedFailureReasonErrorKey: "Internal broadcast error"] + ) + + static let authError = NSError( + domain: errorDomain, + code: 10002, + userInfo: [NSLocalizedFailureReasonErrorKey: "Internal broadcast error"] + ) + + static let callEnded = NSError( + domain: errorDomain, + code: 10003, + userInfo: [NSLocalizedFailureReasonErrorKey: "Call has been ended"] + ) +} diff --git a/ScreenSharingUploadAppex/Info.plist b/ScreenSharingUploadAppex/Info.plist new file mode 100644 index 0000000..f3bb47e --- /dev/null +++ b/ScreenSharingUploadAppex/Info.plist @@ -0,0 +1,33 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + ScreenSharingUploadAppex + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSExtension + + NSExtensionPointIdentifier + com.apple.broadcast-services-upload + NSExtensionPrincipalClass + $(PRODUCT_MODULE_NAME).SampleHandler + RPBroadcastProcessMode + RPBroadcastProcessModeSampleBuffer + + + diff --git a/ScreenSharingUploadAppex/SampleHandler.swift b/ScreenSharingUploadAppex/SampleHandler.swift new file mode 100644 index 0000000..f66a374 --- /dev/null +++ b/ScreenSharingUploadAppex/SampleHandler.swift @@ -0,0 +1,161 @@ +/* +* Copyright (c) 2011-2020, Zingaya, Inc. All rights reserved. +*/ + +import ReplayKit +import VoxImplantSDK + +extension UserDefaults { + static var main: UserDefaults { + // App Group UserDefaults needed for communication between the app and the appex + return UserDefaults(suiteName: "group.com.voximplant.demos")! + } +} + +extension CGImagePropertyOrientation { + var rotation: VIRotation { + switch self { + case .up, .upMirrored: + return ._0 + case .right, .rightMirrored: + return ._270 + case .left, .leftMirrored: + return ._90 + case .down, .downMirrored: + return ._180 + } + } +} + +class SampleHandler: RPBroadcastSampleHandler, Loggable, VICallDelegate { + var client: VIClient + var authService: AuthService + var screenVideoSource = VICustomVideoSource(screenCastFormat: ()) + var screenSharingCall: VICall? + + @UserDefault("activecall") + var managedCallee: String? + + override init() { + let viclient = VIClient.init(delegateQueue: .main) + self.client = viclient + self.authService = AuthService(viclient) + super.init() + } + + var appName: String { "ScreenSharingUploadAppex" } + + private let notificationCenter = DarwinNotificationCenter() + + func call(_ call: VICall, + didDisconnectWithHeaders headers: [AnyHashable : Any]?, + answeredElsewhere: NSNumber + ) { + screenSharingCall?.remove(self) + notificationCenter.sendNotification(.broadcastCallEnded) + } + + func call(_ call: VICall, + didFailWithError error: Error, + headers: [AnyHashable : Any]? + ) { + screenSharingCall?.remove(self) + notificationCenter.sendNotification(.broadcastCallEnded) + } + + override func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?) { + // User has requested to start the broadcast. + // On iOS 11 it starts from Control Panel, on iOS 12 and above it could be started within the app (RPSystemBroadcastPickerView). + + configureDefaultLogging() + + let screenVideoSource = self.screenVideoSource + + if authService.possibleToLogin, + let roomid = self.managedCallee + { + Log.i("Logging in with token which used in the app") + authService.loginWithAccessToken() + { [weak self] (isError: Error?) in + if let error = isError { + self?.finishBroadcast(with: BroadcastError.authError) + Log.i("Problem due to login: \(error)") + } else { + Log.i("Login success, roomid: \(roomid)") + let settings = VICallSettings() + // It is important to use only .H264 encoding in appex. + settings.preferredVideoCodec = .H264 + settings.receiveAudio = false + settings.videoFlags = VIVideoFlags.videoFlags(receiveVideo: false, sendVideo: true) + if let screenSharingCall = self?.client.callConference(roomid, settings: settings), + let self = self + { + self.screenSharingCall = screenSharingCall + screenSharingCall.videoSource = screenVideoSource + screenSharingCall.sendAudio = false + + screenSharingCall.add(self) + screenSharingCall.start() + + self.notificationCenter.registerForNotification(.callEnded) + self.notificationCenter.callEndedHandler = { + Log.i("callEnded notification received, screensharing call will hangup") + screenSharingCall.hangup(withHeaders: nil) + self.finishBroadcast(with: BroadcastError.callEnded) + } + self.notificationCenter.sendNotification(.broadcastStarted) + + } else { + self?.finishBroadcast(with: BroadcastError.nilOnCallInitialisation) + Log.i("Can't initiate a screensharing") + } + } + } + } else { + self.finishBroadcast(with: BroadcastError.noCall) + Log.i("Impossible to login or no room") + } + } + + override func broadcastPaused() { + // Empty method should be. Don't need it. Used only in BroadcastSetupUIAppex design method. + } + + override func broadcastResumed() { + // Empty method should be. Don't need it. Used only in BroadcastSetupUIAppex design method. + } + + override func broadcastFinished() { + // User has requested to finish the broadcast or it interrupted by OS (with incoming call, for example). + self.notificationCenter.sendNotification(.broadcastEnded) + } + + override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) { + if sampleBufferType != .video { + return + } + + if (CMSampleBufferGetNumSamples(sampleBuffer) != 1 || !CMSampleBufferIsValid(sampleBuffer) || + !CMSampleBufferDataIsReady(sampleBuffer)) { + return; + } + + if let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) { + // let options = [kCVPixelBufferCGImageCompatibilityKey: true, kCVPixelBufferCGBitmapContextCompatibilityKey: true] + + var rotation: VIRotation? + if let uint32rotation = CMGetAttachment(sampleBuffer, key: RPVideoSampleOrientationKey as CFString, attachmentModeOut: nil)?.uint32Value { + rotation = CGImagePropertyOrientation(rawValue: uint32rotation)?.rotation + } + + self.screenVideoSource.sendVideoFrame(pixelBuffer, rotation: rotation ?? VIRotation._0) + } + } + + private func finishBroadcast(with error: Error) { + // This is iOS bug: sometimes finishBroadcastWithError could have no effect. + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.finishBroadcastWithError(error) + } + } +} diff --git a/ScreenSharingUploadAppex/ScreenSharingUploadAppex.entitlements b/ScreenSharingUploadAppex/ScreenSharingUploadAppex.entitlements new file mode 100644 index 0000000..9ba9aa6 --- /dev/null +++ b/ScreenSharingUploadAppex/ScreenSharingUploadAppex.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.voximplant.demos + + + diff --git a/Shared/PermissionsHelper.swift b/Shared/PermissionsHelper.swift index b129c99..bd9298a 100644 --- a/Shared/PermissionsHelper.swift +++ b/Shared/PermissionsHelper.swift @@ -7,8 +7,8 @@ import AVFoundation final class PermissionsHelper { static func requestRecordPermissions( includingVideo video: Bool, - accessRequestCompletionQueue: DispatchQueue = .main, - completion: @escaping (Error?) -> Void + completion: @escaping (Error?) -> Void, + accessRequestCompletionQueue: DispatchQueue = .main ) { requestPermissions(for: .audio, queue: accessRequestCompletionQueue) { granted in if granted { @@ -25,11 +25,7 @@ final class PermissionsHelper { } } - static func requestPermissions( - for mediaType: AVMediaType, - queue: DispatchQueue = .main, - completion: @escaping (Bool) -> Void - ) { + static func requestPermissions(for mediaType: AVMediaType, queue: DispatchQueue = .main, completion: @escaping (Bool) -> Void) { switch AVCaptureDevice.authorizationStatus(for: mediaType) { case .notDetermined: AVCaptureDevice.requestAccess(for: mediaType) { granted in @@ -38,7 +34,7 @@ final class PermissionsHelper { } } case .authorized: completion(true) - case .denied: completion(false) + case .denied: completion(false) case .restricted: completion(false) @unknown default: completion(false) } diff --git a/Shared/Protocols/Loggable.swift b/Shared/Protocols/Loggable.swift index 38401a3..0230f31 100644 --- a/Shared/Protocols/Loggable.swift +++ b/Shared/Protocols/Loggable.swift @@ -3,13 +3,12 @@ */ import VoxImplantSDK -import UIKit protocol Loggable { var appName: String { get } } -extension Loggable where Self: AppDelegate { +extension Loggable where Self: AnyObject { func configureDefaultLogging() { // Configure logs: Log.enable(level: .debug) diff --git a/Shared/UI/AlertHelper.swift b/Shared/UI/AlertHelper.swift index 19018e4..b4177a1 100644 --- a/Shared/UI/AlertHelper.swift +++ b/Shared/UI/AlertHelper.swift @@ -75,7 +75,7 @@ final class AlertHelper { static func showAlert( title: String, message: String, - actions: [UIAlertAction], + actions: [UIAlertAction] = [], defaultAction: Bool = false, on viewController: UIViewController? = nil ) { diff --git a/Conference/Stories/ConferenceCall/ConferenceParticipantView/ConferenceParticipantView.swift b/Shared/UI/ConferenceView/ConferenceParticipantView/ConferenceParticipantView.swift similarity index 100% rename from Conference/Stories/ConferenceCall/ConferenceParticipantView/ConferenceParticipantView.swift rename to Shared/UI/ConferenceView/ConferenceParticipantView/ConferenceParticipantView.swift diff --git a/Conference/Stories/ConferenceCall/ConferenceParticipantView/ConferenceParticipantView.xib b/Shared/UI/ConferenceView/ConferenceParticipantView/ConferenceParticipantView.xib similarity index 100% rename from Conference/Stories/ConferenceCall/ConferenceParticipantView/ConferenceParticipantView.xib rename to Shared/UI/ConferenceView/ConferenceParticipantView/ConferenceParticipantView.xib diff --git a/Conference/Stories/ConferenceCall/ConferenceView.swift b/Shared/UI/ConferenceView/ConferenceView.swift similarity index 98% rename from Conference/Stories/ConferenceCall/ConferenceView.swift rename to Shared/UI/ConferenceView/ConferenceView.swift index 33ea5e7..e08b160 100644 --- a/Conference/Stories/ConferenceCall/ConferenceView.swift +++ b/Shared/UI/ConferenceView/ConferenceView.swift @@ -36,6 +36,11 @@ final class ConferenceView: UIView { } private var didDraw: Bool = false + override func layoutSubviews() { + super.layoutSubviews() + rearrange() + } + func addParticipant(withID id: String, displayName: String?) { guard participantViews.count <= 25 else { print("Limit!") diff --git a/Shared/UserDefault.swift b/Shared/UserDefault.swift index f00e305..91e8dd3 100644 --- a/Shared/UserDefault.swift +++ b/Shared/UserDefault.swift @@ -4,8 +4,21 @@ import Foundation -fileprivate let userDefaults = UserDefaults.standard -fileprivate let userDefaultsDomain = Bundle.main.bundleIdentifier ?? "" +protocol UserDefaultsMain { + static var main: UserDefaults { get } +} + +extension UserDefaultsMain { + static var main: UserDefaults { + // Switching UserDefaults implementation needed for ScreenSharing and ScreenSharingUploadAppex targets. + return UserDefaults.standard + } +} + +extension UserDefaults: UserDefaultsMain {} + +fileprivate let userDefaults = UserDefaults.main +fileprivate let userDefaultsDomain = "" //Bundle.main.bundleIdentifier ?? "" fileprivate extension String { var appendingAppDomain: String { "\(userDefaultsDomain).\(self)" diff --git a/Swift.xcodeproj/project.pbxproj b/Swift.xcodeproj/project.pbxproj index b72b20d..9f4db19 100644 --- a/Swift.xcodeproj/project.pbxproj +++ b/Swift.xcodeproj/project.pbxproj @@ -21,7 +21,6 @@ 7E386CAD248793B800556D3D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E386CAC248793B800556D3D /* AppDelegate.swift */; }; 7E386CB1248793B800556D3D /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E386CB0248793B800556D3D /* LoginViewController.swift */; }; 7E386CB4248793B800556D3D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7E386CB2248793B800556D3D /* Main.storyboard */; }; - 7E386CB6248793B900556D3D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7E386CB5248793B900556D3D /* Assets.xcassets */; }; 7E386CBE248794E900556D3D /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E0CFCF22B773BC009C7F2D /* Log.swift */; }; 7E386CBF248794FF00556D3D /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E0CFCF22B773BC009C7F2D /* Log.swift */; }; 7E386CC2248795C200556D3D /* Loggable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E386CC02487954100556D3D /* Loggable.swift */; }; @@ -65,12 +64,9 @@ 7E386CF62487B3D300556D3D /* CallViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E386CF52487B3D300556D3D /* CallViewController.swift */; }; 7E386CF72487C02500556D3D /* AudioDeviceSelecting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E37C36B244F349F00F18266 /* AudioDeviceSelecting.swift */; }; 7E386CF92487C09000556D3D /* EdgeMagneticView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E386CF82487C09000556D3D /* EdgeMagneticView.swift */; }; - 7E386CFB2487C0E400556D3D /* CallVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E386CFA2487C0E400556D3D /* CallVideoView.swift */; }; - 7E386CFE2487C12B00556D3D /* CallVideoView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7E386CFD2487C12B00556D3D /* CallVideoView.xib */; }; 7E386D06248802BF00556D3D /* DefaultIncomingCallView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7E386D042488004900556D3D /* DefaultIncomingCallView.xib */; }; 7E386D08248802C200556D3D /* DefaultIncomingCallView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7E386D042488004900556D3D /* DefaultIncomingCallView.xib */; }; 7E386D0A2488040100556D3D /* Call.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7E386D092488040100556D3D /* Call.storyboard */; }; - 7E386D0C2488041600556D3D /* IncomingCallViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E386D0B2488041600556D3D /* IncomingCallViewController.swift */; }; 7E386D0D2488046700556D3D /* DefaultIncomingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E386D022488003600556D3D /* DefaultIncomingCallView.swift */; }; 7E386D112488046900556D3D /* DefaultIncomingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E386D022488003600556D3D /* DefaultIncomingCallView.swift */; }; 7E386D142488056000556D3D /* PressableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E386D132488056000556D3D /* PressableButton.swift */; }; @@ -99,6 +95,9 @@ 7E537DD12488235B00013D6E /* EdgeMagneticView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E386CF82487C09000556D3D /* EdgeMagneticView.swift */; }; 7E544266243BA45500450A73 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 7E544265243BA45500450A73 /* README.md */; }; 7E55DF37248829880067A214 /* AppLifeCycleDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EB67E37244DDE2C009DDA51 /* AppLifeCycleDelegate.swift */; }; + 7E616725249A17820066C43B /* ConferenceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E738F772435E259004387C2 /* ConferenceView.swift */; }; + 7E616726249A17870066C43B /* ConferenceParticipantView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EC3FDB92436433A0021BD9E /* ConferenceParticipantView.swift */; }; + 7E616727249A178A0066C43B /* ConferenceParticipantView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7EC3FDBB243643880021BD9E /* ConferenceParticipantView.xib */; }; 7E708B7E2452D35B00F58F92 /* PermissionsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92B6829E22F4492A002244B5 /* PermissionsHelper.swift */; }; 7E738F912435E259004387C2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7E738F6E2435E259004387C2 /* Main.storyboard */; }; 7E738F922435E259004387C2 /* Storyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E738F702435E259004387C2 /* Storyboard.swift */; }; @@ -120,7 +119,42 @@ 7E738FA82435E259004387C2 /* ManageConferenceUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E738F8F2435E259004387C2 /* ManageConferenceUseCase.swift */; }; 7E738FA92435E25A004387C2 /* JoinConferenceUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E738F902435E259004387C2 /* JoinConferenceUseCase.swift */; }; 7E738FAB2435E297004387C2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E738FAA2435E297004387C2 /* AppDelegate.swift */; }; + 7E7BE47824A6012700243B8E /* BroadcastError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E7BE47724A6012700243B8E /* BroadcastError.swift */; }; 7E8A831124891BC3000578C6 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 7E8A831024891BC3000578C6 /* README.md */; }; + 7E8E19F9249A4F6000BC78F9 /* Loggable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E386CC02487954100556D3D /* Loggable.swift */; }; + 7E8E19FA249A4F6600BC78F9 /* AppLifeCycleDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EB67E37244DDE2C009DDA51 /* AppLifeCycleDelegate.swift */; }; + 7E8E19FB249A4F7300BC78F9 /* UIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92F00F9322C610E00061AF13 /* UIExtensions.swift */; }; + 7E8E19FC249A661700BC78F9 /* Shared.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 92C8E20022EB4139006CCCFF /* Shared.xcassets */; }; + 7E8E19FD249A661E00BC78F9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C5E0CFB622B771A0009C7F2D /* LaunchScreen.storyboard */; }; + 7E8E19FE249A668100BC78F9 /* DefaultCallFailedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E537DCB24880B9D00013D6E /* DefaultCallFailedView.swift */; }; + 7E8E19FF249A668400BC78F9 /* DefaultCallFailedView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7E537DCD24880BA800013D6E /* DefaultCallFailedView.xib */; }; + 7E8E1A00249A668900BC78F9 /* DefaultIncomingCallView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7E386D042488004900556D3D /* DefaultIncomingCallView.xib */; }; + 7E8E1A01249A668C00BC78F9 /* DefaultIncomingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E386D022488003600556D3D /* DefaultIncomingCallView.swift */; }; + 7E8E1A02249A669400BC78F9 /* NibLoadable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E738F812435E259004387C2 /* NibLoadable.swift */; }; + 7E8E1A03249A669900BC78F9 /* PressableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E386D132488056000556D3D /* PressableButton.swift */; }; + 7E8E1A04249A66A600BC78F9 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCC4967DF41EEB4E5BA1A8B5 /* Log.swift */; }; + 7E8E1A05249A66AF00BC78F9 /* DefaultLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EB67E40244DE0FC009DDA51 /* DefaultLoginView.swift */; }; + 7E8E1A06249A66B400BC78F9 /* DefaultLoginView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7EB67E3C244DE069009DDA51 /* DefaultLoginView.xib */; }; + 7E8E1A07249A66BC00BC78F9 /* AlertHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E9F20E0243C7323002B5700 /* AlertHelper.swift */; }; + 7E8E1A08249A66C300BC78F9 /* ErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E9A494B2451E04200F8773E /* ErrorHandling.swift */; }; + 7E8E1A09249A66C600BC78F9 /* LoadingShowable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EB67E1A244DC327009DDA51 /* LoadingShowable.swift */; }; + 7E8E1A0A249A66CC00BC78F9 /* DefaultMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E47560D244DEF010028495E /* DefaultMainView.swift */; }; + 7E8E1A0B249A66D000BC78F9 /* DefaultMainView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7E475610244DEF100028495E /* DefaultMainView.xib */; }; + 7E8E1A0C249A66D800BC78F9 /* PermissionsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92B6829E22F4492A002244B5 /* PermissionsHelper.swift */; }; + 7E8E1A0D249A66E100BC78F9 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92C8E1F522E99419006CCCFF /* Errors.swift */; }; + 7E8E1A0E249A66E900BC78F9 /* VoximplantSDKVersionContainable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EB67E02244D8A8F009DDA51 /* VoximplantSDKVersionContainable.swift */; }; + 7E8E1A0F249A66EE00BC78F9 /* MovingWithKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E47561B244ECC580028495E /* MovingWithKeyboard.swift */; }; + 7E8E1A10249A66F800BC78F9 /* ColoredButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92049F8F22DCD177006DB07F /* ColoredButton.swift */; }; + 7E8E1A11249A670000BC78F9 /* DefaultTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92F00F9122C60B7F0061AF13 /* DefaultTextField.swift */; }; + 7E8E1A12249A670800BC78F9 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EB67E1E244DC567009DDA51 /* Color.swift */; }; + 7E8E1A14249A671900BC78F9 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E475627244EE41B0028495E /* Theme.swift */; }; + 7E8E1A15249A672000BC78F9 /* UserDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EB67E0E244DA159009DDA51 /* UserDefault.swift */; }; + 7E8E1A16249A672700BC78F9 /* CallOptionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E738F742435E259004387C2 /* CallOptionButton.swift */; }; + 7E8E1A17249A672E00BC78F9 /* CallOptionButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E738F762435E259004387C2 /* CallOptionButtonModel.swift */; }; + 7E8E1A18249A673900BC78F9 /* EdgeMagneticView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E386CF82487C09000556D3D /* EdgeMagneticView.swift */; }; + 7E8E1A19249A673E00BC78F9 /* Tokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E0CFD722B78633009C7F2D /* Tokens.swift */; }; + 7E8E1A1A249A674B00BC78F9 /* Tokens+VIAuthParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E9A49412451D5AD00F8773E /* Tokens+VIAuthParams.swift */; }; + 7E8E1A1B249A67D800BC78F9 /* CallOptionButton.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7E738F752435E259004387C2 /* CallOptionButton.xib */; }; 7E9A49392451BC7F00F8773E /* PushTokenHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E9A49372451BC5A00F8773E /* PushTokenHolder.swift */; }; 7E9A493A2451BC8000F8773E /* PushTokenHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E9A49372451BC5A00F8773E /* PushTokenHolder.swift */; }; 7E9A493D2451BEE800F8773E /* PushCallNotifierDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E9A493B2451BCFD00F8773E /* PushCallNotifierDelegate.swift */; }; @@ -186,6 +220,23 @@ 7EC3FDBC243643880021BD9E /* ConferenceParticipantView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7EC3FDBB243643880021BD9E /* ConferenceParticipantView.xib */; }; 7EC3FDC124379DC40021BD9E /* LoginTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EC3FDC024379DC40021BD9E /* LoginTextField.swift */; }; 7EC3FDC32437B0170021BD9E /* Shared.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 92C8E20022EB4139006CCCFF /* Shared.xcassets */; }; + 7ECA29D124A508A100F3D1ED /* DarwinNotificationsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ECA29D024A508A100F3D1ED /* DarwinNotificationsService.swift */; }; + 7ECA29D224A509CB00F3D1ED /* DarwinNotificationsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ECA29D024A508A100F3D1ED /* DarwinNotificationsService.swift */; }; + 7EF4B629249A4E9F00081013 /* Call.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7EF4B5E4249A4DA600081013 /* Call.storyboard */; }; + 7EF4B62A249A4EA200081013 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7EF4B5E5249A4DA600081013 /* Main.storyboard */; }; + 7EF4B62B249A4EA400081013 /* Storyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EF4B5E7249A4DA600081013 /* Storyboard.swift */; }; + 7EF4B62C249A4EAC00081013 /* CallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EF4B5F9249A4DA600081013 /* CallManager.swift */; }; + 7EF4B62D249A4EB100081013 /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EF4B5FA249A4DA600081013 /* AuthService.swift */; }; + 7EF4B630249A4EC900081013 /* StoryAssembler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EF4B5F1249A4DA600081013 /* StoryAssembler.swift */; }; + 7EF4B631249A4ECE00081013 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EF4B5F7249A4DA600081013 /* AppDelegate.swift */; }; + 7EF4B632249A4ED100081013 /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 7EF4B5E1249A4DA600081013 /* README.md */; }; + 7EF4B633249A4ED600081013 /* CallFailedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EF4B5E9249A4DA600081013 /* CallFailedViewController.swift */; }; + 7EF4B634249A4EDE00081013 /* CallViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EF4B5EB249A4DA600081013 /* CallViewController.swift */; }; + 7EF4B635249A4EE300081013 /* CallVideoView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7EF4B5ED249A4DA600081013 /* CallVideoView.xib */; }; + 7EF4B636249A4EE600081013 /* CallVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EF4B5EE249A4DA600081013 /* CallVideoView.swift */; }; + 7EF4B637249A4EEA00081013 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EF4B5F0249A4DA600081013 /* MainViewController.swift */; }; + 7EF4B638249A4EEE00081013 /* IncomingCallViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EF4B5F3249A4DA600081013 /* IncomingCallViewController.swift */; }; + 7EF4B639249A4F0800081013 /* LoginViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EF4B5F6249A4DA600081013 /* LoginViewController.swift */; }; 7EF72227244F3CD000B0037E /* AudioDeviceSelecting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E37C36B244F349F00F18266 /* AudioDeviceSelecting.swift */; }; 7EF7222A244F4FEA00B0037E /* CallVideoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EF72229244F4FEA00B0037E /* CallVideoView.swift */; }; 7EF7222C244F506D00B0037E /* CallVideoView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7EF7222B244F506D00B0037E /* CallVideoView.xib */; }; @@ -235,6 +286,16 @@ C5551BBD22C4FEC6003A90A9 /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5551BBA22C4FEC6003A90A9 /* SettingsViewController.swift */; }; C5551BBE22C4FEC6003A90A9 /* SettingTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5551BBB22C4FEC6003A90A9 /* SettingTableViewCell.swift */; }; C5551BBF22C4FEC6003A90A9 /* SettingsDetailTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5551BBC22C4FEC6003A90A9 /* SettingsDetailTableViewController.swift */; }; + C56001612497686500818028 /* ReplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C56001602497686500818028 /* ReplayKit.framework */; }; + C56001642497686500818028 /* SampleHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56001632497686500818028 /* SampleHandler.swift */; }; + C56001682497686500818028 /* ScreenSharingUploadAppex.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C560015F2497686500818028 /* ScreenSharingUploadAppex.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + C56001832498BF1D00818028 /* Tokens.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E0CFD722B78633009C7F2D /* Tokens.swift */; }; + C56001842498C36200818028 /* UserDefault.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7EB67E0E244DA159009DDA51 /* UserDefault.swift */; }; + C56001852498C3C800818028 /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92C8E1F522E99419006CCCFF /* Errors.swift */; }; + C56001862498C3EF00818028 /* Tokens+VIAuthParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E9A49412451D5AD00F8773E /* Tokens+VIAuthParams.swift */; }; + C560018924990C8100818028 /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = C5E0CFCF22B773BC009C7F2D /* Log.swift */; }; + C560018D2499422800818028 /* Loggable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E386CC02487954100556D3D /* Loggable.swift */; }; + C560018F249A023A00818028 /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7E386CEF24879A2200556D3D /* AuthService.swift */; }; C58D0F4823D1A18200A337EB /* LoudAudioFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = C58D0F4623D1A16E00A337EB /* LoudAudioFile.swift */; }; C590E7C32322E0F300E112D7 /* CallWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C590E7C22322E0F300E112D7 /* CallWrapper.swift */; }; C5A248C322B3F7150008FAF4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C5A248C222B3F7150008FAF4 /* Assets.xcassets */; }; @@ -278,6 +339,30 @@ CCC49FB0513FDFD9B086E1CD /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = CCC4967DF41EEB4E5BA1A8B5 /* Log.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + C56001662497686500818028 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 8EEA98A81FB5A1AC00E90861 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C560015E2497686500818028; + remoteInfo = ScreenSharingUploadAppex; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + C56001692497686500818028 /* Embed App Extensions */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 13; + files = ( + C56001682497686500818028 /* ScreenSharingUploadAppex.appex in Embed App Extensions */, + ); + name = "Embed App Extensions"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 7E37C365244F1FD100F18266 /* CallWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallWrapper.swift; sourceTree = ""; }; 7E37C369244F29C100F18266 /* CallViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallViewController.swift; sourceTree = ""; }; @@ -286,7 +371,6 @@ 7E386CAC248793B800556D3D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7E386CB0248793B800556D3D /* LoginViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; 7E386CB3248793B800556D3D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; - 7E386CB5248793B900556D3D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 7E386CBA248793B900556D3D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7E386CC02487954100556D3D /* Loggable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Loggable.swift; sourceTree = ""; }; 7E386CE1248798CF00556D3D /* StoryAssembler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryAssembler.swift; sourceTree = ""; }; @@ -296,12 +380,9 @@ 7E386CF32487B1F000556D3D /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; 7E386CF52487B3D300556D3D /* CallViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallViewController.swift; sourceTree = ""; }; 7E386CF82487C09000556D3D /* EdgeMagneticView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EdgeMagneticView.swift; sourceTree = ""; }; - 7E386CFA2487C0E400556D3D /* CallVideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVideoView.swift; sourceTree = ""; }; - 7E386CFD2487C12B00556D3D /* CallVideoView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CallVideoView.xib; sourceTree = ""; }; 7E386D022488003600556D3D /* DefaultIncomingCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultIncomingCallView.swift; sourceTree = ""; }; 7E386D042488004900556D3D /* DefaultIncomingCallView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DefaultIncomingCallView.xib; sourceTree = ""; }; 7E386D092488040100556D3D /* Call.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Call.storyboard; sourceTree = ""; }; - 7E386D0B2488041600556D3D /* IncomingCallViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallViewController.swift; sourceTree = ""; }; 7E386D132488056000556D3D /* PressableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PressableButton.swift; sourceTree = ""; }; 7E47560D244DEF010028495E /* DefaultMainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultMainView.swift; sourceTree = ""; }; 7E475610244DEF100028495E /* DefaultMainView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DefaultMainView.xib; sourceTree = ""; }; @@ -333,6 +414,7 @@ 7E738F8F2435E259004387C2 /* ManageConferenceUseCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManageConferenceUseCase.swift; sourceTree = ""; }; 7E738F902435E259004387C2 /* JoinConferenceUseCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JoinConferenceUseCase.swift; sourceTree = ""; }; 7E738FAA2435E297004387C2 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7E7BE47724A6012700243B8E /* BroadcastError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BroadcastError.swift; sourceTree = ""; }; 7E8A831024891BC3000578C6 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 7E9A49372451BC5A00F8773E /* PushTokenHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushTokenHolder.swift; sourceTree = ""; }; 7E9A493B2451BCFD00F8773E /* PushCallNotifierDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushCallNotifierDelegate.swift; sourceTree = ""; }; @@ -362,6 +444,24 @@ 7EC3FDB92436433A0021BD9E /* ConferenceParticipantView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConferenceParticipantView.swift; sourceTree = ""; }; 7EC3FDBB243643880021BD9E /* ConferenceParticipantView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ConferenceParticipantView.xib; sourceTree = ""; }; 7EC3FDC024379DC40021BD9E /* LoginTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoginTextField.swift; sourceTree = ""; }; + 7ECA29D024A508A100F3D1ED /* DarwinNotificationsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DarwinNotificationsService.swift; sourceTree = ""; }; + 7EF4B5E0249A4DA600081013 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7EF4B5E1249A4DA600081013 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 7EF4B5E4249A4DA600081013 /* Call.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Call.storyboard; sourceTree = ""; }; + 7EF4B5E6249A4DA600081013 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 7EF4B5E7249A4DA600081013 /* Storyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Storyboard.swift; sourceTree = ""; }; + 7EF4B5E9249A4DA600081013 /* CallFailedViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallFailedViewController.swift; sourceTree = ""; }; + 7EF4B5EB249A4DA600081013 /* CallViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallViewController.swift; sourceTree = ""; }; + 7EF4B5ED249A4DA600081013 /* CallVideoView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CallVideoView.xib; sourceTree = ""; }; + 7EF4B5EE249A4DA600081013 /* CallVideoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallVideoView.swift; sourceTree = ""; }; + 7EF4B5F0249A4DA600081013 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; + 7EF4B5F1249A4DA600081013 /* StoryAssembler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StoryAssembler.swift; sourceTree = ""; }; + 7EF4B5F3249A4DA600081013 /* IncomingCallViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IncomingCallViewController.swift; sourceTree = ""; }; + 7EF4B5F6249A4DA600081013 /* LoginViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LoginViewController.swift; sourceTree = ""; }; + 7EF4B5F7249A4DA600081013 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7EF4B5F9249A4DA600081013 /* CallManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallManager.swift; sourceTree = ""; }; + 7EF4B5FA249A4DA600081013 /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; + 7EF4B615249A4DF500081013 /* InAppScreenSharing.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = InAppScreenSharing.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7EF72229244F4FEA00B0037E /* CallVideoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallVideoView.swift; sourceTree = ""; }; 7EF7222B244F506D00B0037E /* CallVideoView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = CallVideoView.xib; sourceTree = ""; }; 8E85E3881FBB06E50033831A /* VoximplantDemo.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = VoximplantDemo.entitlements; sourceTree = ""; }; @@ -397,6 +497,13 @@ C5551BBA22C4FEC6003A90A9 /* SettingsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewController.swift; sourceTree = ""; }; C5551BBB22C4FEC6003A90A9 /* SettingTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingTableViewCell.swift; sourceTree = ""; }; C5551BBC22C4FEC6003A90A9 /* SettingsDetailTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDetailTableViewController.swift; sourceTree = ""; }; + C560015F2497686500818028 /* ScreenSharingUploadAppex.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ScreenSharingUploadAppex.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + C56001602497686500818028 /* ReplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ReplayKit.framework; path = System/Library/Frameworks/ReplayKit.framework; sourceTree = SDKROOT; }; + C56001632497686500818028 /* SampleHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleHandler.swift; sourceTree = ""; }; + C56001652497686500818028 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C56001732497687A00818028 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + C56001872498CD1100818028 /* ScreenSharing.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ScreenSharing.entitlements; sourceTree = ""; }; + C56001882498CD3800818028 /* ScreenSharingUploadAppex.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ScreenSharingUploadAppex.entitlements; sourceTree = ""; }; C58D0F4623D1A16E00A337EB /* LoudAudioFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoudAudioFile.swift; sourceTree = ""; }; C590E7C22322E0F300E112D7 /* CallWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallWrapper.swift; sourceTree = ""; }; C5A248B922B3F7140008FAF4 /* AudioCall.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AudioCall.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -469,6 +576,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7EF4B612249A4DF500081013 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8EEA98AD1FB5A1AD00E90861 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -476,6 +590,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C560015C2497686500818028 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C56001612497686500818028 /* ReplayKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; C5A248B622B3F7140008FAF4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -497,6 +619,8 @@ isa = PBXGroup; children = ( 92DB9E612333C8B5007BF73C /* PushKit.framework */, + C56001602497686500818028 /* ReplayKit.framework */, + C56001732497687A00818028 /* UIKit.framework */, ); name = Frameworks; sourceTree = ""; @@ -545,6 +669,7 @@ 7E386CAB248793B800556D3D /* ScreenSharing */ = { isa = PBXGroup; children = ( + C56001872498CD1100818028 /* ScreenSharing.entitlements */, 7E8A831024891BC3000578C6 /* README.md */, 7E386CAC248793B800556D3D /* AppDelegate.swift */, 7E386CE0248798C300556D3D /* Stories */, @@ -558,39 +683,13 @@ isa = PBXGroup; children = ( 7E386CE1248798CF00556D3D /* StoryAssembler.swift */, - 7E386CE3248798E500556D3D /* Login */, - 7E386CE4248798F000556D3D /* Main */, - 7E386D122488049400556D3D /* IncomingCall */, - 7E537DC724880B0C00013D6E /* CallFailed */, - 7E386CE5248798F500556D3D /* Call */, - 7E386CE6248798FA00556D3D /* Storyboards */, - ); - path = Stories; - sourceTree = ""; - }; - 7E386CE3248798E500556D3D /* Login */ = { - isa = PBXGroup; - children = ( 7E386CB0248793B800556D3D /* LoginViewController.swift */, - ); - path = Login; - sourceTree = ""; - }; - 7E386CE4248798F000556D3D /* Main */ = { - isa = PBXGroup; - children = ( 7E386CF32487B1F000556D3D /* MainViewController.swift */, - ); - path = Main; - sourceTree = ""; - }; - 7E386CE5248798F500556D3D /* Call */ = { - isa = PBXGroup; - children = ( - 7E386CFC2487C0EA00556D3D /* CallVideoView */, + 7E537DC824880B3300013D6E /* CallFailedViewController.swift */, 7E386CF52487B3D300556D3D /* CallViewController.swift */, + 7E386CE6248798FA00556D3D /* Storyboards */, ); - path = Call; + path = Stories; sourceTree = ""; }; 7E386CE6248798FA00556D3D /* Storyboards */ = { @@ -606,7 +705,6 @@ 7E386CE92487993800556D3D /* Resources */ = { isa = PBXGroup; children = ( - 7E386CB5248793B900556D3D /* Assets.xcassets */, 7E386CBA248793B900556D3D /* Info.plist */, ); path = Resources; @@ -617,19 +715,11 @@ children = ( 7E386CEF24879A2200556D3D /* AuthService.swift */, 7E386CF12487A3F600556D3D /* CallManager.swift */, + 7ECA29D024A508A100F3D1ED /* DarwinNotificationsService.swift */, ); path = Services; sourceTree = ""; }; - 7E386CFC2487C0EA00556D3D /* CallVideoView */ = { - isa = PBXGroup; - children = ( - 7E386CFD2487C12B00556D3D /* CallVideoView.xib */, - 7E386CFA2487C0E400556D3D /* CallVideoView.swift */, - ); - path = CallVideoView; - sourceTree = ""; - }; 7E386D012488002200556D3D /* IncomingCallView */ = { isa = PBXGroup; children = ( @@ -639,14 +729,6 @@ path = IncomingCallView; sourceTree = ""; }; - 7E386D122488049400556D3D /* IncomingCall */ = { - isa = PBXGroup; - children = ( - 7E386D0B2488041600556D3D /* IncomingCallViewController.swift */, - ); - path = IncomingCall; - sourceTree = ""; - }; 7E47560F244DEF050028495E /* MainView */ = { isa = PBXGroup; children = ( @@ -656,21 +738,22 @@ path = MainView; sourceTree = ""; }; - 7E537DC724880B0C00013D6E /* CallFailed */ = { + 7E537DCA24880B8F00013D6E /* CallFailedView */ = { isa = PBXGroup; children = ( - 7E537DC824880B3300013D6E /* CallFailedViewController.swift */, + 7E537DCB24880B9D00013D6E /* DefaultCallFailedView.swift */, + 7E537DCD24880BA800013D6E /* DefaultCallFailedView.xib */, ); - path = CallFailed; + path = CallFailedView; sourceTree = ""; }; - 7E537DCA24880B8F00013D6E /* CallFailedView */ = { + 7E616724249A17700066C43B /* ConferenceView */ = { isa = PBXGroup; children = ( - 7E537DCB24880B9D00013D6E /* DefaultCallFailedView.swift */, - 7E537DCD24880BA800013D6E /* DefaultCallFailedView.xib */, + 7E738F772435E259004387C2 /* ConferenceView.swift */, + 7EC3FDBD243738BE0021BD9E /* ConferenceParticipantView */, ); - path = CallFailedView; + path = ConferenceView; sourceTree = ""; }; 7E738F5A2435DF92004387C2 /* Conference */ = { @@ -702,8 +785,6 @@ 7E738F712435E259004387C2 /* ConferenceCall */ = { isa = PBXGroup; children = ( - 7E738F772435E259004387C2 /* ConferenceView.swift */, - 7EC3FDBD243738BE0021BD9E /* ConferenceParticipantView */, 7E738F792435E259004387C2 /* ConferenceCallViewController.swift */, ); path = ConferenceCall; @@ -862,6 +943,109 @@ path = ConferenceParticipantView; sourceTree = ""; }; + 7EF4B5DE249A4DA600081013 /* Resources */ = { + isa = PBXGroup; + children = ( + 7EF4B5E0249A4DA600081013 /* Info.plist */, + ); + path = Resources; + sourceTree = ""; + }; + 7EF4B5E2249A4DA600081013 /* Stories */ = { + isa = PBXGroup; + children = ( + 7EF4B5F1249A4DA600081013 /* StoryAssembler.swift */, + 7EF4B5F4249A4DA600081013 /* Login */, + 7EF4B5EF249A4DA600081013 /* Main */, + 7EF4B5F2249A4DA600081013 /* IncomingCall */, + 7EF4B5E8249A4DA600081013 /* CallFailed */, + 7EF4B5EA249A4DA600081013 /* Call */, + 7EF4B5E3249A4DA600081013 /* Storyboards */, + ); + path = Stories; + sourceTree = ""; + }; + 7EF4B5E3249A4DA600081013 /* Storyboards */ = { + isa = PBXGroup; + children = ( + 7EF4B5E5249A4DA600081013 /* Main.storyboard */, + 7EF4B5E4249A4DA600081013 /* Call.storyboard */, + 7EF4B5E7249A4DA600081013 /* Storyboard.swift */, + ); + path = Storyboards; + sourceTree = ""; + }; + 7EF4B5E8249A4DA600081013 /* CallFailed */ = { + isa = PBXGroup; + children = ( + 7EF4B5E9249A4DA600081013 /* CallFailedViewController.swift */, + ); + path = CallFailed; + sourceTree = ""; + }; + 7EF4B5EA249A4DA600081013 /* Call */ = { + isa = PBXGroup; + children = ( + 7EF4B5EB249A4DA600081013 /* CallViewController.swift */, + 7EF4B5EC249A4DA600081013 /* CallVideoView */, + ); + path = Call; + sourceTree = ""; + }; + 7EF4B5EC249A4DA600081013 /* CallVideoView */ = { + isa = PBXGroup; + children = ( + 7EF4B5ED249A4DA600081013 /* CallVideoView.xib */, + 7EF4B5EE249A4DA600081013 /* CallVideoView.swift */, + ); + path = CallVideoView; + sourceTree = ""; + }; + 7EF4B5EF249A4DA600081013 /* Main */ = { + isa = PBXGroup; + children = ( + 7EF4B5F0249A4DA600081013 /* MainViewController.swift */, + ); + path = Main; + sourceTree = ""; + }; + 7EF4B5F2249A4DA600081013 /* IncomingCall */ = { + isa = PBXGroup; + children = ( + 7EF4B5F3249A4DA600081013 /* IncomingCallViewController.swift */, + ); + path = IncomingCall; + sourceTree = ""; + }; + 7EF4B5F4249A4DA600081013 /* Login */ = { + isa = PBXGroup; + children = ( + 7EF4B5F6249A4DA600081013 /* LoginViewController.swift */, + ); + path = Login; + sourceTree = ""; + }; + 7EF4B5F8249A4DA600081013 /* Services */ = { + isa = PBXGroup; + children = ( + 7EF4B5F9249A4DA600081013 /* CallManager.swift */, + 7EF4B5FA249A4DA600081013 /* AuthService.swift */, + ); + path = Services; + sourceTree = ""; + }; + 7EF4B616249A4DF500081013 /* InAppScreenSharing */ = { + isa = PBXGroup; + children = ( + 7EF4B5F7249A4DA600081013 /* AppDelegate.swift */, + 7EF4B5E1249A4DA600081013 /* README.md */, + 7EF4B5E2249A4DA600081013 /* Stories */, + 7EF4B5F8249A4DA600081013 /* Services */, + 7EF4B5DE249A4DA600081013 /* Resources */, + ); + path = InAppScreenSharing; + sourceTree = ""; + }; 7EF72228244F4FD300B0037E /* CallVideoView */ = { isa = PBXGroup; children = ( @@ -909,6 +1093,8 @@ 7E738F5A2435DF92004387C2 /* Conference */, 7EB67DE8244D8543009DDA51 /* VideoCallKit */, 7E386CAB248793B800556D3D /* ScreenSharing */, + C56001622497686500818028 /* ScreenSharingUploadAppex */, + 7EF4B616249A4DF500081013 /* InAppScreenSharing */, 8EEA98B11FB5A1AD00E90861 /* Products */, 369334A608BD108D7AD7AA32 /* Pods */, 1DD0F974DCACD349A4CA3C60 /* Frameworks */, @@ -924,6 +1110,8 @@ 7E738F592435DF92004387C2 /* Conference.app */, 7EB67DE7244D8543009DDA51 /* VideoCallKit.app */, 7E386CAA248793B800556D3D /* ScreenSharing.app */, + C560015F2497686500818028 /* ScreenSharingUploadAppex.appex */, + 7EF4B615249A4DF500081013 /* InAppScreenSharing.app */, ); name = Products; sourceTree = ""; @@ -993,6 +1181,7 @@ 7E537DCA24880B8F00013D6E /* CallFailedView */, 7E386D012488002200556D3D /* IncomingCallView */, 7E738F732435E259004387C2 /* CallOptionButton */, + 7E616724249A17700066C43B /* ConferenceView */, 7E386CF82487C09000556D3D /* EdgeMagneticView.swift */, 92049F8F22DCD177006DB07F /* ColoredButton.swift */, 7E386D132488056000556D3D /* PressableButton.swift */, @@ -1037,6 +1226,17 @@ path = Fonts; sourceTree = ""; }; + C56001622497686500818028 /* ScreenSharingUploadAppex */ = { + isa = PBXGroup; + children = ( + C56001882498CD3800818028 /* ScreenSharingUploadAppex.entitlements */, + C56001632497686500818028 /* SampleHandler.swift */, + C56001652497686500818028 /* Info.plist */, + 7E7BE47724A6012700243B8E /* BroadcastError.swift */, + ); + path = ScreenSharingUploadAppex; + sourceTree = ""; + }; C5A248BA22B3F7140008FAF4 /* AudioCall */ = { isa = PBXGroup; children = ( @@ -1155,10 +1355,12 @@ 7E386CA6248793B800556D3D /* Sources */, 7E386CA7248793B800556D3D /* Frameworks */, 7E386CA8248793B800556D3D /* Resources */, + C56001692497686500818028 /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( + C56001672497686500818028 /* PBXTargetDependency */, ); name = ScreenSharing; productName = ScreenSharing; @@ -1199,6 +1401,23 @@ productReference = 7EB67DE7244D8543009DDA51 /* VideoCallKit.app */; productType = "com.apple.product-type.application"; }; + 7EF4B614249A4DF500081013 /* InAppScreenSharing */ = { + isa = PBXNativeTarget; + buildConfigurationList = 7EF4B626249A4DF700081013 /* Build configuration list for PBXNativeTarget "InAppScreenSharing" */; + buildPhases = ( + 7EF4B611249A4DF500081013 /* Sources */, + 7EF4B612249A4DF500081013 /* Frameworks */, + 7EF4B613249A4DF500081013 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = InAppScreenSharing; + productName = InAppScreenSharing; + productReference = 7EF4B615249A4DF500081013 /* InAppScreenSharing.app */; + productType = "com.apple.product-type.application"; + }; 8EEA98AF1FB5A1AD00E90861 /* Voximplant Demo */ = { isa = PBXNativeTarget; buildConfigurationList = 8EEA98C21FB5A1AD00E90861 /* Build configuration list for PBXNativeTarget "Voximplant Demo" */; @@ -1216,6 +1435,23 @@ productReference = 8EEA98B01FB5A1AD00E90861 /* Voximplant Demo.app */; productType = "com.apple.product-type.application"; }; + C560015E2497686500818028 /* ScreenSharingUploadAppex */ = { + isa = PBXNativeTarget; + buildConfigurationList = C560016C2497686500818028 /* Build configuration list for PBXNativeTarget "ScreenSharingUploadAppex" */; + buildPhases = ( + C560015B2497686500818028 /* Sources */, + C560015C2497686500818028 /* Frameworks */, + C560015D2497686500818028 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ScreenSharingUploadAppex; + productName = ScreenSharingUploadAppex; + productReference = C560015F2497686500818028 /* ScreenSharingUploadAppex.appex */; + productType = "com.apple.product-type.app-extension"; + }; C5A248B822B3F7140008FAF4 /* AudioCall */ = { isa = PBXNativeTarget; buildConfigurationList = C5A248CA22B3F7150008FAF4 /* Build configuration list for PBXNativeTarget "AudioCall" */; @@ -1272,6 +1508,10 @@ CreatedOnToolsVersion = 11.4.1; ProvisioningStyle = Automatic; }; + 7EF4B614249A4DF500081013 = { + CreatedOnToolsVersion = 11.5; + ProvisioningStyle = Automatic; + }; 8EEA98AF1FB5A1AD00E90861 = { CreatedOnToolsVersion = 9.1; LastSwiftMigration = 1000; @@ -1285,6 +1525,10 @@ }; }; }; + C560015E2497686500818028 = { + CreatedOnToolsVersion = 11.3.1; + ProvisioningStyle = Automatic; + }; C5A248B822B3F7140008FAF4 = { CreatedOnToolsVersion = 10.2.1; ProvisioningStyle = Automatic; @@ -1319,6 +1563,8 @@ 7E738F582435DF92004387C2 /* Conference */, 7EB67DE6244D8543009DDA51 /* VideoCallKit */, 7E386CA9248793B800556D3D /* ScreenSharing */, + C560015E2497686500818028 /* ScreenSharingUploadAppex */, + 7EF4B614249A4DF500081013 /* InAppScreenSharing */, ); }; /* End PBXProject section */ @@ -1329,13 +1575,12 @@ buildActionMask = 2147483647; files = ( 7E537DCE24880BA800013D6E /* DefaultCallFailedView.xib in Resources */, - 7E386CFE2487C12B00556D3D /* CallVideoView.xib in Resources */, 7E386CED248799AA00556D3D /* LaunchScreen.storyboard in Resources */, 7E386D0A2488040100556D3D /* Call.storyboard in Resources */, 7E386CD1248797CE00556D3D /* DefaultLoginView.xib in Resources */, - 7E386CB6248793B900556D3D /* Assets.xcassets in Resources */, 7E8A831124891BC3000578C6 /* README.md in Resources */, 7E386CD5248797E000556D3D /* CallOptionButton.xib in Resources */, + 7E616727249A178A0066C43B /* ConferenceParticipantView.xib in Resources */, 7E386CD3248797D500556D3D /* DefaultMainView.xib in Resources */, 7E386D08248802C200556D3D /* DefaultIncomingCallView.xib in Resources */, 7E386CCF248797BE00556D3D /* Shared.xcassets in Resources */, @@ -1373,6 +1618,24 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7EF4B613249A4DF500081013 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7EF4B629249A4E9F00081013 /* Call.storyboard in Resources */, + 7E8E19FC249A661700BC78F9 /* Shared.xcassets in Resources */, + 7EF4B632249A4ED100081013 /* README.md in Resources */, + 7E8E19FD249A661E00BC78F9 /* LaunchScreen.storyboard in Resources */, + 7E8E1A00249A668900BC78F9 /* DefaultIncomingCallView.xib in Resources */, + 7E8E1A0B249A66D000BC78F9 /* DefaultMainView.xib in Resources */, + 7E8E1A1B249A67D800BC78F9 /* CallOptionButton.xib in Resources */, + 7E8E19FF249A668400BC78F9 /* DefaultCallFailedView.xib in Resources */, + 7E8E1A06249A66B400BC78F9 /* DefaultLoginView.xib in Resources */, + 7EF4B62A249A4EA200081013 /* Main.storyboard in Resources */, + 7EF4B635249A4EE300081013 /* CallVideoView.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8EEA98AE1FB5A1AD00E90861 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1392,6 +1655,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C560015D2497686500818028 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; C5A248B722B3F7140008FAF4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -1449,19 +1719,20 @@ 7E386CE82487991D00556D3D /* Storyboard.swift in Sources */, 7E386CD92487980F00556D3D /* UIExtensions.swift in Sources */, 7E386CE2248798CF00556D3D /* StoryAssembler.swift in Sources */, - 7E386D0C2488041600556D3D /* IncomingCallViewController.swift in Sources */, 7E386CD7248797E600556D3D /* ColoredButton.swift in Sources */, 7E386CF92487C09000556D3D /* EdgeMagneticView.swift in Sources */, 7E386CCC248797A000556D3D /* ErrorHandling.swift in Sources */, + 7E616726249A17870066C43B /* ConferenceParticipantView.swift in Sources */, 7E386CCA2487977000556D3D /* MovingWithKeyboard.swift in Sources */, 7E386CDF2487983600556D3D /* UserDefault.swift in Sources */, + 7ECA29D124A508A100F3D1ED /* DarwinNotificationsService.swift in Sources */, 7E386CB1248793B800556D3D /* LoginViewController.swift in Sources */, 7E55DF37248829880067A214 /* AppLifeCycleDelegate.swift in Sources */, + 7E616725249A17820066C43B /* ConferenceView.swift in Sources */, 7E386CF42487B1F000556D3D /* MainViewController.swift in Sources */, 7E386CCD248797B300556D3D /* Color.swift in Sources */, 7E386D142488056000556D3D /* PressableButton.swift in Sources */, 7E386CDA2487981100556D3D /* AlertHelper.swift in Sources */, - 7E386CFB2487C0E400556D3D /* CallVideoView.swift in Sources */, 7E537DC924880B3300013D6E /* CallFailedViewController.swift in Sources */, 7E386CDC2487981800556D3D /* Tokens+VIAuthParams.swift in Sources */, 7E386CDD2487981E00556D3D /* PermissionsHelper.swift in Sources */, @@ -1565,6 +1836,51 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 7EF4B611249A4DF500081013 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 7EF4B62D249A4EB100081013 /* AuthService.swift in Sources */, + 7E8E1A02249A669400BC78F9 /* NibLoadable.swift in Sources */, + 7E8E1A10249A66F800BC78F9 /* ColoredButton.swift in Sources */, + 7E8E19FB249A4F7300BC78F9 /* UIExtensions.swift in Sources */, + 7EF4B62B249A4EA400081013 /* Storyboard.swift in Sources */, + 7E8E19FE249A668100BC78F9 /* DefaultCallFailedView.swift in Sources */, + 7E8E1A16249A672700BC78F9 /* CallOptionButton.swift in Sources */, + 7E8E1A0A249A66CC00BC78F9 /* DefaultMainView.swift in Sources */, + 7E8E1A1A249A674B00BC78F9 /* Tokens+VIAuthParams.swift in Sources */, + 7E8E1A0C249A66D800BC78F9 /* PermissionsHelper.swift in Sources */, + 7EF4B630249A4EC900081013 /* StoryAssembler.swift in Sources */, + 7EF4B633249A4ED600081013 /* CallFailedViewController.swift in Sources */, + 7E8E19FA249A4F6600BC78F9 /* AppLifeCycleDelegate.swift in Sources */, + 7EF4B636249A4EE600081013 /* CallVideoView.swift in Sources */, + 7E8E1A19249A673E00BC78F9 /* Tokens.swift in Sources */, + 7E8E1A0E249A66E900BC78F9 /* VoximplantSDKVersionContainable.swift in Sources */, + 7E8E1A11249A670000BC78F9 /* DefaultTextField.swift in Sources */, + 7E8E1A15249A672000BC78F9 /* UserDefault.swift in Sources */, + 7E8E1A0F249A66EE00BC78F9 /* MovingWithKeyboard.swift in Sources */, + 7E8E1A03249A669900BC78F9 /* PressableButton.swift in Sources */, + 7E8E1A01249A668C00BC78F9 /* DefaultIncomingCallView.swift in Sources */, + 7E8E1A17249A672E00BC78F9 /* CallOptionButtonModel.swift in Sources */, + 7EF4B631249A4ECE00081013 /* AppDelegate.swift in Sources */, + 7E8E19F9249A4F6000BC78F9 /* Loggable.swift in Sources */, + 7E8E1A12249A670800BC78F9 /* Color.swift in Sources */, + 7EF4B637249A4EEA00081013 /* MainViewController.swift in Sources */, + 7E8E1A04249A66A600BC78F9 /* Log.swift in Sources */, + 7EF4B62C249A4EAC00081013 /* CallManager.swift in Sources */, + 7EF4B638249A4EEE00081013 /* IncomingCallViewController.swift in Sources */, + 7E8E1A08249A66C300BC78F9 /* ErrorHandling.swift in Sources */, + 7E8E1A14249A671900BC78F9 /* Theme.swift in Sources */, + 7E8E1A18249A673900BC78F9 /* EdgeMagneticView.swift in Sources */, + 7E8E1A0D249A66E100BC78F9 /* Errors.swift in Sources */, + 7EF4B634249A4EDE00081013 /* CallViewController.swift in Sources */, + 7E8E1A07249A66BC00BC78F9 /* AlertHelper.swift in Sources */, + 7E8E1A09249A66C600BC78F9 /* LoadingShowable.swift in Sources */, + 7E8E1A05249A66AF00BC78F9 /* DefaultLoginView.swift in Sources */, + 7EF4B639249A4F0800081013 /* LoginViewController.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 8EEA98AC1FB5A1AD00E90861 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1591,6 +1907,23 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C560015B2497686500818028 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C56001852498C3C800818028 /* Errors.swift in Sources */, + C56001642497686500818028 /* SampleHandler.swift in Sources */, + 7ECA29D224A509CB00F3D1ED /* DarwinNotificationsService.swift in Sources */, + C560018D2499422800818028 /* Loggable.swift in Sources */, + C560018924990C8100818028 /* Log.swift in Sources */, + C56001832498BF1D00818028 /* Tokens.swift in Sources */, + C56001862498C3EF00818028 /* Tokens+VIAuthParams.swift in Sources */, + 7E7BE47824A6012700243B8E /* BroadcastError.swift in Sources */, + C56001842498C36200818028 /* UserDefault.swift in Sources */, + C560018F249A023A00818028 /* AuthService.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; C5A248B522B3F7140008FAF4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1682,6 +2015,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + C56001672497686500818028 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C560015E2497686500818028 /* ScreenSharingUploadAppex */; + targetProxy = C56001662497686500818028 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 7E386CB2248793B800556D3D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -1707,16 +2048,26 @@ name = Main.storyboard; sourceTree = ""; }; + 7EF4B5E5249A4DA600081013 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 7EF4B5E6249A4DA600081013 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 7E386CBB248793B900556D3D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_ENTITLEMENTS = ScreenSharing/ScreenSharing.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = W9BHJBL635; INFOPLIST_FILE = "$(SRCROOT)/ScreenSharing/Resources/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; @@ -1733,10 +2084,12 @@ 7E386CBC248793B900556D3D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_ENTITLEMENTS = ScreenSharing/ScreenSharing.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = W9BHJBL635; INFOPLIST_FILE = "$(SRCROOT)/ScreenSharing/Resources/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 11.0; @@ -1831,6 +2184,47 @@ }; name = Release; }; + 7EF4B627249A4DF700081013 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 3; + DEVELOPMENT_TEAM = W9BHJBL635; + INFOPLIST_FILE = "$(SRCROOT)/InAppScreenSharing/Resources/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 1.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.voximplant.demos.InAppScreenSharing; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 7EF4B628249A4DF700081013 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 3; + DEVELOPMENT_TEAM = W9BHJBL635; + INFOPLIST_FILE = "$(SRCROOT)/InAppScreenSharing/Resources/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + MARKETING_VERSION = 1.0; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.voximplant.demos.InAppScreenSharing; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; 8EEA98C01FB5A1AD00E90861 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1989,6 +2383,47 @@ }; name = Release; }; + C560016A2497686500818028 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_ENTITLEMENTS = ScreenSharingUploadAppex/ScreenSharingUploadAppex.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 4; + DEVELOPMENT_TEAM = W9BHJBL635; + INFOPLIST_FILE = ScreenSharingUploadAppex/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.voximplant.demos.ScreenSharing.ScreenSharingUploadAppex; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C560016B2497686500818028 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_OBJC_WEAK = YES; + CODE_SIGN_ENTITLEMENTS = ScreenSharingUploadAppex/ScreenSharingUploadAppex.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 4; + DEVELOPMENT_TEAM = W9BHJBL635; + INFOPLIST_FILE = ScreenSharingUploadAppex/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + MTL_FAST_MATH = YES; + PRODUCT_BUNDLE_IDENTIFIER = com.voximplant.demos.ScreenSharing.ScreenSharingUploadAppex; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; C5A248C822B3F7150008FAF4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2109,6 +2544,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 7EF4B626249A4DF700081013 /* Build configuration list for PBXNativeTarget "InAppScreenSharing" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 7EF4B627249A4DF700081013 /* Debug */, + 7EF4B628249A4DF700081013 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 8EEA98AB1FB5A1AC00E90861 /* Build configuration list for PBXProject "Swift" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -2127,6 +2571,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + C560016C2497686500818028 /* Build configuration list for PBXNativeTarget "ScreenSharingUploadAppex" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C560016A2497686500818028 /* Debug */, + C560016B2497686500818028 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; C5A248CA22B3F7150008FAF4 /* Build configuration list for PBXNativeTarget "AudioCall" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Swift.xcodeproj/xcshareddata/xcschemes/ScreenSharing.xcscheme b/Swift.xcodeproj/xcshareddata/xcschemes/ScreenSharing.xcscheme new file mode 100644 index 0000000..db5b7b8 --- /dev/null +++ b/Swift.xcodeproj/xcshareddata/xcschemes/ScreenSharing.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Swift.xcodeproj/xcshareddata/xcschemes/ScreenSharingUploadAppex.xcscheme b/Swift.xcodeproj/xcshareddata/xcschemes/ScreenSharingUploadAppex.xcscheme new file mode 100644 index 0000000..68ae189 --- /dev/null +++ b/Swift.xcodeproj/xcshareddata/xcschemes/ScreenSharingUploadAppex.xcscheme @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/VideoCallKit/Resources/Info.plist b/VideoCallKit/Resources/Info.plist index 2d6ce4d..971cbc2 100644 --- a/VideoCallKit/Resources/Info.plist +++ b/VideoCallKit/Resources/Info.plist @@ -33,8 +33,6 @@ UILaunchStoryboardName LaunchScreen - UIMainStoryboardFile - Main UIRequiredDeviceCapabilities armv7