mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
add preview before staring video
This commit is contained in:
parent
7b23b8f601
commit
1231b9c20a
9 changed files with 276 additions and 10 deletions
|
@ -137,6 +137,9 @@
|
|||
76C87F19181EFCE600C4ACAB /* MediaPlayer.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 76C87F18181EFCE600C4ACAB /* MediaPlayer.framework */; };
|
||||
76EB054018170B33006006FC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 76EB03C318170B33006006FC /* AppDelegate.m */; };
|
||||
7B1581E2271E743B00848B49 /* OWSSounds.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E1271E743B00848B49 /* OWSSounds.swift */; };
|
||||
7B1581E4271FC59D00848B49 /* CallModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E3271FC59C00848B49 /* CallModal.swift */; };
|
||||
7B1581E6271FD2A100848B49 /* VideoPreviewVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */; };
|
||||
7B1581E827210ECC00848B49 /* RenderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1581E727210ECC00848B49 /* RenderView.swift */; };
|
||||
7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; };
|
||||
7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; };
|
||||
7B7CB189270430D20079FF93 /* CallMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7CB188270430D20079FF93 /* CallMessageView.swift */; };
|
||||
|
@ -1116,6 +1119,9 @@
|
|||
76EB03C218170B33006006FC /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
|
||||
76EB03C318170B33006006FC /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
|
||||
7B1581E1271E743B00848B49 /* OWSSounds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OWSSounds.swift; sourceTree = "<group>"; };
|
||||
7B1581E3271FC59C00848B49 /* CallModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallModal.swift; sourceTree = "<group>"; };
|
||||
7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPreviewVC.swift; sourceTree = "<group>"; };
|
||||
7B1581E727210ECC00848B49 /* RenderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderView.swift; sourceTree = "<group>"; };
|
||||
7B2DB2AD26F1B0FF0035B509 /* si */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = si; path = si.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = "<group>"; };
|
||||
7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = "<group>"; };
|
||||
|
@ -2053,6 +2059,7 @@
|
|||
children = (
|
||||
7B7CB18D270D066F0079FF93 /* IncomingCallBanner.swift */,
|
||||
7B7CB18F270FB2150079FF93 /* MiniCallView.swift */,
|
||||
7B1581E727210ECC00848B49 /* RenderView.swift */,
|
||||
);
|
||||
path = "Views & Modals";
|
||||
sourceTree = "<group>";
|
||||
|
@ -2169,6 +2176,7 @@
|
|||
B8214A2A25D63EB9009C0F2A /* MessagesTableView.swift */,
|
||||
C374EEEA25DA3CA70073A857 /* ConversationTitleView.swift */,
|
||||
B848A4C4269EAAA200617031 /* UserDetailsSheet.swift */,
|
||||
7B1581E3271FC59C00848B49 /* CallModal.swift */,
|
||||
);
|
||||
path = "Views & Modals";
|
||||
sourceTree = "<group>";
|
||||
|
@ -2350,6 +2358,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
B877E24126CA12910007970A /* CallVC.swift */,
|
||||
7B1581E5271FD2A100848B49 /* VideoPreviewVC.swift */,
|
||||
B877E24526CA13BA0007970A /* CallVC+Camera.swift */,
|
||||
B8B558F026C4BB0600693325 /* CameraManager.swift */,
|
||||
7B7CB18C270D06350079FF93 /* Views & Modals */,
|
||||
|
@ -4837,6 +4846,7 @@
|
|||
34386A54207D271D009F5D9C /* NeverClearView.swift in Sources */,
|
||||
451166C01FD86B98000739BA /* AccountManager.swift in Sources */,
|
||||
C374EEF425DB31D40073A857 /* VoiceMessageRecordingView.swift in Sources */,
|
||||
7B1581E6271FD2A100848B49 /* VideoPreviewVC.swift in Sources */,
|
||||
B83F2B88240CB75A000A54AB /* UIImage+Scaling.swift in Sources */,
|
||||
3430FE181F7751D4000EC51B /* GiphyAPI.swift in Sources */,
|
||||
340FC8AA204DAC8D007AEB0F /* NotificationSettingsViewController.m in Sources */,
|
||||
|
@ -4917,6 +4927,7 @@
|
|||
B875885A264503A6000E60D0 /* JoinOpenGroupModal.swift in Sources */,
|
||||
B8CCF6432397711F0091D419 /* SettingsVC.swift in Sources */,
|
||||
C354E75A23FE2A7600CE22E3 /* BaseVC.swift in Sources */,
|
||||
7B1581E827210ECC00848B49 /* RenderView.swift in Sources */,
|
||||
3441FD9F21A3604F00BB9542 /* BackupRestoreViewController.swift in Sources */,
|
||||
45C0DC1B1E68FE9000E04C47 /* UIApplication+OWS.swift in Sources */,
|
||||
4539B5861F79348F007141FF /* PushRegistrationManager.swift in Sources */,
|
||||
|
@ -4926,6 +4937,7 @@
|
|||
4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */,
|
||||
C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */,
|
||||
340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */,
|
||||
7B1581E4271FC59D00848B49 /* CallModal.swift in Sources */,
|
||||
34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */,
|
||||
B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */,
|
||||
34D1F0C01F8EC1760066283D /* MessageRecipientStatusUtils.swift in Sources */,
|
||||
|
|
|
@ -4,7 +4,7 @@ import SessionMessagingKit
|
|||
import SessionUtilitiesKit
|
||||
import UIKit
|
||||
|
||||
final class CallVC : UIViewController, WebRTCSessionDelegate {
|
||||
final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelegate {
|
||||
let sessionID: String
|
||||
let uuid: String
|
||||
let mode: Mode
|
||||
|
@ -225,7 +225,7 @@ final class CallVC : UIViewController, WebRTCSessionDelegate {
|
|||
view.addSubview(fadeView)
|
||||
fadeView.translatesAutoresizingMaskIntoConstraints = false
|
||||
fadeView.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: view)
|
||||
// Close button
|
||||
// Minimize button
|
||||
view.addSubview(minimizeButton)
|
||||
minimizeButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
minimizeButton.pin(.left, to: .left, of: view)
|
||||
|
@ -354,15 +354,22 @@ final class CallVC : UIViewController, WebRTCSessionDelegate {
|
|||
cameraManager.stop()
|
||||
videoButton.alpha = 0.5
|
||||
switchCameraButton.isEnabled = false
|
||||
isVideoEnabled = false
|
||||
} else {
|
||||
webRTCSession.turnOnVideo()
|
||||
localVideoView.isHidden = false
|
||||
cameraManager.prepare()
|
||||
cameraManager.start()
|
||||
videoButton.alpha = 1.0
|
||||
switchCameraButton.isEnabled = true
|
||||
let previewVC = VideoPreviewVC()
|
||||
previewVC.delegate = self
|
||||
present(previewVC, animated: true, completion: nil)
|
||||
}
|
||||
isVideoEnabled = !isVideoEnabled
|
||||
}
|
||||
|
||||
func cameraDidConfirmTurningOn() {
|
||||
webRTCSession.turnOnVideo()
|
||||
localVideoView.isHidden = false
|
||||
cameraManager.prepare()
|
||||
cameraManager.start()
|
||||
videoButton.alpha = 1.0
|
||||
switchCameraButton.isEnabled = true
|
||||
isVideoEnabled = true
|
||||
}
|
||||
|
||||
@objc private func switchCamera() {
|
||||
|
|
123
Session/Calls/VideoPreviewVC.swift
Normal file
123
Session/Calls/VideoPreviewVC.swift
Normal file
|
@ -0,0 +1,123 @@
|
|||
import UIKit
|
||||
import WebRTC
|
||||
|
||||
public protocol VideoPreviewDelegate : AnyObject {
|
||||
func cameraDidConfirmTurningOn()
|
||||
}
|
||||
|
||||
class VideoPreviewVC: UIViewController, CameraManagerDelegate {
|
||||
weak var delegate: VideoPreviewDelegate?
|
||||
|
||||
lazy var cameraManager: CameraManager = {
|
||||
let result = CameraManager()
|
||||
result.delegate = self
|
||||
return result
|
||||
}()
|
||||
|
||||
// MARK: UI Components
|
||||
private lazy var renderView: RenderView = {
|
||||
let result = RenderView()
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var fadeView: UIView = {
|
||||
let result = UIView()
|
||||
let height: CGFloat = 64
|
||||
var frame = UIScreen.main.bounds
|
||||
frame.size.height = height
|
||||
let layer = CAGradientLayer()
|
||||
layer.frame = frame
|
||||
layer.colors = [ UIColor(hex: 0x000000).withAlphaComponent(0.4).cgColor, UIColor(hex: 0x000000).withAlphaComponent(0).cgColor ]
|
||||
result.layer.insertSublayer(layer, at: 0)
|
||||
result.set(.height, to: height)
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var closeButton: UIButton = {
|
||||
let result = UIButton(type: .custom)
|
||||
let image = UIImage(named: "X")!.withTint(.white)
|
||||
result.setImage(image, for: UIControl.State.normal)
|
||||
result.set(.width, to: 60)
|
||||
result.set(.height, to: 60)
|
||||
result.addTarget(self, action: #selector(cancel), for: UIControl.Event.touchUpInside)
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var confirmButton: UIButton = {
|
||||
let result = UIButton(type: .custom)
|
||||
let image = UIImage(named: "Check")!.withTint(.white)
|
||||
result.setImage(image, for: UIControl.State.normal)
|
||||
result.set(.width, to: 60)
|
||||
result.set(.height, to: 60)
|
||||
result.addTarget(self, action: #selector(confirm), for: UIControl.Event.touchUpInside)
|
||||
return result
|
||||
}()
|
||||
|
||||
private lazy var titleLabel: UILabel = {
|
||||
let result = UILabel()
|
||||
result.text = "Preview"
|
||||
result.textColor = .white
|
||||
result.font = .boldSystemFont(ofSize: Values.veryLargeFontSize)
|
||||
result.textAlignment = .center
|
||||
return result
|
||||
}()
|
||||
|
||||
// MARK: Lifecycle
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
view.backgroundColor = .black
|
||||
setUpViewHierarchy()
|
||||
cameraManager.prepare()
|
||||
}
|
||||
|
||||
func setUpViewHierarchy() {
|
||||
// Preview video view
|
||||
view.addSubview(renderView)
|
||||
renderView.translatesAutoresizingMaskIntoConstraints = false
|
||||
renderView.pin(to: view)
|
||||
// Fade view
|
||||
view.addSubview(fadeView)
|
||||
fadeView.translatesAutoresizingMaskIntoConstraints = false
|
||||
fadeView.pin([ UIView.HorizontalEdge.left, UIView.VerticalEdge.top, UIView.HorizontalEdge.right ], to: view)
|
||||
// Close button
|
||||
view.addSubview(closeButton)
|
||||
closeButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
closeButton.pin(.left, to: .left, of: view)
|
||||
closeButton.center(.vertical, in: fadeView)
|
||||
// Confirm button
|
||||
view.addSubview(confirmButton)
|
||||
confirmButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
confirmButton.pin(.right, to: .right, of: view)
|
||||
confirmButton.center(.vertical, in: fadeView)
|
||||
// Title label
|
||||
view.addSubview(titleLabel)
|
||||
titleLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
titleLabel.center(.vertical, in: closeButton)
|
||||
titleLabel.center(.horizontal, in: view)
|
||||
}
|
||||
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
cameraManager.start()
|
||||
}
|
||||
|
||||
override func viewWillDisappear(_ animated: Bool) {
|
||||
super.viewWillDisappear(animated)
|
||||
cameraManager.stop()
|
||||
}
|
||||
|
||||
// MARK: Interaction
|
||||
@objc func confirm() {
|
||||
delegate?.cameraDidConfirmTurningOn()
|
||||
self.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@objc func cancel() {
|
||||
self.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
// MARK: CameraManagerDelegate
|
||||
func handleVideoOutputCaptured(sampleBuffer: CMSampleBuffer) {
|
||||
renderView.enqueue(sampleBuffer: sampleBuffer)
|
||||
}
|
||||
}
|
36
Session/Calls/Views & Modals/RenderView.swift
Normal file
36
Session/Calls/Views & Modals/RenderView.swift
Normal file
|
@ -0,0 +1,36 @@
|
|||
// Copyright © 2021 Rangeproof Pty Ltd. All rights reserved.
|
||||
|
||||
import UIKit
|
||||
import CoreMedia
|
||||
|
||||
class RenderView: UIView {
|
||||
|
||||
private lazy var displayLayer: AVSampleBufferDisplayLayer = {
|
||||
let result = AVSampleBufferDisplayLayer()
|
||||
result.videoGravity = .resizeAspectFill
|
||||
return result
|
||||
}()
|
||||
|
||||
init() {
|
||||
super.init(frame: CGRect.zero)
|
||||
self.layer.addSublayer(displayLayer)
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
preconditionFailure("Use init(message:) instead.")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(coder:) instead.")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
displayLayer.frame = self.bounds
|
||||
}
|
||||
|
||||
public func enqueue(sampleBuffer: CMSampleBuffer) {
|
||||
displayLayer.enqueue(sampleBuffer)
|
||||
}
|
||||
|
||||
}
|
|
@ -27,7 +27,7 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
|
|||
}
|
||||
|
||||
// MARK: Call
|
||||
@objc func startCall(_ sender: Any) {
|
||||
@objc func startCall(_ sender: Any?) {
|
||||
guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID() else { return }
|
||||
let callVC = CallVC(for: contactSessionID, uuid: UUID().uuidString, mode: .offer)
|
||||
callVC.conversationVC = self
|
||||
|
@ -38,6 +38,15 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc
|
|||
present(callVC, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
internal func showCallModal() {
|
||||
let callModal = CallModal() { [weak self] in
|
||||
self?.startCall(nil)
|
||||
}
|
||||
callModal.modalPresentationStyle = .overFullScreen
|
||||
callModal.modalTransitionStyle = .crossDissolve
|
||||
present(callModal, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
internal func showCallVCIfNeeded() {
|
||||
guard let contactSessionID = (thread as? TSContactThread)?.contactSessionID(),
|
||||
let incomingCallBanner = IncomingCallBanner.current, incomingCallBanner.sessionID == contactSessionID
|
||||
|
|
65
Session/Conversations/Views & Modals/CallModal.swift
Normal file
65
Session/Conversations/Views & Modals/CallModal.swift
Normal file
|
@ -0,0 +1,65 @@
|
|||
|
||||
final class CallModal : Modal {
|
||||
private let onCallEnabled: () -> Void
|
||||
|
||||
// MARK: Lifecycle
|
||||
init(onCallEnabled: @escaping () -> Void) {
|
||||
self.onCallEnabled = onCallEnabled
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(onCallEnabled:) instead.")
|
||||
}
|
||||
|
||||
override init(nibName: String?, bundle: Bundle?) {
|
||||
preconditionFailure("Use init(onCallEnabled:) instead.")
|
||||
}
|
||||
|
||||
override func populateContentView() {
|
||||
// Title
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.textColor = Colors.text
|
||||
titleLabel.font = .boldSystemFont(ofSize: Values.largeFontSize)
|
||||
titleLabel.text = NSLocalizedString("modal_call_title", comment: "")
|
||||
titleLabel.textAlignment = .center
|
||||
// Message
|
||||
let messageLabel = UILabel()
|
||||
messageLabel.textColor = Colors.text
|
||||
messageLabel.font = .systemFont(ofSize: Values.smallFontSize)
|
||||
let message = NSLocalizedString("modal_call_explanation", comment: "")
|
||||
messageLabel.text = message
|
||||
messageLabel.numberOfLines = 0
|
||||
messageLabel.lineBreakMode = .byWordWrapping
|
||||
messageLabel.textAlignment = .center
|
||||
// Enable button
|
||||
let enableButton = UIButton()
|
||||
enableButton.set(.height, to: Values.mediumButtonHeight)
|
||||
enableButton.layer.cornerRadius = Modal.buttonCornerRadius
|
||||
enableButton.backgroundColor = Colors.buttonBackground
|
||||
enableButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize)
|
||||
enableButton.setTitleColor(Colors.text, for: UIControl.State.normal)
|
||||
enableButton.setTitle(NSLocalizedString("modal_link_previews_button_title", comment: ""), for: UIControl.State.normal)
|
||||
enableButton.addTarget(self, action: #selector(enable), for: UIControl.Event.touchUpInside)
|
||||
// Button stack view
|
||||
let buttonStackView = UIStackView(arrangedSubviews: [ cancelButton, enableButton ])
|
||||
buttonStackView.axis = .horizontal
|
||||
buttonStackView.spacing = Values.mediumSpacing
|
||||
buttonStackView.distribution = .fillEqually
|
||||
// Main stack view
|
||||
let mainStackView = UIStackView(arrangedSubviews: [ titleLabel, messageLabel, buttonStackView ])
|
||||
mainStackView.axis = .vertical
|
||||
mainStackView.spacing = Values.largeSpacing
|
||||
contentView.addSubview(mainStackView)
|
||||
mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing)
|
||||
mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing)
|
||||
contentView.pin(.trailing, to: .trailing, of: mainStackView, withInset: Values.largeSpacing)
|
||||
contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: Values.largeSpacing)
|
||||
}
|
||||
|
||||
// MARK: Interaction
|
||||
@objc private func enable() {
|
||||
presentingViewController?.dismiss(animated: true, completion: nil)
|
||||
onCallEnabled()
|
||||
}
|
||||
}
|
12
Session/Meta/Images.xcassets/Session/Check.imageset/Contents.json
vendored
Normal file
12
Session/Meta/Images.xcassets/Session/Check.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "check.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
Session/Meta/Images.xcassets/Session/Check.imageset/check.pdf
vendored
Normal file
BIN
Session/Meta/Images.xcassets/Session/Check.imageset/check.pdf
vendored
Normal file
Binary file not shown.
|
@ -538,6 +538,8 @@
|
|||
"modal_link_previews_title" = "Enable Link Previews?";
|
||||
"modal_link_previews_explanation" = "Enabling link previews will show previews for URLs you send and receive. This can be useful, but Session will need to contact linked websites to generate previews. You can always disable link previews in Session's settings.";
|
||||
"modal_link_previews_button_title" = "Enable";
|
||||
"modal_call_title" = "Voice and video calls";
|
||||
"modal_call_explanation" = "The current implementation of voice/video calls will expose your IP address to the Oxen Foundation servers and the calling/called user.";
|
||||
"modal_share_logs_title" = "Share Logs";
|
||||
"modal_share_logs_explanation" = "Would you like to export your application logs to be able to share for troubleshooting?";
|
||||
"vc_share_title" = "Share to Session";
|
||||
|
|
Loading…
Reference in a new issue