Commit 70702cba authored by Kateryna Kostiuk's avatar Kateryna Kostiuk Committed by Andreas Traczyk

call: display video

Change-Id: I5271602778a0d7ee3345619a5b9c41359a742912
Reviewed-by: Andreas Traczyk's avatarAndreas Traczyk <andreas.traczyk@savoirfairelinux.com>
parent ee79fdb6
...@@ -27,13 +27,31 @@ import SwiftyBeaver ...@@ -27,13 +27,31 @@ import SwiftyBeaver
class CallViewController: UIViewController, StoryboardBased, ViewModelBased { class CallViewController: UIViewController, StoryboardBased, ViewModelBased {
@IBOutlet weak var profileImageView: UIImageView! //preview screen
@IBOutlet weak var nameLabel: UILabel! @IBOutlet private weak var profileImageView: UIImageView!
@IBOutlet weak var durationLabel: UILabel! @IBOutlet private weak var nameLabel: UILabel!
@IBOutlet weak var infoBottomLabel: UILabel! @IBOutlet private weak var durationLabel: UILabel!
@IBOutlet weak var cancelButton: UIButton! @IBOutlet private weak var infoBottomLabel: UILabel!
@IBOutlet weak var incomingVideo: UIImageView!
@IBOutlet weak var capturedVideo: UIImageView! @IBOutlet private weak var cancelButton: UIButton!
@IBOutlet private weak var mainView: UIView!
//video screen
@IBOutlet private weak var callView: UIView!
@IBOutlet private weak var incomingVideo: UIImageView!
@IBOutlet private weak var capturedVideo: UIImageView!
@IBOutlet private weak var infoContainer: UIView!
@IBOutlet private weak var callProfileImage: UIImageView!
@IBOutlet private weak var callNameLabel: UILabel!
@IBOutlet private weak var callInfoTimerLabel: UILabel!
@IBOutlet private weak var infoLabelConstraint: NSLayoutConstraint!
// call options buttons
@IBOutlet private weak var buttonsContainer: UIView!
@IBOutlet private weak var muteAudioButton: UIButton!
@IBOutlet private weak var muteVideoButton: UIButton!
@IBOutlet private weak var pauseCallButton: UIButton!
@IBOutlet private weak var switchCameraButton: UIButton!
var viewModel: CallViewModel! var viewModel: CallViewModel!
...@@ -41,18 +59,23 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased { ...@@ -41,18 +59,23 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased {
private let log = SwiftyBeaver.self private let log = SwiftyBeaver.self
private var task: DispatchWorkItem?
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(screenTaped))
self.mainView.addGestureRecognizer(tapGestureRecognizer)
self.setupUI() self.setupUI()
self.setupBindings() self.setupBindings()
} }
func setupUI() { func setupUI() {
self.cancelButton.backgroundColor = UIColor.red self.cancelButton.backgroundColor = UIColor.red
self.infoContainer.backgroundColor = UIColor.black.withAlphaComponent(0.3)
self.buttonsContainer.backgroundColor = UIColor.black.withAlphaComponent(0.3)
} }
func setupBindings() { func setupBindings() {
//Cancel button action //Cancel button action
self.cancelButton.rx.tap self.cancelButton.rx.tap
.subscribe(onNext: { [weak self] in .subscribe(onNext: { [weak self] in
...@@ -61,13 +84,13 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased { ...@@ -61,13 +84,13 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased {
}).disposed(by: self.disposeBag) }).disposed(by: self.disposeBag)
//Data bindings //Data bindings
self.viewModel.contactImageData.asObservable() self.viewModel.contactImageData.asObservable()
.observeOn(MainScheduler.instance) .observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] dataOrNil in .subscribe(onNext: { [weak self] dataOrNil in
if let imageData = dataOrNil { if let imageData = dataOrNil {
if let image = UIImage(data: imageData) { if let image = UIImage(data: imageData) {
self?.profileImageView.image = image self?.profileImageView.image = image
self?.callProfileImage.image = image
} }
} }
}).disposed(by: self.disposeBag) }).disposed(by: self.disposeBag)
...@@ -80,23 +103,93 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased { ...@@ -80,23 +103,93 @@ class CallViewController: UIViewController, StoryboardBased, ViewModelBased {
} }
}).disposed(by: self.disposeBag) }).disposed(by: self.disposeBag)
self.viewModel.contactName self.viewModel.contactName.drive(self.nameLabel.rx.text)
.observeOn(MainScheduler.instance)
.bind(to: self.nameLabel.rx.text)
.disposed(by: self.disposeBag) .disposed(by: self.disposeBag)
self.viewModel.callDuration self.viewModel.contactName.drive(self.callNameLabel.rx.text)
.observeOn(MainScheduler.instance) .disposed(by: self.disposeBag)
.bind(to: self.durationLabel.rx.text)
self.viewModel.callDuration.drive(self.durationLabel.rx.text)
.disposed(by: self.disposeBag)
self.viewModel.callDuration.drive(self.callInfoTimerLabel.rx.text)
.disposed(by: self.disposeBag) .disposed(by: self.disposeBag)
self.viewModel.bottomInfo self.viewModel.bottomInfo
.observeOn(MainScheduler.instance) .observeOn(MainScheduler.instance)
.bind(to: self.infoBottomLabel.rx.text) .bind(to: self.infoBottomLabel.rx.text)
.disposed(by: self.disposeBag) .disposed(by: self.disposeBag)
self.viewModel.incomingFrame
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] frame in
if let image = frame {
DispatchQueue.main.async {
self?.callView.isHidden = false
self?.incomingVideo.image = image
}
}
}).disposed(by: self.disposeBag)
self.viewModel.capturedFrame
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] frame in
if let image = frame {
DispatchQueue.main.async {
self?.capturedVideo.image = image
}
}
}).disposed(by: self.disposeBag)
self.viewModel.showCallOptions
.subscribeOn(MainScheduler.instance)
.subscribe(onNext: { show in
if show {
self.showContactInfo()
}
}).disposed(by: self.disposeBag)
} }
func removeFromScreen() { func removeFromScreen() {
self.dismiss(animated: false) self.dismiss(animated: false)
} }
@objc func screenTaped() {
self.viewModel.respondOnTap()
}
func showContactInfo() {
if !self.infoContainer.isHidden {
task?.cancel()
self.hideContactInfo()
return
}
self.infoLabelConstraint.constant = -200.00
self.buttonsContainer.isHidden = false
self.infoContainer.isHidden = false
self.view.layoutIfNeeded()
UIView.animate(withDuration: 0.2, delay: 0.0,
options: .curveEaseOut,
animations: { [weak self] in
self?.infoLabelConstraint.constant = 0.00
self?.view.layoutIfNeeded()
}, completion: nil)
task = DispatchWorkItem { self.hideContactInfo() }
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2, execute: task!)
}
func hideContactInfo() {
UIView.animate(withDuration: 0.2, delay: 0.00,
options: .curveEaseOut,
animations: { [weak self] in
self?.infoLabelConstraint.constant = -200.00
self?.view.layoutIfNeeded()
}, completion: { [weak self] _ in
self?.infoContainer.isHidden = true
self?.buttonsContainer.isHidden = true
})
}
} }
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
import RxSwift import RxSwift
import SwiftyBeaver import SwiftyBeaver
import Contacts import Contacts
import RxCocoa
class CallViewModel: Stateable, ViewModel { class CallViewModel: Stateable, ViewModel {
...@@ -38,17 +39,6 @@ class CallViewModel: Stateable, ViewModel { ...@@ -38,17 +39,6 @@ class CallViewModel: Stateable, ViewModel {
private let disposeBag = DisposeBag() private let disposeBag = DisposeBag()
fileprivate let log = SwiftyBeaver.self fileprivate let log = SwiftyBeaver.self
lazy var incomingFrame: Observable<UIImage?> = {
return videoService.incomingVideoFrame.asObservable().map({ frame in
return frame
})
}()
lazy var capturedFrame: Observable<UIImage?> = {
return videoService.capturedVideoFrame.asObservable().map({ frame in
return frame
})
}()
var call: CallModel? { var call: CallModel? {
didSet { didSet {
guard let call = self.call else { guard let call = self.call else {
...@@ -84,6 +74,17 @@ class CallViewModel: Stateable, ViewModel { ...@@ -84,6 +74,17 @@ class CallViewModel: Stateable, ViewModel {
var contactImageData = Variable<Data?>(nil) var contactImageData = Variable<Data?>(nil)
lazy var incomingFrame: Observable<UIImage?> = {
return videoService.incomingVideoFrame.asObservable().map({ frame in
return frame
})
}()
lazy var capturedFrame: Observable<UIImage?> = {
return videoService.capturedVideoFrame.asObservable().map({ frame in
return frame
})
}()
lazy var dismisVC: Observable<Bool> = { lazy var dismisVC: Observable<Bool> = {
return callService.currentCall.map({[weak self] call in return callService.currentCall.map({[weak self] call in
return call.state == .over || call.state == .failure && call.callId == self?.call?.callId return call.state == .over || call.state == .failure && call.callId == self?.call?.callId
...@@ -92,7 +93,7 @@ class CallViewModel: Stateable, ViewModel { ...@@ -92,7 +93,7 @@ class CallViewModel: Stateable, ViewModel {
}) })
}() }()
lazy var contactName: Observable<String> = { lazy var contactName: Driver<String> = {
return callService.currentCall.filter({ [weak self] call in return callService.currentCall.filter({ [weak self] call in
return call.state != .over && call.state != .inactive && call.callId == self?.call?.callId return call.state != .over && call.state != .inactive && call.callId == self?.call?.callId
}).map({ call in }).map({ call in
...@@ -103,10 +104,10 @@ class CallViewModel: Stateable, ViewModel { ...@@ -103,10 +104,10 @@ class CallViewModel: Stateable, ViewModel {
} else { } else {
return L10n.Calls.unknown return L10n.Calls.unknown
} }
}) }).asDriver(onErrorJustReturn: "")
}() }()
lazy var callDuration: Observable<String> = { lazy var callDuration: Driver<String> = {
let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance) let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
.takeUntil(self.callService.currentCall .takeUntil(self.callService.currentCall
.filter { [weak self] call in .filter { [weak self] call in
...@@ -120,7 +121,7 @@ class CallViewModel: Stateable, ViewModel { ...@@ -120,7 +121,7 @@ class CallViewModel: Stateable, ViewModel {
return call.state == .current return call.state == .current
}).flatMap({ _ in }).flatMap({ _ in
return timer return timer
}) }).asDriver(onErrorJustReturn: "")
}() }()
lazy var bottomInfo: Observable<String> = { lazy var bottomInfo: Observable<String> = {
...@@ -135,6 +136,22 @@ class CallViewModel: Stateable, ViewModel { ...@@ -135,6 +136,22 @@ class CallViewModel: Stateable, ViewModel {
}) })
}() }()
lazy var showCallOptions: Observable<Bool> = {
return Observable.combineLatest(self.callIsActive, self.screenTapped.asObservable()) {(active, tapped) -> Bool in
return active && tapped
}
}()
lazy var callIsActive: Observable<Bool> = {
self.callService.currentCall.filter({ call in
return call.state == .current && call.callId == self.call?.callId
}).map({_ in
return true
})
}()
var screenTapped = BehaviorSubject(value: false)
required init(with injectionBag: InjectionBag) { required init(with injectionBag: InjectionBag) {
self.callService = injectionBag.callService self.callService = injectionBag.callService
self.contactsService = injectionBag.contactsService self.contactsService = injectionBag.contactsService
...@@ -160,8 +177,6 @@ class CallViewModel: Stateable, ViewModel { ...@@ -160,8 +177,6 @@ class CallViewModel: Stateable, ViewModel {
}).disposed(by: self.disposeBag) }).disposed(by: self.disposeBag)
} }
func answerCall() { func answerCall() {
guard let call = self.call else { guard let call = self.call else {
return return
...@@ -196,4 +211,8 @@ class CallViewModel: Stateable, ViewModel { ...@@ -196,4 +211,8 @@ class CallViewModel: Stateable, ViewModel {
} }
self.contactImageData.value = data self.contactImageData.value = data
} }
func respondOnTap() {
self.screenTapped.onNext(true)
}
} }
...@@ -56,8 +56,14 @@ enum Asset { ...@@ -56,8 +56,14 @@ enum Asset {
static let device = ImageAsset(name: "device") static let device = ImageAsset(name: "device")
static let fallbackAvatar = ImageAsset(name: "fallback_avatar") static let fallbackAvatar = ImageAsset(name: "fallback_avatar")
static let icContactPicture = ImageAsset(name: "ic_contact_picture") static let icContactPicture = ImageAsset(name: "ic_contact_picture")
static let moreSettings = ImageAsset(name: "more_settings")
static let muteAudio = ImageAsset(name: "mute_audio")
static let muteVideo = ImageAsset(name: "mute_video")
static let pauseCall = ImageAsset(name: "pause_call")
static let ringLogo = ImageAsset(name: "ring_logo") static let ringLogo = ImageAsset(name: "ring_logo")
static let settingsIcon = ImageAsset(name: "settings_icon") static let settingsIcon = ImageAsset(name: "settings_icon")
static let stopCall = ImageAsset(name: "stop_call")
static let switchCamera = ImageAsset(name: "switch_camera")
// swiftlint:disable trailing_comma // swiftlint:disable trailing_comma
static let allColors: [ColorAsset] = [ static let allColors: [ColorAsset] = [
...@@ -73,8 +79,14 @@ enum Asset { ...@@ -73,8 +79,14 @@ enum Asset {
device, device,
fallbackAvatar, fallbackAvatar,
icContactPicture, icContactPicture,
moreSettings,
muteAudio,
muteVideo,
pauseCall,
ringLogo, ringLogo,
settingsIcon, settingsIcon,
stopCall,
switchCamera,
] ]
// swiftlint:enable trailing_comma // swiftlint:enable trailing_comma
@available(*, deprecated, renamed: "allImages") @available(*, deprecated, renamed: "allImages")
......
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_more_horiz_white.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ic_more_horiz_white_2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ic_more_horiz_white_3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_volume_mute_white.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ic_volume_mute_white_2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ic_volume_mute_white_3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_videocam_white.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ic_videocam_white_2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ic_videocam_white_3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_pause_white.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ic_pause_white_2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ic_pause_white_3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_call_end_white.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ic_call_end_white_2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ic_call_end_white_3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
{
"images" : [
{
"idiom" : "universal",
"filename" : "ic_replay_white.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "ic_replay_white_2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "ic_replay_white_3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
\ No newline at end of file
...@@ -135,7 +135,7 @@ class FrameExtractor: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate { ...@@ -135,7 +135,7 @@ class FrameExtractor: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate {
} }
captureSession.addInput(captureDeviceInput) captureSession.addInput(captureDeviceInput)
let videoOutput = AVCaptureVideoDataOutput() let videoOutput = AVCaptureVideoDataOutput()
videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue(label: "sample buffer")) videoOutput.setSampleBufferDelegate(self, queue: sessionQueue)
guard captureSession.canAddOutput(videoOutput) else { guard captureSession.canAddOutput(videoOutput) else {
throw VideoError.setupOutputDeviceFailed throw VideoError.setupOutputDeviceFailed
} }
......
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