session-ios/Session/Calls/CallVC.swift

776 lines
30 KiB
Swift
Raw Normal View History

// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
import UIKit
import YYImage
import MediaPlayer
import SessionUIKit
import SessionMessagingKit
import SessionUtilitiesKit
2021-08-16 06:40:07 +02:00
final class CallVC: UIViewController, VideoPreviewDelegate {
private static let avatarRadius: CGFloat = (isIPhone6OrSmaller ? 100 : 120)
private static let floatingVideoViewWidth: CGFloat = (UIDevice.current.isIPad ? 160 : 80)
private static let floatingVideoViewHeight: CGFloat = (UIDevice.current.isIPad ? 346: 173)
2021-10-28 08:02:41 +02:00
let call: SessionCall
2021-11-30 01:57:56 +01:00
var latestKnownAudioOutputDeviceName: String?
2021-12-01 00:38:02 +01:00
var durationTimer: Timer?
2021-12-01 00:39:27 +01:00
var duration: Int = 0
2021-10-14 07:01:50 +02:00
var shouldRestartCamera = true
2021-10-28 08:02:41 +02:00
weak var conversationVC: ConversationVC? = nil
2021-08-16 06:40:07 +02:00
lazy var cameraManager: CameraManager = {
let result = CameraManager()
result.delegate = self
return result
}()
2023-02-14 06:34:27 +01:00
enum FloatingViewVideoSource {
case local
case remote
}
var floatingViewVideoSource: FloatingViewVideoSource = .local
// MARK: - UI Components
private lazy var floatingLocalVideoView: LocalVideoView = {
let result = LocalVideoView()
2023-02-15 05:32:40 +01:00
result.alpha = 0
result.themeBackgroundColor = .backgroundSecondary
result.set(.width, to: Self.floatingVideoViewWidth)
result.set(.height, to: Self.floatingVideoViewHeight)
2021-09-08 06:55:52 +02:00
return result
}()
private lazy var floatingRemoteVideoView: RemoteVideoView = {
let result = RemoteVideoView()
result.alpha = 0
result.themeBackgroundColor = .backgroundSecondary
result.set(.width, to: Self.floatingVideoViewWidth)
result.set(.height, to: Self.floatingVideoViewHeight)
2021-09-08 06:55:52 +02:00
return result
}()
private lazy var fullScreenLocalVideoView: LocalVideoView = {
let result = LocalVideoView()
result.alpha = 0
result.themeBackgroundColor = .backgroundPrimary
result.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleFullScreenVideoViewTapped)))
return result
}()
private lazy var fullScreenRemoteVideoView: RemoteVideoView = {
let result = RemoteVideoView()
result.alpha = 0
result.themeBackgroundColor = .backgroundPrimary
result.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleFullScreenVideoViewTapped)))
return result
}()
private lazy var floatingViewContainer: UIView = {
let result = UIView()
2023-02-14 06:34:27 +01:00
result.isHidden = true
2023-02-16 01:38:55 +01:00
result.clipsToBounds = true
result.layer.cornerRadius = UIDevice.current.isIPad ? 20 : 10
result.layer.masksToBounds = true
2023-02-16 00:34:03 +01:00
result.themeBackgroundColor = .backgroundSecondary
result.makeViewDraggable()
2023-02-16 00:34:03 +01:00
let noVideoIcon: UIImageView = UIImageView(
image: UIImage(systemName: "video.slash")?
.withRenderingMode(.alwaysTemplate)
)
noVideoIcon.themeTintColor = .textPrimary
2023-02-16 01:38:55 +01:00
noVideoIcon.set(.width, to: 34)
noVideoIcon.set(.height, to: 28)
2023-02-16 00:34:03 +01:00
result.addSubview(noVideoIcon)
noVideoIcon.center(in: result)
result.addSubview(floatingLocalVideoView)
2023-02-15 05:32:40 +01:00
floatingLocalVideoView.pin(to: result)
result.addSubview(floatingRemoteVideoView)
2023-02-15 05:32:40 +01:00
floatingRemoteVideoView.pin(to: result)
2023-02-16 00:34:03 +01:00
let swappingVideoIcon: UIImageView = UIImageView(
image: UIImage(systemName: "arrow.2.squarepath")?
.withRenderingMode(.alwaysTemplate)
)
swappingVideoIcon.themeTintColor = .textPrimary
2023-02-16 01:38:55 +01:00
swappingVideoIcon.set(.width, to: 16)
swappingVideoIcon.set(.height, to: 12)
2023-02-16 00:34:03 +01:00
result.addSubview(swappingVideoIcon)
2023-02-16 01:38:55 +01:00
swappingVideoIcon.pin(.top, to: .top, of: result, withInset: Values.smallSpacing)
swappingVideoIcon.pin(.trailing, to: .trailing, of: result, withInset: -Values.smallSpacing)
2023-02-16 00:34:03 +01:00
result.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(switchVideo)))
2021-08-18 02:45:55 +02:00
return result
}()
private lazy var fadeView: GradientView = {
let height: CGFloat = ((UIApplication.shared.keyWindow?.safeAreaInsets.top)
.map { $0 + Values.veryLargeSpacing })
.defaulting(to: 64)
let result: GradientView = GradientView()
result.themeBackgroundGradient = [
.value(.backgroundPrimary, alpha: 0.4),
.value(.backgroundPrimary, alpha: 0)
]
2021-08-18 01:56:28 +02:00
result.set(.height, to: height)
2021-08-18 01:56:28 +02:00
return result
}()
2021-12-01 00:38:02 +01:00
private lazy var profilePictureView: UIImageView = {
let result = UIImageView()
result.image = self.call.profilePicture
result.set(.width, to: CallVC.avatarRadius * 2)
result.set(.height, to: CallVC.avatarRadius * 2)
result.layer.cornerRadius = CallVC.avatarRadius
result.layer.masksToBounds = true
result.contentMode = .scaleAspectFill
return result
}()
private lazy var animatedImageView: YYAnimatedImageView = {
let result: YYAnimatedImageView = YYAnimatedImageView()
result.image = self.call.animatedProfilePicture
result.set(.width, to: CallVC.avatarRadius * 2)
result.set(.height, to: CallVC.avatarRadius * 2)
result.layer.cornerRadius = CallVC.avatarRadius
2021-12-01 00:38:02 +01:00
result.layer.masksToBounds = true
result.contentMode = .scaleAspectFill
result.isHidden = (self.call.animatedProfilePicture == nil)
2021-12-01 00:38:02 +01:00
return result
}()
private lazy var minimizeButton: UIButton = {
2021-08-18 01:56:28 +02:00
let result = UIButton(type: .custom)
result.setImage(
UIImage(named: "Minimize")?
.withRenderingMode(.alwaysTemplate),
for: .normal
)
result.themeTintColor = .textPrimary
result.addTarget(self, action: #selector(minimize), for: UIControl.Event.touchUpInside)
result.isHidden = !call.hasConnected
2021-08-18 01:56:28 +02:00
result.set(.width, to: 60)
result.set(.height, to: 60)
2021-08-18 02:03:10 +02:00
return result
}()
2021-09-23 04:55:28 +02:00
private lazy var answerButton: UIButton = {
let result = UIButton(type: .custom)
result.setImage(
UIImage(named: "AnswerCall")?
.withRenderingMode(.alwaysTemplate),
for: .normal
)
result.themeTintColor = .white
result.themeBackgroundColor = .callAccept_background
result.layer.cornerRadius = 30
result.addTarget(self, action: #selector(answerCall), for: UIControl.Event.touchUpInside)
2021-11-10 04:31:02 +01:00
result.isHidden = call.hasStartedConnecting
2021-09-23 04:55:28 +02:00
result.set(.width, to: 60)
result.set(.height, to: 60)
2021-09-23 04:55:28 +02:00
return result
}()
2021-09-08 06:55:52 +02:00
private lazy var hangUpButton: UIButton = {
let result = UIButton(type: .custom)
result.accessibilityLabel = "End call button"
result.setImage(
UIImage(named: "EndCall")?
.withRenderingMode(.alwaysTemplate),
for: .normal
)
result.themeTintColor = .white
result.themeBackgroundColor = .callDecline_background
2021-09-08 06:55:52 +02:00
result.layer.cornerRadius = 30
2021-09-23 04:55:28 +02:00
result.addTarget(self, action: #selector(endCall), for: UIControl.Event.touchUpInside)
result.set(.width, to: 60)
result.set(.height, to: 60)
2021-09-23 04:55:28 +02:00
return result
}()
private lazy var responsePanel: UIStackView = {
let result = UIStackView(arrangedSubviews: [hangUpButton, answerButton])
result.axis = .horizontal
result.spacing = Values.veryLargeSpacing * 2 + 40
2021-09-08 06:55:52 +02:00
return result
}()
private lazy var switchCameraButton: UIButton = {
let result = UIButton(type: .custom)
2021-11-03 05:31:50 +01:00
result.isEnabled = call.isVideoEnabled
result.setImage(
UIImage(named: "SwitchCamera")?
.withRenderingMode(.alwaysTemplate),
for: .normal
)
result.themeTintColor = .textPrimary
result.themeBackgroundColor = .backgroundSecondary
2021-09-08 06:55:52 +02:00
result.layer.cornerRadius = 30
result.addTarget(self, action: #selector(switchCamera), for: UIControl.Event.touchUpInside)
result.set(.width, to: 60)
result.set(.height, to: 60)
2021-09-08 06:55:52 +02:00
return result
}()
private lazy var switchAudioButton: UIButton = {
let result = UIButton(type: .custom)
result.setImage(
UIImage(named: "AudioOff")?
.withRenderingMode(.alwaysTemplate),
for: .normal
)
result.themeTintColor = (call.isMuted ?
.white :
.textPrimary
)
result.themeBackgroundColor = (call.isMuted ?
.danger :
.backgroundSecondary
)
2021-09-08 06:55:52 +02:00
result.layer.cornerRadius = 30
result.addTarget(self, action: #selector(switchAudio), for: UIControl.Event.touchUpInside)
result.set(.width, to: 60)
result.set(.height, to: 60)
2021-09-08 06:55:52 +02:00
return result
}()
2021-09-23 04:55:28 +02:00
private lazy var videoButton: UIButton = {
let result = UIButton(type: .custom)
result.setImage(
UIImage(named: "VideoCall")?
.withRenderingMode(.alwaysTemplate),
for: .normal
)
result.themeTintColor = .textPrimary
result.themeBackgroundColor = .backgroundSecondary
2021-09-23 04:55:28 +02:00
result.layer.cornerRadius = 30
result.addTarget(self, action: #selector(operateCamera), for: UIControl.Event.touchUpInside)
result.set(.width, to: 60)
result.set(.height, to: 60)
2021-09-23 04:55:28 +02:00
return result
}()
2021-11-15 02:22:31 +01:00
private lazy var volumeView: MPVolumeView = {
let result = MPVolumeView()
2021-11-15 06:02:24 +01:00
result.showsVolumeSlider = false
2021-11-15 02:22:31 +01:00
result.showsRouteButton = true
result.setRouteButtonImage(
UIImage(named: "Speaker")?
.withRenderingMode(.alwaysTemplate),
for: .normal
)
result.themeTintColor = .textPrimary
result.themeBackgroundColor = .backgroundSecondary
result.layer.cornerRadius = 30
2021-11-15 02:22:31 +01:00
result.set(.width, to: 60)
result.set(.height, to: 60)
2021-11-15 02:22:31 +01:00
return result
}()
2021-09-23 04:55:28 +02:00
private lazy var operationPanel: UIStackView = {
2021-11-15 02:22:31 +01:00
let result = UIStackView(arrangedSubviews: [switchCameraButton, videoButton, switchAudioButton, volumeView])
2021-09-23 04:55:28 +02:00
result.axis = .horizontal
result.spacing = Values.veryLargeSpacing
2021-09-23 04:55:28 +02:00
return result
}()
2021-08-18 02:33:33 +02:00
private lazy var titleLabel: UILabel = {
let result: UILabel = UILabel()
2021-08-18 02:03:10 +02:00
result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
result.themeTextColor = .textPrimary
2021-08-18 02:03:10 +02:00
result.textAlignment = .center
2021-08-18 01:56:28 +02:00
return result
}()
2021-08-18 05:07:15 +02:00
private lazy var callInfoLabel: UILabel = {
let result: UILabel = UILabel()
2021-08-18 02:33:33 +02:00
result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
result.themeTextColor = .textPrimary
2021-08-18 02:33:33 +02:00
result.textAlignment = .center
result.isHidden = call.hasConnected
2021-11-10 04:31:02 +01:00
if call.hasStartedConnecting { result.text = "Connecting..." }
2021-08-18 02:33:33 +02:00
return result
}()
2022-04-07 09:02:57 +02:00
private lazy var callDurationLabel: UILabel = {
let result = UILabel()
result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
result.themeTextColor = .textPrimary
2022-04-07 09:02:57 +02:00
result.textAlignment = .center
result.isHidden = true
2022-04-07 09:02:57 +02:00
return result
}()
// MARK: - Lifecycle
2021-10-28 08:02:41 +02:00
init(for call: SessionCall) {
self.call = call
super.init(nibName: nil, bundle: nil)
2021-11-03 05:31:50 +01:00
setupStateChangeCallbacks()
self.modalPresentationStyle = .overFullScreen
self.modalTransitionStyle = .crossDissolve
}
func setupStateChangeCallbacks() {
self.call.remoteVideoStateDidChange = { isEnabled in
DispatchQueue.main.async {
UIView.animate(withDuration: 0.25) {
2023-02-14 06:34:27 +01:00
let remoteVideoView: RemoteVideoView = self.floatingViewVideoSource == .remote ? self.floatingRemoteVideoView : self.fullScreenRemoteVideoView
remoteVideoView.alpha = isEnabled ? 1 : 0
2021-11-03 05:31:50 +01:00
}
2021-12-01 03:25:12 +01:00
if self.callInfoLabel.alpha < 0.5 {
UIView.animate(withDuration: 0.25) {
self.operationPanel.alpha = 1
self.responsePanel.alpha = 1
self.callInfoLabel.alpha = 1
}
}
2021-11-03 05:31:50 +01:00
}
}
2021-11-09 01:53:38 +01:00
self.call.hasStartedConnectingDidChange = {
DispatchQueue.main.async {
self.callInfoLabel.text = "Connecting..."
self.answerButton.alpha = 0
UIView.animate(
withDuration: 0.5,
delay: 0,
usingSpringWithDamping: 1,
initialSpringVelocity: 1,
options: .curveEaseIn,
animations: { [weak self] in
self?.answerButton.isHidden = true
},
completion: nil
)
2021-11-09 01:53:38 +01:00
}
}
self.call.hasConnectedDidChange = { [weak self] in
2021-11-03 05:31:50 +01:00
DispatchQueue.main.async {
2021-11-29 06:32:02 +01:00
CallRingTonePlayer.shared.stopPlayingRingTone()
self?.callInfoLabel.text = "Connected"
self?.minimizeButton.isHidden = false
self?.durationTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self?.updateDuration()
2021-12-01 00:38:02 +01:00
}
self?.callInfoLabel.isHidden = true
self?.callDurationLabel.isHidden = false
2021-11-03 05:31:50 +01:00
}
}
self.call.hasEndedDidChange = { [weak self] in
2021-11-09 01:53:38 +01:00
DispatchQueue.main.async {
self?.durationTimer?.invalidate()
self?.durationTimer = nil
self?.handleEndCallMessage()
2021-11-09 01:53:38 +01:00
}
2021-10-28 08:02:41 +02:00
}
self.call.hasStartedReconnecting = { [weak self] in
DispatchQueue.main.async {
self?.callInfoLabel.isHidden = false
self?.callDurationLabel.isHidden = true
self?.callInfoLabel.text = "Reconnecting..."
}
}
self.call.hasReconnected = { [weak self] in
DispatchQueue.main.async {
self?.callInfoLabel.isHidden = true
self?.callDurationLabel.isHidden = false
}
}
}
required init(coder: NSCoder) { preconditionFailure("Use init(for:) instead.") }
2021-08-16 06:40:07 +02:00
override func viewDidLoad() {
super.viewDidLoad()
view.themeBackgroundColor = .backgroundPrimary
2021-08-16 06:40:07 +02:00
setUpViewHierarchy()
2021-10-14 07:01:50 +02:00
if shouldRestartCamera { cameraManager.prepare() }
_ = call.videoCapturer // Force the lazy var to instantiate
2021-10-28 08:02:41 +02:00
titleLabel.text = self.call.contactName
AppEnvironment.shared.callManager.startCall(call) { [weak self] error in
2021-11-09 01:53:38 +01:00
DispatchQueue.main.async {
if let _ = error {
self?.callInfoLabel.text = "Can't start a call."
self?.endCall()
}
else {
self?.callInfoLabel.text = "Ringing..."
self?.answerButton.isHidden = true
2021-11-09 01:53:38 +01:00
}
}
}
2022-02-15 00:12:12 +01:00
setupOrientationMonitoring()
NotificationCenter.default.addObserver(self, selector: #selector(audioRouteDidChange), name: AVAudioSession.routeChangeNotification, object: nil)
}
deinit {
2022-02-15 00:12:12 +01:00
UIDevice.current.endGeneratingDeviceOrientationNotifications()
NotificationCenter.default.removeObserver(self)
2021-08-16 06:40:07 +02:00
}
func setUpViewHierarchy() {
2021-12-01 00:38:02 +01:00
// Profile picture container
let profilePictureContainer = UIView()
view.addSubview(profilePictureContainer)
2021-08-16 07:00:38 +02:00
// Remote video view
2023-02-14 06:34:27 +01:00
call.attachRemoteVideoRenderer(fullScreenRemoteVideoView)
view.addSubview(fullScreenRemoteVideoView)
fullScreenRemoteVideoView.translatesAutoresizingMaskIntoConstraints = false
fullScreenRemoteVideoView.pin(to: view)
2021-08-16 07:00:38 +02:00
// Local video view
2023-02-14 06:34:27 +01:00
call.attachLocalVideoRenderer(floatingLocalVideoView)
view.addSubview(fullScreenLocalVideoView)
fullScreenLocalVideoView.translatesAutoresizingMaskIntoConstraints = false
fullScreenLocalVideoView.pin(to: view)
2021-08-18 01:56:28 +02:00
// Fade view
view.addSubview(fadeView)
fadeView.translatesAutoresizingMaskIntoConstraints = false
fadeView.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: view)
2021-10-21 07:28:48 +02:00
// Minimize button
view.addSubview(minimizeButton)
minimizeButton.translatesAutoresizingMaskIntoConstraints = false
minimizeButton.pin(.left, to: .left, of: view)
minimizeButton.pin(.top, to: .top, of: view, withInset: 32)
2021-08-18 05:07:15 +02:00
// Title label
2021-08-18 02:33:33 +02:00
view.addSubview(titleLabel)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.center(.vertical, in: minimizeButton)
titleLabel.pin(.leading, to: .leading, of: view, withInset: Values.largeSpacing)
titleLabel.pin(.trailing, to: .trailing, of: view, withInset: -Values.largeSpacing)
2021-09-23 04:55:28 +02:00
// Response Panel
view.addSubview(responsePanel)
responsePanel.center(.horizontal, in: view)
Merge remote-tracking branch 'upstream/dev' into feature/theming # Conflicts: # Session.xcodeproj/project.pbxproj # Session/Closed Groups/NewClosedGroupVC.swift # Session/Conversations/ConversationVC+Interaction.swift # Session/Conversations/Message Cells/CallMessageCell.swift # Session/Conversations/Views & Modals/JoinOpenGroupModal.swift # Session/Home/HomeVC.swift # Session/Home/New Conversation/NewDMVC.swift # Session/Home/NewConversationButtonSet.swift # Session/Meta/Translations/de.lproj/Localizable.strings # Session/Meta/Translations/en.lproj/Localizable.strings # Session/Meta/Translations/es.lproj/Localizable.strings # Session/Meta/Translations/fa.lproj/Localizable.strings # Session/Meta/Translations/fi.lproj/Localizable.strings # Session/Meta/Translations/fr.lproj/Localizable.strings # Session/Meta/Translations/hi.lproj/Localizable.strings # Session/Meta/Translations/hr.lproj/Localizable.strings # Session/Meta/Translations/id-ID.lproj/Localizable.strings # Session/Meta/Translations/it.lproj/Localizable.strings # Session/Meta/Translations/ja.lproj/Localizable.strings # Session/Meta/Translations/nl.lproj/Localizable.strings # Session/Meta/Translations/pl.lproj/Localizable.strings # Session/Meta/Translations/pt_BR.lproj/Localizable.strings # Session/Meta/Translations/ru.lproj/Localizable.strings # Session/Meta/Translations/si.lproj/Localizable.strings # Session/Meta/Translations/sk.lproj/Localizable.strings # Session/Meta/Translations/sv.lproj/Localizable.strings # Session/Meta/Translations/th.lproj/Localizable.strings # Session/Meta/Translations/vi-VN.lproj/Localizable.strings # Session/Meta/Translations/zh-Hant.lproj/Localizable.strings # Session/Meta/Translations/zh_CN.lproj/Localizable.strings # Session/Open Groups/JoinOpenGroupVC.swift # Session/Open Groups/OpenGroupSuggestionGrid.swift # Session/Settings/SettingsVC.swift # Session/Shared/BaseVC.swift # Session/Shared/OWSQRCodeScanningViewController.m # Session/Shared/ScanQRCodeWrapperVC.swift # Session/Shared/UserCell.swift # SessionMessagingKit/Configuration.swift # SessionShareExtension/SAEScreenLockViewController.swift # SessionUIKit/Style Guide/Gradients.swift # SignalUtilitiesKit/Media Viewing & Editing/OWSViewController+ImageEditor.swift # SignalUtilitiesKit/Screen Lock/ScreenLockViewController.m
2022-09-26 03:16:47 +02:00
responsePanel.pin(.bottom, to: .bottom, of: view.safeAreaLayoutGuide, withInset: -Values.smallSpacing)
2021-09-23 04:55:28 +02:00
// Operation Panel
view.addSubview(operationPanel)
operationPanel.center(.horizontal, in: view)
operationPanel.pin(.bottom, to: .top, of: responsePanel, withInset: -Values.veryLargeSpacing)
2021-12-01 00:38:02 +01:00
// Profile picture view
profilePictureContainer.pin(.top, to: .bottom, of: fadeView)
profilePictureContainer.pin(.bottom, to: .top, of: operationPanel)
profilePictureContainer.pin([ UIView.HorizontalEdge.left, UIView.HorizontalEdge.right ], to: view)
profilePictureContainer.addSubview(profilePictureView)
profilePictureContainer.addSubview(animatedImageView)
2021-12-01 00:38:02 +01:00
profilePictureView.center(in: profilePictureContainer)
animatedImageView.center(in: profilePictureContainer)
2021-12-01 00:38:02 +01:00
// Call info label
let callInfoLabelContainer = UIView()
view.addSubview(callInfoLabelContainer)
callInfoLabelContainer.pin(.top, to: .bottom, of: profilePictureView)
callInfoLabelContainer.pin(.bottom, to: .bottom, of: profilePictureContainer)
callInfoLabelContainer.pin([ UIView.HorizontalEdge.left, UIView.HorizontalEdge.right ], to: view)
callInfoLabelContainer.addSubview(callInfoLabel)
2022-04-07 09:02:57 +02:00
callInfoLabelContainer.addSubview(callDurationLabel)
2021-12-01 00:38:02 +01:00
callInfoLabel.translatesAutoresizingMaskIntoConstraints = false
callInfoLabel.center(in: callInfoLabelContainer)
2022-04-07 09:02:57 +02:00
callDurationLabel.translatesAutoresizingMaskIntoConstraints = false
callDurationLabel.center(in: callInfoLabelContainer)
2021-09-22 09:06:14 +02:00
}
2023-02-14 06:34:27 +01:00
private func addFloatingVideoView() {
let safeAreaInsets = UIApplication.shared.keyWindow?.safeAreaInsets
2023-02-14 06:34:27 +01:00
CurrentAppContext().mainWindow?.addSubview(floatingViewContainer)
floatingViewContainer.autoPinEdge(toSuperviewEdge: .right, withInset: Values.smallSpacing)
let topMargin = (safeAreaInsets?.top ?? 0) + Values.veryLargeSpacing
2023-02-14 06:34:27 +01:00
floatingViewContainer.autoPinEdge(toSuperviewEdge: .top, withInset: topMargin)
}
2021-08-16 06:40:07 +02:00
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
2021-11-03 05:31:50 +01:00
if (call.isVideoEnabled && shouldRestartCamera) { cameraManager.start() }
2021-10-14 07:01:50 +02:00
shouldRestartCamera = true
2023-02-14 06:34:27 +01:00
addFloatingVideoView()
let remoteVideoView: RemoteVideoView = self.floatingViewVideoSource == .remote ? self.floatingRemoteVideoView : self.fullScreenRemoteVideoView
remoteVideoView.alpha = (call.isRemoteVideoEnabled ? 1 : 0)
2021-08-16 06:40:07 +02:00
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
2021-11-03 05:31:50 +01:00
if (call.isVideoEnabled && shouldRestartCamera) { cameraManager.stop() }
2023-02-14 06:34:27 +01:00
floatingViewContainer.removeFromSuperview()
2021-10-26 06:48:31 +02:00
}
2022-02-15 00:12:12 +01:00
// MARK: - Orientation
private func setupOrientationMonitoring() {
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
NotificationCenter.default.addObserver(self, selector: #selector(didChangeDeviceOrientation), name: UIDevice.orientationDidChangeNotification, object: UIDevice.current)
}
@objc func didChangeDeviceOrientation(notification: Notification) {
2022-09-30 08:44:48 +02:00
if UIDevice.current.isIPad { return }
2022-02-15 00:12:12 +01:00
func rotateAllButtons(rotationAngle: CGFloat) {
let transform = CGAffineTransform(rotationAngle: rotationAngle)
2022-02-15 00:12:12 +01:00
UIView.animate(withDuration: 0.2) {
self.answerButton.transform = transform
self.hangUpButton.transform = transform
self.switchAudioButton.transform = transform
self.switchCameraButton.transform = transform
self.videoButton.transform = transform
self.volumeView.transform = transform
}
}
switch UIDevice.current.orientation {
case .portrait: rotateAllButtons(rotationAngle: 0)
case .portraitUpsideDown: rotateAllButtons(rotationAngle: .pi)
case .landscapeLeft: rotateAllButtons(rotationAngle: .halfPi)
case .landscapeRight: rotateAllButtons(rotationAngle: .pi + .halfPi)
default: break
2022-02-15 00:12:12 +01:00
}
}
// MARK: Call signalling
2021-08-18 05:07:15 +02:00
func handleAnswerMessage(_ message: CallMessage) {
2021-08-19 05:42:46 +02:00
callInfoLabel.text = "Connecting..."
2021-08-18 05:07:15 +02:00
}
2021-11-10 04:31:02 +01:00
func handleEndCallMessage() {
2022-04-05 08:35:09 +02:00
SNLog("[Calls] Ending call.")
2022-04-07 09:02:57 +02:00
self.callInfoLabel.isHidden = false
self.callDurationLabel.isHidden = true
self.callInfoLabel.text = "Call Ended"
2021-08-18 02:33:33 +02:00
UIView.animate(withDuration: 0.25) {
2023-02-14 06:34:27 +01:00
let remoteVideoView: RemoteVideoView = self.floatingViewVideoSource == .remote ? self.floatingRemoteVideoView : self.fullScreenRemoteVideoView
remoteVideoView.alpha = 0
2021-12-14 04:34:59 +01:00
self.operationPanel.alpha = 1
self.responsePanel.alpha = 1
self.callInfoLabel.alpha = 1
2021-08-18 02:33:33 +02:00
}
Timer.scheduledTimer(withTimeInterval: 2, repeats: false) { [weak self] _ in
self?.conversationVC?.showInputAccessoryView()
self?.presentingViewController?.dismiss(animated: true, completion: nil)
2021-08-18 02:33:33 +02:00
}
2021-08-16 06:40:07 +02:00
}
2021-08-18 01:56:28 +02:00
2021-09-23 04:55:28 +02:00
@objc private func answerCall() {
AppEnvironment.shared.callManager.answerCall(call) { [weak self] error in
DispatchQueue.main.async {
if let _ = error {
self?.callInfoLabel.text = "Can't answer the call."
self?.endCall()
2021-11-09 01:53:38 +01:00
}
2021-10-25 02:49:54 +02:00
}
2021-09-23 04:55:28 +02:00
}
}
@objc private func endCall() {
AppEnvironment.shared.callManager.endCall(call) { [weak self] error in
2021-11-09 01:53:38 +01:00
if let _ = error {
self?.call.endSessionCall()
2021-11-09 01:53:38 +01:00
AppEnvironment.shared.callManager.reportCurrentCallEnded(reason: nil)
}
DispatchQueue.main.async {
self?.conversationVC?.showInputAccessoryView()
self?.presentingViewController?.dismiss(animated: true, completion: nil)
}
2021-11-09 01:53:38 +01:00
}
2021-08-18 01:56:28 +02:00
}
2021-09-08 06:55:52 +02:00
2021-12-01 00:38:02 +01:00
@objc private func updateDuration() {
2022-04-07 09:02:57 +02:00
callDurationLabel.text = String(format: "%.2d:%.2d", duration/60, duration%60)
2021-12-01 00:39:27 +01:00
duration += 1
2021-12-01 00:38:02 +01:00
}
// MARK: - Minimize to a floating view
@objc private func minimize() {
2021-10-14 07:01:50 +02:00
self.shouldRestartCamera = false
self.conversationVC?.showInputAccessoryView()
2021-10-11 04:14:39 +02:00
let miniCallView = MiniCallView(from: self)
miniCallView.show()
2021-10-11 04:14:39 +02:00
presentingViewController?.dismiss(animated: true, completion: nil)
2021-09-23 04:55:28 +02:00
}
// MARK: - Video and Audio
2021-09-23 04:55:28 +02:00
@objc private func operateCamera() {
2021-11-03 05:31:50 +01:00
if (call.isVideoEnabled) {
2023-02-14 06:34:27 +01:00
floatingViewContainer.isHidden = true
2021-09-23 04:55:28 +02:00
cameraManager.stop()
videoButton.themeTintColor = .textPrimary
videoButton.themeBackgroundColor = .backgroundSecondary
2021-09-23 04:55:28 +02:00
switchCameraButton.isEnabled = false
2021-11-03 05:31:50 +01:00
call.isVideoEnabled = false
}
else {
guard Permissions.requestCameraPermissionIfNeeded() else { return }
2021-10-21 07:28:48 +02:00
let previewVC = VideoPreviewVC()
previewVC.delegate = self
present(previewVC, animated: true, completion: nil)
2021-09-22 06:54:26 +02:00
}
2021-10-21 07:28:48 +02:00
}
func cameraDidConfirmTurningOn() {
2023-02-14 06:34:27 +01:00
floatingViewContainer.isHidden = false
2023-02-15 05:32:40 +01:00
let localVideoView: LocalVideoView = self.floatingViewVideoSource == .local ? self.floatingLocalVideoView : self.fullScreenLocalVideoView
localVideoView.alpha = 1
2021-10-21 07:28:48 +02:00
cameraManager.prepare()
cameraManager.start()
videoButton.themeTintColor = .backgroundSecondary
videoButton.themeBackgroundColor = .textPrimary
2021-10-21 07:28:48 +02:00
switchCameraButton.isEnabled = true
2021-11-03 05:31:50 +01:00
call.isVideoEnabled = true
}
@objc private func switchVideo() {
2023-02-14 06:34:27 +01:00
if self.floatingViewVideoSource == .remote {
call.removeRemoteVideoRenderer(self.floatingRemoteVideoView)
call.removeLocalVideoRenderer(self.fullScreenLocalVideoView)
self.floatingRemoteVideoView.alpha = 0
2023-02-15 06:31:06 +01:00
self.floatingLocalVideoView.alpha = call.isVideoEnabled ? 1 : 0
self.fullScreenRemoteVideoView.alpha = call.isRemoteVideoEnabled ? 1 : 0
2023-02-14 06:34:27 +01:00
self.fullScreenLocalVideoView.alpha = 0
self.floatingViewVideoSource = .local
call.attachRemoteVideoRenderer(self.fullScreenRemoteVideoView)
call.attachLocalVideoRenderer(self.floatingLocalVideoView)
} else {
call.removeRemoteVideoRenderer(self.fullScreenRemoteVideoView)
call.removeLocalVideoRenderer(self.floatingLocalVideoView)
2023-02-15 06:31:06 +01:00
self.floatingRemoteVideoView.alpha = call.isRemoteVideoEnabled ? 1 : 0
2023-02-14 06:34:27 +01:00
self.floatingLocalVideoView.alpha = 0
self.fullScreenRemoteVideoView.alpha = 0
2023-02-15 06:31:06 +01:00
self.fullScreenLocalVideoView.alpha = call.isVideoEnabled ? 1 : 0
2023-02-14 06:34:27 +01:00
self.floatingViewVideoSource = .remote
call.attachRemoteVideoRenderer(self.floatingRemoteVideoView)
call.attachLocalVideoRenderer(self.fullScreenLocalVideoView)
}
}
2021-09-08 06:55:52 +02:00
@objc private func switchCamera() {
cameraManager.switchCamera()
}
@objc private func switchAudio() {
2021-11-03 05:31:50 +01:00
if call.isMuted {
switchAudioButton.themeTintColor = .textPrimary
switchAudioButton.themeBackgroundColor = .backgroundSecondary
2021-11-03 05:31:50 +01:00
call.isMuted = false
}
else {
switchAudioButton.themeTintColor = .white
switchAudioButton.themeBackgroundColor = .danger
2021-11-03 05:31:50 +01:00
call.isMuted = true
2021-09-08 06:55:52 +02:00
}
}
@objc private func audioRouteDidChange() {
let currentSession = AVAudioSession.sharedInstance()
let currentRoute = currentSession.currentRoute
if let currentOutput = currentRoute.outputs.first {
2021-11-30 01:57:56 +01:00
if let latestKnownAudioOutputDeviceName = latestKnownAudioOutputDeviceName, currentOutput.portName == latestKnownAudioOutputDeviceName { return }
2021-11-30 01:57:56 +01:00
latestKnownAudioOutputDeviceName = currentOutput.portName
switch currentOutput.portType {
case .builtInSpeaker:
let image = UIImage(named: "Speaker")?.withRenderingMode(.alwaysTemplate)
volumeView.setRouteButtonImage(image, for: .normal)
volumeView.themeTintColor = .backgroundSecondary
volumeView.themeBackgroundColor = .textPrimary
case .headphones:
let image = UIImage(named: "Headsets")?.withRenderingMode(.alwaysTemplate)
volumeView.setRouteButtonImage(image, for: .normal)
volumeView.themeTintColor = .backgroundSecondary
volumeView.themeBackgroundColor = .textPrimary
case .bluetoothLE: fallthrough
case .bluetoothA2DP:
let image = UIImage(named: "Bluetooth")?.withRenderingMode(.alwaysTemplate)
volumeView.setRouteButtonImage(image, for: .normal)
volumeView.themeTintColor = .backgroundSecondary
volumeView.themeBackgroundColor = .textPrimary
case .bluetoothHFP:
let image = UIImage(named: "Airpods")?.withRenderingMode(.alwaysTemplate)
volumeView.setRouteButtonImage(image, for: .normal)
volumeView.themeTintColor = .backgroundSecondary
volumeView.themeBackgroundColor = .textPrimary
case .builtInReceiver: fallthrough
default:
let image = UIImage(named: "Speaker")?.withRenderingMode(.alwaysTemplate)
volumeView.setRouteButtonImage(image, for: .normal)
volumeView.themeTintColor = .backgroundSecondary
volumeView.themeBackgroundColor = .textPrimary
}
}
}
@objc private func handleFullScreenVideoViewTapped(gesture: UITapGestureRecognizer) {
2022-04-07 09:02:57 +02:00
let isHidden = callDurationLabel.alpha < 0.5
2021-12-01 03:25:12 +01:00
UIView.animate(withDuration: 0.5) {
self.operationPanel.alpha = isHidden ? 1 : 0
self.responsePanel.alpha = isHidden ? 1 : 0
2022-04-07 09:02:57 +02:00
self.callDurationLabel.alpha = isHidden ? 1 : 0
2021-12-01 03:25:12 +01:00
}
}
2021-08-16 06:40:07 +02:00
}