add preview before staring video

This commit is contained in:
ryanzhao 2021-10-21 16:28:48 +11:00
parent 7b23b8f601
commit 1231b9c20a
9 changed files with 276 additions and 10 deletions

View file

@ -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 */,

View file

@ -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() {

View 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)
}
}

View 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)
}
}

View file

@ -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

View 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()
}
}

View file

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "check.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

View file

@ -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";