Commit 65594a4b authored by Kateryna Kostiuk's avatar Kateryna Kostiuk

cleanup: record video/audio

- clean up UI
- allow record with back camera
- remove unused recorded files

Change-Id: I650d9c11f2202c8efd1c1eef36d505f268fe53d4
parent bebadc78
......@@ -110,7 +110,7 @@ class CallViewModel: Stateable, ViewModel {
return call.state == .over || call.state == .failure
}).map({ [weak self] hide in
if hide {
self?.videoService.setCameraOrientation(orientation: UIDevice.current.orientation, callID: nil)
self?.videoService.setCameraOrientation(orientation: UIDevice.current.orientation)
self?.videoService.stopAudioDevice()
if #available(iOS 10.0, *), let call = self?.call {
self?.callsProvider.stopCall(callUUID: call.callUUID)
......@@ -319,10 +319,9 @@ class CallViewModel: Stateable, ViewModel {
return call.callId == self?.call?.callId
}).map({ call in
return call.state == .current
}).subscribe(onNext: { [weak self] call in
}).subscribe(onNext: { [weak self] _ in
self?.videoService
.setCameraOrientation(orientation: UIDevice.current.orientation,
callID: self?.call?.callId)
.setCameraOrientation(orientation: UIDevice.current.orientation)
}).disposed(by: self.disposeBag)
callsProvider.sharedResponseStream
.filter({ [unowned self] serviceEvent in
......@@ -460,8 +459,7 @@ class CallViewModel: Stateable, ViewModel {
}
func setCameraOrientation(orientation: UIDeviceOrientation) {
videoService.setCameraOrientation(orientation: orientation,
callID: self.call?.callId)
videoService.setCameraOrientation(orientation: orientation)
}
func showDialpad() {
......
......@@ -920,10 +920,9 @@ extension ConversationViewController: UITableViewDataSource {
}
})
.disposed(by: cell.disposeBag)
if item.message.transferStatus == .success {
self.addShareAction(cell: cell, item: item)
}
}
if item.message.transferStatus == .success {
self.addShareAction(cell: cell, item: item)
}
}
......
......@@ -191,14 +191,16 @@ class MessageViewModel {
func transferedFile(conversationID: String) -> URL? {
guard let account = self.accountService.currentAccount else {return nil}
if !self.message.incoming {return nil}
if self.lastTransferStatus != .success &&
self.message.transferStatus != .success {
return nil
}
let transferInfo = transferFileData
let folderName = self.message.incoming ? Directories.downloads.rawValue : Directories.recorded.rawValue
return self.dataTransferService
.getFileUrl(fileName: transferInfo.fileName, accountID: account.id,
.getFileUrl(fileName: transferInfo.fileName,
inFolder: folderName,
accountID: account.id,
conversationID: conversationID)
}
......
......@@ -33,13 +33,32 @@ class SendFileViewController: UIViewController, StoryboardBased, ViewModelBased
@IBOutlet weak var recordButton: UIButton!
@IBOutlet weak var sendButton: UIButton!
@IBOutlet weak var cancelButton: UIButton!
@IBOutlet weak var switchButton: UIButton!
@IBOutlet weak var placeholderButton: UIButton!
@IBOutlet weak var timerLabel: UILabel!
@IBOutlet weak var infoLabel: UILabel!
@IBOutlet weak var placeholderLabel: UILabel!
@IBOutlet weak var viewBottomConstraint: NSLayoutConstraint!
@IBOutlet weak var viewLeftConstraint: NSLayoutConstraint!
@IBOutlet weak var viewRightConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
self.applyL10()
let isAudio = self.viewModel.audioOnly
viewBottomConstraint.constant = isAudio ? 120 : 0
viewLeftConstraint.constant = isAudio ? 20 : 0
viewRightConstraint.constant = isAudio ? 20 : 0
self.bindViewsToViewModel()
}
func applyL10() {
self.sendButton.setTitle(L10n.DataTransfer.sendMessage, for: .normal)
self.cancelButton.setTitle(L10n.Actions.cancelAction, for: .normal)
self.infoLabel.text = L10n.DataTransfer.infoMessage
}
func bindViewsToViewModel() {
self.viewModel.capturedFrame
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] frame in
......@@ -50,26 +69,41 @@ class SendFileViewController: UIViewController, StoryboardBased, ViewModelBased
}
}).disposed(by: self.disposeBag)
self.cancelButton.rx.tap
.subscribe(onNext: { [unowned self] in
self.viewModel.cancel()
.subscribe(onNext: { [weak self] in
self?.viewModel.cancel()
}).disposed(by: self.disposeBag)
self.recordButton.rx.tap
.subscribe(onNext: { [unowned self] in
self.viewModel.triggerRecording()
.subscribe(onNext: { [weak self] in
self?.viewModel.triggerRecording()
}).disposed(by: self.disposeBag)
self.sendButton.rx.tap
.subscribe(onNext: { [unowned self] in
self.viewModel.sendFile()
.subscribe(onNext: { [weak self] in
self?.viewModel.sendFile()
}).disposed(by: self.disposeBag)
self.switchButton.rx.tap
.subscribe(onNext: { [weak self] in
self?.viewModel.switchCamera()
}).disposed(by: self.disposeBag)
self.viewModel.hidePreview
self.viewModel.hideVideoControls
.observeOn(MainScheduler.instance)
.bind(to: self.preview.rx.isHidden)
.disposed(by: self.disposeBag)
self.viewModel.hideVideoControls
.observeOn(MainScheduler.instance)
.bind(to: self.placeholderButton.rx.isHidden)
.disposed(by: self.disposeBag)
self.viewModel.hideVideoControls
.observeOn(MainScheduler.instance)
.bind(to: self.switchButton.rx.isHidden)
.disposed(by: self.disposeBag)
self.viewModel.readyToSend
.map {!$0}
.drive(self.sendButton.rx.isHidden)
.disposed(by: self.disposeBag)
self.viewModel.readyToSend
.drive(self.placeholderLabel.rx.isHidden)
.disposed(by: self.disposeBag)
self.viewModel.duration
.drive(self.timerLabel.rx.text)
.disposed(by: self.disposeBag)
......@@ -77,12 +111,11 @@ class SendFileViewController: UIViewController, StoryboardBased, ViewModelBased
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] finished in
if finished {
self?.dismiss(animated: true, completion: nil)
self?
.dismiss(animated: !(self?.viewModel.audioOnly ?? false),
completion: nil)
}
}).disposed(by: self.disposeBag)
self.viewModel.readyToSend
.drive(self.placeholderLabel.rx.isHidden)
.disposed(by: self.disposeBag)
self.viewModel.recording
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] recording in
......@@ -108,10 +141,4 @@ class SendFileViewController: UIViewController, StoryboardBased, ViewModelBased
.drive(self.infoLabel.rx.isHidden)
.disposed(by: self.disposeBag)
}
func applyL10() {
self.sendButton.setTitle(L10n.DataTransfer.sendMessage, for: .normal)
self.cancelButton.setTitle(L10n.Actions.cancelAction, for: .normal)
self.infoLabel.text = L10n.DataTransfer.infoMessage
}
}
......@@ -47,7 +47,7 @@ class SendFileViewModel: Stateable, ViewModel {
})
}()
lazy var hidePreview: Observable<Bool> = {
lazy var hideVideoControls: Observable<Bool> = {
Observable.just(audioOnly)
}()
......@@ -62,8 +62,8 @@ class SendFileViewModel: Stateable, ViewModel {
lazy var hideInfo: Driver<Bool> = {
recordingState
.asObservable()
.map({ state in
state != .initial
.map({ [weak self] state in
state != .initial || !(self?.audioOnly ?? true)
}).share()
.asDriver(onErrorJustReturn: false)
}()
......@@ -136,8 +136,15 @@ class SendFileViewModel: Stateable, ViewModel {
}
func startRecording() {
let dateFormatter: DateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd_HH:mm:ss"
let date = Date()
let dateString = dateFormatter.string(from: date)
let random = String(arc4random_uniform(9999))
let nameForRecordingFile = dateString + "_" + random
guard let url = self.fileTransferService.getFilePathForRecordings(forFile: nameForRecordingFile, accountID: conversation.accountId, conversationID: conversation.conversationId) else {return}
guard let name = self.videoService
.startLocalRecorder(audioOnly: audioOnly) else {
.startLocalRecorder(audioOnly: audioOnly, path: url.path) else {
return
}
recordingState.value = .recording
......@@ -170,5 +177,10 @@ class SendFileViewModel: Stateable, ViewModel {
}
self.videoService.videRecordingFinished()
recordingState.value = .sent
try? FileManager.default.removeItem(atPath: fileName)
}
func switchCamera() {
self.videoService.switchCamera()
}
}
......@@ -70,7 +70,7 @@ extension ConversationNavigation where Self: Coordinator, Self: StateableRespons
recordFileViewController.viewModel.audioOnly = audioOnly
self.present(viewController: recordFileViewController,
withStyle: .popup,
withAnimation: true,
withAnimation: !audioOnly,
withStateable: recordFileViewController.viewModel)
}
......
......@@ -624,6 +624,15 @@ class AccountsService: AccountAdapterDelegate {
self.loadAccountsFromDaemon()
if self.getAccount(fromAccountId: id) == nil {
self.dbManager.removeDBForAccount(accountId: id)
guard let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
return
}
let downloadsURL = documentsURL.appendingPathComponent(Directories.downloads.rawValue)
.appendingPathComponent(id)
try? FileManager.default.removeItem(atPath: downloadsURL.path)
let recordingsURL = documentsURL.appendingPathComponent(Directories.recorded.rawValue)
.appendingPathComponent(id)
try? FileManager.default.removeItem(atPath: recordingsURL.path)
}
}
......
......@@ -356,13 +356,17 @@ class ConversationsService {
}
func removeSavedFiles(accountId: String, conversationId: String) {
let downloadsFolderName = "downloads"
let downloadsFolderName = Directories.downloads.rawValue
guard let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
return
}
let directoryURL = documentsURL.appendingPathComponent(downloadsFolderName)
let downloadsURL = documentsURL.appendingPathComponent(downloadsFolderName)
.appendingPathComponent(accountId).appendingPathComponent(conversationId)
try? FileManager.default.removeItem(atPath: directoryURL.path)
try? FileManager.default.removeItem(atPath: downloadsURL.path)
let recordedFolderName = Directories.recorded.rawValue
let recordedURL = documentsURL.appendingPathComponent(recordedFolderName)
.appendingPathComponent(accountId).appendingPathComponent(conversationId)
try? FileManager.default.removeItem(atPath: recordedURL.path)
}
func messageStatusChanged(_ status: MessageStatus,
......
......@@ -33,6 +33,11 @@ enum DataTransferServiceError: Error {
case updateTransferError
}
enum Directories: String {
case recorded
case downloads
}
enum DataTransferStatus: CustomStringConvertible {
var description: String {
switch self {
......@@ -140,8 +145,14 @@ public final class DataTransferService: DataTransferAdapterDelegate {
}
}
func getFileUrl(fileName: String, accountID: String, conversationID: String) -> URL? {
guard let pathUrl = getFilePath(fileName: fileName, accountID: accountID, conversationID: conversationID) else {return nil}
func getFileUrl(fileName: String,
inFolder: String,
accountID: String,
conversationID: String) -> URL? {
guard let pathUrl = getFilePath(fileName: fileName,
inFolder: inFolder,
accountID: accountID,
conversationID: conversationID) else {return nil}
let fileManager = FileManager.default
var file: URL?
if fileManager.fileExists(atPath: pathUrl.path) {
......@@ -149,6 +160,7 @@ public final class DataTransferService: DataTransferAdapterDelegate {
}
return file
}
/*
to avoid creating images multiple time keep images in dictionary
images saved in app document folder referenced by conversationId concatinated with image name
......@@ -192,7 +204,9 @@ public final class DataTransferService: DataTransferAdapterDelegate {
maxSize: CGFloat,
accountID: String,
conversationID: String) -> UIImage? {
guard let pathUrl = getFilePath(fileName: name, accountID: accountID,
guard let pathUrl = getFilePath(fileName: name,
inFolder: Directories.downloads.rawValue,
accountID: accountID,
conversationID: conversationID) else {return nil}
let fileExtension = pathUrl.pathExtension as CFString
guard let uti = UTTypeCreatePreferredIdentifierForTag(
......@@ -219,7 +233,9 @@ public final class DataTransferService: DataTransferAdapterDelegate {
func isTransferImage(withId transferId: UInt64, accountID: String, conversationID: String) -> Bool? {
guard let info = getTransferInfo(withId: transferId) else { return nil }
guard let pathUrl = getFilePath(fileName: info.displayName,
accountID: accountID, conversationID: conversationID) else { return nil }
inFolder: Directories.downloads.rawValue,
accountID: accountID,
conversationID: conversationID) else { return nil }
let fileExtension = pathUrl.pathExtension as CFString
guard let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension,
fileExtension,
......@@ -286,37 +302,40 @@ public final class DataTransferService: DataTransferAdapterDelegate {
// MARK: private
fileprivate func getFilePath(fileName: String, accountID: String, conversationID: String) -> URL? {
let downloadsFolderName = "downloads"
fileprivate func getFilePath(fileName: String, inFolder: String, accountID: String, conversationID: String) -> URL? {
let folderName = inFolder
guard let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
return nil
}
let directoryURL = documentsURL.appendingPathComponent(downloadsFolderName)
let directoryURL = documentsURL.appendingPathComponent(folderName)
.appendingPathComponent(accountID).appendingPathComponent(conversationID)
return directoryURL.appendingPathComponent(fileName)
}
fileprivate func getFilePathForTransfer(forFile fileName: String, accountID: String, conversationID: String) -> URL? {
let downloadsFolderName = "downloads"
fileprivate func getFilePathForDirectory(directory: String, fileName: String, accountID: String, conversationID: String) -> URL? {
let folderName = directory
let fileNameOnly = (fileName as NSString).deletingPathExtension
let fileExtensionOnly = (fileName as NSString).pathExtension
var filePathUrl: URL?
guard let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
return nil
}
let directoryURL = documentsURL.appendingPathComponent(downloadsFolderName)
let directoryURL = documentsURL.appendingPathComponent(folderName)
.appendingPathComponent(accountID).appendingPathComponent(conversationID)
var isDirectory = ObjCBool(false)
let directoryExists = FileManager.default.fileExists(atPath: directoryURL.path, isDirectory: &isDirectory)
if directoryExists && isDirectory.boolValue {
if directory == Directories.recorded.rawValue {
return directoryURL.appendingPathComponent(fileName, isDirectory: false)
}
// check if file exists, if so add " (<duplicates+1>)" or "_<duplicates+1>"
// first check /.../AppData/Documents/downloads/<fileNameOnly>.<fileExtensionOnly>
// first check /.../AppData/Documents/directory/<fileNameOnly>.<fileExtensionOnly>
var finalFileName = fileNameOnly + "." + fileExtensionOnly
var filePathCheck = directoryURL.appendingPathComponent(finalFileName)
var fileExists = FileManager.default.fileExists(atPath: filePathCheck.path, isDirectory: &isDirectory)
var duplicates = 2
while fileExists {
// check /.../AppData/Documents/downloads/<fileNameOnly>_<duplicates>.<fileExtensionOnly>
// check /.../AppData/Documents/directory/<fileNameOnly>_<duplicates>.<fileExtensionOnly>
finalFileName = fileNameOnly + "_" + String(duplicates) + "." + fileExtensionOnly
filePathCheck = directoryURL.appendingPathComponent(finalFileName)
fileExists = FileManager.default.fileExists(atPath: filePathCheck.path, isDirectory: &isDirectory)
......@@ -327,7 +346,7 @@ public final class DataTransferService: DataTransferAdapterDelegate {
// need to create dir
do {
try FileManager.default.createDirectory(atPath: directoryURL.path, withIntermediateDirectories: true, attributes: nil)
filePathUrl = directoryURL.appendingPathComponent(fileName)
filePathUrl = directoryURL.appendingPathComponent(fileName, isDirectory: false)
return filePathUrl
} catch _ as NSError {
self.log.error("DataTransferService: error creating dir")
......@@ -335,6 +354,20 @@ public final class DataTransferService: DataTransferAdapterDelegate {
}
}
fileprivate func getFilePathForTransfer(forFile fileName: String, accountID: String, conversationID: String) -> URL? {
return self.getFilePathForDirectory(directory: Directories.downloads.rawValue,
fileName: fileName,
accountID: accountID,
conversationID: conversationID)
}
func getFilePathForRecordings(forFile fileName: String, accountID: String, conversationID: String) -> URL? {
return self.getFilePathForDirectory(directory: Directories.recorded.rawValue,
fileName: fileName,
accountID: accountID,
conversationID: conversationID)
}
// MARK: DataTransferAdapter
fileprivate func dataTransferIdList() -> [UInt64]? {
......
......@@ -364,7 +364,7 @@ class VideoService: FrameExtractorDelegate {
}).disposed(by: self.disposeBag)
}
func setCameraOrientation(orientation: UIDeviceOrientation, callID: String?) {
func setCameraOrientation(orientation: UIDeviceOrientation) {
var newOrientation: AVCaptureVideoOrientation
switch orientation {
case .portrait:
......@@ -455,6 +455,9 @@ extension VideoService: VideoAdapterDelegate {
}
func videRecordingFinished() {
if self.cameraPosition == .back {
self.switchCamera()
}
self.videoAdapter.stopCamera()
self.stopAudioDevice()
}
......@@ -469,15 +472,20 @@ extension VideoService: VideoAdapterDelegate {
}
func getImageOrienation() -> UIImage.Orientation {
let shouldMirror = cameraPosition == AVCaptureDevice.Position.front
switch self.currentOrientation {
case AVCaptureVideoOrientation.portrait:
return UIImage.Orientation.up
return shouldMirror ? UIImage.Orientation.upMirrored :
UIImage.Orientation.up
case AVCaptureVideoOrientation.portraitUpsideDown:
return UIImage.Orientation.down
return shouldMirror ? UIImage.Orientation.downMirrored :
UIImage.Orientation.down
case AVCaptureVideoOrientation.landscapeRight:
return cameraPosition == AVCaptureDevice.Position.front ? UIImage.Orientation.left : UIImage.Orientation.right
return shouldMirror ? UIImage.Orientation.rightMirrored :
UIImage.Orientation.right
case AVCaptureVideoOrientation.landscapeLeft:
return cameraPosition == AVCaptureDevice.Position.front ? UIImage.Orientation.right : UIImage.Orientation.left
return shouldMirror ? UIImage.Orientation.leftMirrored :
UIImage.Orientation.left
@unknown default:
return UIImage.Orientation.up
}
......@@ -504,31 +512,9 @@ extension VideoService: VideoAdapterDelegate {
videoAdapter.stopAudioDevice()
}
func startLocalRecorder(audioOnly: Bool) -> String? {
//configure path
let recordingsFolderName = "recorded"
guard let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
return nil
}
let directoryURL = documentsURL.appendingPathComponent(recordingsFolderName)
var isDirectory = ObjCBool(false)
let directoryExists = FileManager.default.fileExists(atPath: directoryURL.path, isDirectory: &isDirectory)
if !directoryExists || !isDirectory.boolValue {
do {
try FileManager.default.createDirectory(atPath: directoryURL.path, withIntermediateDirectories: true, attributes: nil)
} catch _ as NSError {
return nil
}
}
let dateFormatter: DateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd_HH:mm:ss"
let date = Date()
let dateString = dateFormatter.string(from: date)
let random = String(arc4random_uniform(9999))
let fileName = dateString + "_" + random
let videoURL = directoryURL.appendingPathComponent(fileName, isDirectory: false)
func startLocalRecorder(audioOnly: Bool, path: String) -> String? {
self.recording = true
return self.videoAdapter.startLocalRecording(videoURL.path, audioOnly: audioOnly)
return self.videoAdapter.startLocalRecording(path, audioOnly: audioOnly)
}
func stopLocalRecorder(path: String) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment