diff --git a/Signal/src/ViewControllers/CallViewController.swift b/Signal/src/ViewControllers/CallViewController.swift index 7130f7da99..9dbfd05f16 100644 --- a/Signal/src/ViewControllers/CallViewController.swift +++ b/Signal/src/ViewControllers/CallViewController.swift @@ -46,7 +46,10 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, // MARK: - Ongoing Call Controls - var ongoingCallView: UIView! + var ongoingCallControls: UIStackView! + + var ongoingAudioCallControls: UIStackView! + var ongoingVideoCallControls: UIStackView! var hangUpButton: UIButton! var audioSourceButton: UIButton! @@ -54,14 +57,11 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, var audioModeVideoButton: UIButton! var videoModeMuteButton: UIButton! var videoModeVideoButton: UIButton! - // TODO: Later, we'll re-enable the text message button - // so users can send and read messages during a - // call. -// var textMessageButton: UIButton! + var videoModeFlipCameraButton: UIButton! // MARK: - Incoming Call Controls - var incomingCallView: UIView! + var incomingCallControls: UIStackView! var acceptIncomingButton: UIButton! var declineIncomingButton: UIButton! @@ -182,7 +182,10 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, override func loadView() { self.view = UIView() + + self.view.layoutMargins = UIEdgeInsets(top: 16, left: 20, bottom: 16, right: 20) createViews() + createViewConstraints() } override func viewDidLoad() { @@ -348,42 +351,60 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, // textMessageButton = createButton(imageName:"message-active-wide", // action:#selector(didPressTextMessage)) - audioSourceButton = createButton(imageName: "audio-call-speaker-inactive", + audioSourceButton = createButton(image: #imageLiteral(resourceName: "audio-call-speaker-inactive"), action: #selector(didPressAudioSource)) audioSourceButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_AUDIO_SOURCE_LABEL", comment: "Accessibility label for selection the audio source") - hangUpButton = createButton(imageName: "hangup-active-wide", + hangUpButton = createButton(image: #imageLiteral(resourceName: "hangup-active-wide"), action: #selector(didPressHangup)) hangUpButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_HANGUP_LABEL", comment: "Accessibility label for hang up call") - audioModeMuteButton = createButton(imageName: "audio-call-mute-inactive", + audioModeMuteButton = createButton(image: #imageLiteral(resourceName: "audio-call-mute-inactive"), action: #selector(didPressMute)) + audioModeMuteButton.setImage(#imageLiteral(resourceName: "audio-call-mute-active"), for: .selected) + audioModeMuteButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_MUTE_LABEL", comment: "Accessibility label for muting the microphone") - videoModeMuteButton = createButton(imageName: "video-mute-unselected", - action: #selector(didPressMute)) - videoModeMuteButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_MUTE_LABEL", comment: "Accessibility label for muting the microphone") - - audioModeVideoButton = createButton(imageName: "audio-call-video-inactive", + audioModeVideoButton = createButton(image: #imageLiteral(resourceName: "audio-call-video-inactive"), action: #selector(didPressVideo)) + audioModeVideoButton.setImage(#imageLiteral(resourceName: "audio-call-video-active"), for: .selected) audioModeVideoButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_SWITCH_TO_VIDEO_LABEL", comment: "Accessibility label to switch to video call") - videoModeVideoButton = createButton(imageName: "video-video-unselected", + videoModeMuteButton = createButton(image: #imageLiteral(resourceName: "video-mute-unselected"), + action: #selector(didPressMute)) + videoModeMuteButton.setImage(#imageLiteral(resourceName: "video-mute-selected"), for: .selected) + videoModeMuteButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_MUTE_LABEL", comment: "Accessibility label for muting the microphone") + videoModeMuteButton.alpha = 0.9 + + // TODO proper asset + videoModeFlipCameraButton = createButton(image: #imageLiteral(resourceName: "btnRefresh--white"), + action: #selector(didPressFlipCamera)) + videoModeFlipCameraButton.setImage(#imageLiteral(resourceName: "btnRefresh--white"), + for: .selected) + videoModeFlipCameraButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_SWITCH_CAMERA_DIRECTION", comment: "Accessibility label to toggle front vs. rear facing camera") + videoModeFlipCameraButton.alpha = 0.9 + + videoModeVideoButton = createButton(image: #imageLiteral(resourceName: "video-video-unselected"), action: #selector(didPressVideo)) + videoModeVideoButton.setImage(#imageLiteral(resourceName: "video-video-selected"), for: .selected) videoModeVideoButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_SWITCH_TO_AUDIO_LABEL", comment: "Accessibility label to switch to audio only") + videoModeVideoButton.alpha = 0.9 - setButtonSelectedImage(button: audioModeMuteButton, imageName: "audio-call-mute-active") - setButtonSelectedImage(button: videoModeMuteButton, imageName: "video-mute-selected") - setButtonSelectedImage(button: audioModeVideoButton, imageName: "audio-call-video-active") - setButtonSelectedImage(button: videoModeVideoButton, imageName: "video-video-selected") + ongoingCallControls = UIStackView(arrangedSubviews: [hangUpButton]) + ongoingCallControls.axis = .vertical + ongoingCallControls.alignment = .center + view.addSubview(ongoingCallControls) - ongoingCallView = createContainerForCallControls(controlGroups: [ - [audioModeMuteButton, audioSourceButton, audioModeVideoButton ], - [videoModeMuteButton, hangUpButton, videoModeVideoButton ] - ]) + ongoingAudioCallControls = UIStackView(arrangedSubviews: [audioModeMuteButton, audioSourceButton, audioModeVideoButton]) + ongoingAudioCallControls.distribution = .equalSpacing + ongoingAudioCallControls.axis = .horizontal + + ongoingVideoCallControls = UIStackView(arrangedSubviews: [videoModeMuteButton, videoModeFlipCameraButton, videoModeVideoButton]) + ongoingAudioCallControls.distribution = .equalSpacing + ongoingVideoCallControls.axis = .horizontal } func presentAudioSourcePicker() { @@ -416,59 +437,30 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, self.present(actionSheetController, animated: true) } - func setButtonSelectedImage(button: UIButton, imageName: String) { - let image = UIImage(named: imageName) - assert(image != nil) - button.setImage(image, for: .selected) - } - func updateAvatarImage() { contactAvatarView.image = OWSAvatarBuilder.buildImage(thread: thread, diameter: 400, contactsManager: contactsManager) } func createIncomingCallControls() { - acceptIncomingButton = createButton(imageName: "call-active-wide", + acceptIncomingButton = createButton(image: #imageLiteral(resourceName: "call-active-wide"), action: #selector(didPressAnswerCall)) acceptIncomingButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_ACCEPT_INCOMING_CALL_LABEL", comment: "Accessibility label for accepting incoming calls") - declineIncomingButton = createButton(imageName: "hangup-active-wide", + declineIncomingButton = createButton(image: #imageLiteral(resourceName: "hangup-active-wide"), action: #selector(didPressDeclineCall)) declineIncomingButton.accessibilityLabel = NSLocalizedString("CALL_VIEW_DECLINE_INCOMING_CALL_LABEL", comment: "Accessibility label for declining incoming calls") - incomingCallView = createContainerForCallControls(controlGroups: [ - [acceptIncomingButton, declineIncomingButton ] - ]) + incomingCallControls = UIStackView(arrangedSubviews: [acceptIncomingButton, declineIncomingButton]) + incomingCallControls.axis = .horizontal + incomingCallControls.alignment = .center + incomingCallControls.distribution = .equalSpacing + + view.addSubview(incomingCallControls) } - func createContainerForCallControls(controlGroups: [[UIView]]) -> UIView { - let containerView = UIView() - self.view.addSubview(containerView) - var rows: [UIView] = [] - for controlGroup in controlGroups { - rows.append(rowWithSubviews(subviews: controlGroup)) - } - let rowspacing = ScaleFromIPhone5To7Plus(6, 7) - var prevRow: UIView? - for row in rows { - containerView.addSubview(row) - row.autoHCenterInSuperview() - if prevRow != nil { - row.autoPinEdge(.top, to: .bottom, of: prevRow!, withOffset: rowspacing) - } - prevRow = row - } - - containerView.setContentHuggingVerticalHigh() - rows.first!.autoPinEdge(toSuperviewEdge: .top) - rows.last!.autoPinEdge(toSuperviewEdge: .bottom) - return containerView - } - - func createButton(imageName: String, action: Selector) -> UIButton { - let image = UIImage(named: imageName) - assert(image != nil) + func createButton(image: UIImage, action: Selector) -> UIButton { let button = UIButton() button.setImage(image, for: .normal) button.imageEdgeInsets = UIEdgeInsets(top: buttonInset(), @@ -481,134 +473,81 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, return button } - // Creates a row containing a given set of subviews. - func rowWithSubviews(subviews: [UIView]) -> UIView { - let row = UIView() - row.setContentHuggingVerticalHigh() - row.autoSetDimension(.height, toSize: buttonSize()) - - if subviews.count > 1 { - // If there's more than one subview in the row, - // space them evenly within the row. - var lastSubview: UIView? - for subview in subviews { - row.addSubview(subview) - subview.setContentHuggingHorizontalHigh() - subview.autoVCenterInSuperview() - - if lastSubview != nil { - let spacer = UIView() - spacer.isHidden = true - row.addSubview(spacer) - spacer.autoPinEdge(.left, to: .right, of: lastSubview!) - spacer.autoPinEdge(.right, to: .left, of: subview) - spacer.setContentHuggingHorizontalLow() - spacer.autoVCenterInSuperview() - - if subviews.count == 2 { - // special case to hardcode the spacer's size when there is only 1 spacer. - spacer.autoSetDimension(.width, toSize: ScaleFromIPhone5To7Plus(46, 60)) - } else { - spacer.autoSetDimension(.width, toSize: ScaleFromIPhone5To7Plus(3, 5)) - } - } - - lastSubview = subview - } - subviews.first!.autoPinEdge(toSuperviewEdge: .left) - subviews.last!.autoPinEdge(toSuperviewEdge: .right) - } else if subviews.count == 1 { - // If there's only one subview in this row, center it. - let subview = subviews.first! - row.addSubview(subview) - subview.autoVCenterInSuperview() - subview.autoPinWidthToSuperview() - } - - return row - } - // MARK: - Layout - override func updateViewConstraints() { - if !hasConstraints { - // We only want to create our constraints once. - // - // Note that constraints are also created elsewhere. - // This only creates the constraints for the top-level contents of the view. - hasConstraints = true + func createViewConstraints() { + let topMargin = CGFloat(40) + let contactVSpacing = CGFloat(3) + let settingsNagHMargin = CGFloat(30) + let ongoingBottomMargin = ScaleFromIPhone5To7Plus(23, 41) + let incomingHMargin = ScaleFromIPhone5To7Plus(30, 56) + let incomingBottomMargin = CGFloat(41) + let settingsNagBottomMargin = CGFloat(41) + let avatarTopSpacing = ScaleFromIPhone5To7Plus(25, 50) + // The buttons have built-in 10% margins, so to appear centered + // the avatar's bottom spacing should be a bit less. + let avatarBottomSpacing = ScaleFromIPhone5To7Plus(18, 41) + // Layout of the local video view is a bit unusual because + // although the view is square, it will be used + let videoPreviewHMargin = CGFloat(0) - let topMargin = CGFloat(40) - let contactHMargin = CGFloat(5) - let contactVSpacing = CGFloat(3) - let ongoingHMargin = ScaleFromIPhone5To7Plus(46, 72) - let incomingHMargin = ScaleFromIPhone5To7Plus(46, 72) - let settingsNagHMargin = CGFloat(30) - let ongoingBottomMargin = ScaleFromIPhone5To7Plus(23, 41) - let incomingBottomMargin = CGFloat(41) - let settingsNagBottomMargin = CGFloat(41) - let avatarTopSpacing = ScaleFromIPhone5To7Plus(25, 50) - // The buttons have built-in 10% margins, so to appear centered - // the avatar's bottom spacing should be a bit less. - let avatarBottomSpacing = ScaleFromIPhone5To7Plus(18, 41) - // Layout of the local video view is a bit unusual because - // although the view is square, it will be used - let videoPreviewHMargin = CGFloat(0) + // Dark blurred background. + blurView.autoPinEdgesToSuperviewEdges() - // Dark blurred background. - blurView.autoPinEdgesToSuperviewEdges() + localVideoView.autoPinTrailingToSuperviewMargin(withInset: videoPreviewHMargin) + localVideoView.autoPinEdge(toSuperviewEdge: .top, withInset: topMargin) + let localVideoSize = ScaleFromIPhone5To7Plus(80, 100) + localVideoView.autoSetDimension(.width, toSize: localVideoSize) + localVideoView.autoSetDimension(.height, toSize: localVideoSize) - localVideoView.autoPinTrailingToSuperviewMargin(withInset: videoPreviewHMargin) - localVideoView.autoPinEdge(toSuperviewEdge: .top, withInset: topMargin) - let localVideoSize = ScaleFromIPhone5To7Plus(80, 100) - localVideoView.autoSetDimension(.width, toSize: localVideoSize) - localVideoView.autoSetDimension(.height, toSize: localVideoSize) + remoteVideoView.autoPinEdgesToSuperviewEdges() - remoteVideoView.autoPinEdgesToSuperviewEdges() + contactNameLabel.autoPinEdge(toSuperviewEdge: .top, withInset: topMargin) + contactNameLabel.autoPinLeadingToSuperviewMargin() + contactNameLabel.setContentHuggingVerticalHigh() + contactNameLabel.setCompressionResistanceHigh() - contactNameLabel.autoPinEdge(toSuperviewEdge: .top, withInset: topMargin) - contactNameLabel.autoPinLeadingToSuperviewMargin(withInset: contactHMargin) - contactNameLabel.setContentHuggingVerticalHigh() - contactNameLabel.setCompressionResistanceHigh() + callStatusLabel.autoPinEdge(.top, to: .bottom, of: contactNameLabel, withOffset: contactVSpacing) + callStatusLabel.autoPinLeadingToSuperviewMargin() + callStatusLabel.setContentHuggingVerticalHigh() + callStatusLabel.setCompressionResistanceHigh() - callStatusLabel.autoPinEdge(.top, to: .bottom, of: contactNameLabel, withOffset: contactVSpacing) - callStatusLabel.autoPinLeadingToSuperviewMargin(withInset: contactHMargin) - callStatusLabel.setContentHuggingVerticalHigh() - callStatusLabel.setCompressionResistanceHigh() + contactAvatarContainerView.autoPinEdge(.top, to: .bottom, of: callStatusLabel, withOffset: +avatarTopSpacing) + contactAvatarContainerView.autoPinEdge(.bottom, to: .top, of: ongoingCallControls, withOffset: -avatarBottomSpacing) + contactAvatarContainerView.autoPinWidthToSuperview(withMargin: avatarTopSpacing) - contactAvatarContainerView.autoPinEdge(.top, to: .bottom, of: callStatusLabel, withOffset: +avatarTopSpacing) - contactAvatarContainerView.autoPinEdge(.bottom, to: .top, of: ongoingCallView, withOffset: -avatarBottomSpacing) - contactAvatarContainerView.autoPinWidthToSuperview(withMargin: avatarTopSpacing) + contactAvatarView.autoCenterInSuperview() - contactAvatarView.autoCenterInSuperview() - - // Ensure ContacAvatarView gets as close as possible to it's superview edges while maintaining - // aspect ratio. - contactAvatarView.autoPinToSquareAspectRatio() - contactAvatarView.autoPinEdge(toSuperviewEdge: .top, withInset: 0, relation: .greaterThanOrEqual) - contactAvatarView.autoPinEdge(toSuperviewEdge: .right, withInset: 0, relation: .greaterThanOrEqual) - contactAvatarView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0, relation: .greaterThanOrEqual) - contactAvatarView.autoPinEdge(toSuperviewEdge: .left, withInset: 0, relation: .greaterThanOrEqual) - NSLayoutConstraint.autoSetPriority(UILayoutPriorityDefaultLow) { - contactAvatarView.autoPinEdgesToSuperviewMargins() - } - - // Ongoing call controls - ongoingCallView.autoPinEdge(toSuperviewEdge: .bottom, withInset: ongoingBottomMargin) - ongoingCallView.autoPinWidthToSuperview(withMargin: ongoingHMargin) - ongoingCallView.setContentHuggingVerticalHigh() - - // Incoming call controls - incomingCallView.autoPinEdge(toSuperviewEdge: .bottom, withInset: incomingBottomMargin) - incomingCallView.autoPinWidthToSuperview(withMargin: incomingHMargin) - incomingCallView.setContentHuggingVerticalHigh() - - // Settings nag views - settingsNagView.autoPinEdge(toSuperviewEdge: .bottom, withInset: settingsNagBottomMargin) - settingsNagView.autoPinWidthToSuperview(withMargin: settingsNagHMargin) - settingsNagView.autoPinEdge(.top, to: .bottom, of: callStatusLabel) + // Ensure ContacAvatarView gets as close as possible to it's superview edges while maintaining + // aspect ratio. + contactAvatarView.autoPinToSquareAspectRatio() + contactAvatarView.autoPinEdge(toSuperviewEdge: .top, withInset: 0, relation: .greaterThanOrEqual) + contactAvatarView.autoPinEdge(toSuperviewEdge: .right, withInset: 0, relation: .greaterThanOrEqual) + contactAvatarView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 0, relation: .greaterThanOrEqual) + contactAvatarView.autoPinEdge(toSuperviewEdge: .left, withInset: 0, relation: .greaterThanOrEqual) + NSLayoutConstraint.autoSetPriority(UILayoutPriorityDefaultLow) { + contactAvatarView.autoPinEdgesToSuperviewMargins() } + // Ongoing call controls + ongoingCallControls.autoPinEdge(toSuperviewEdge: .bottom, withInset: ongoingBottomMargin) + ongoingCallControls.autoPinLeadingToSuperviewMargin() + ongoingCallControls.autoPinTrailingToSuperviewMargin() + ongoingCallControls.setContentHuggingVerticalHigh() + + // Incoming call controls + incomingCallControls.autoPinEdge(toSuperviewEdge: .bottom, withInset: incomingBottomMargin) + incomingCallControls.autoPinLeadingToSuperviewMargin(withInset: incomingHMargin) + incomingCallControls.autoPinTrailingToSuperviewMargin(withInset: incomingHMargin) + incomingCallControls.setContentHuggingVerticalHigh() + + // Settings nag views + settingsNagView.autoPinEdge(toSuperviewEdge: .bottom, withInset: settingsNagBottomMargin) + settingsNagView.autoPinWidthToSuperview(withMargin: settingsNagHMargin) + settingsNagView.autoPinEdge(.top, to: .bottom, of: callStatusLabel) + } + + override func updateViewConstraints() { updateRemoteVideoLayout() updateLocalVideoLayout() @@ -738,7 +677,7 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, if isShowingSettingsNag { settingsNagView.isHidden = false contactAvatarView.isHidden = true - ongoingCallView.isHidden = true + ongoingCallControls.isHidden = true return } @@ -752,10 +691,10 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, // Show Incoming vs. Ongoing call controls let isRinging = callState == .localRinging - incomingCallView.isHidden = !isRinging - incomingCallView.isUserInteractionEnabled = isRinging - ongoingCallView.isHidden = isRinging - ongoingCallView.isUserInteractionEnabled = !isRinging + incomingCallControls.isHidden = !isRinging + incomingCallControls.isUserInteractionEnabled = isRinging + ongoingCallControls.isHidden = isRinging + ongoingCallControls.isUserInteractionEnabled = !isRinging // Rework control state if remote video is available. let hasRemoteVideo = !remoteVideoView.isHidden @@ -764,18 +703,19 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, // Rework control state if local video is available. let hasLocalVideo = !localVideoView.isHidden - for subview in [audioModeMuteButton, audioModeVideoButton] { - subview?.isHidden = hasLocalVideo - } - for subview in [videoModeMuteButton, videoModeVideoButton] { - subview?.isHidden = !hasLocalVideo + if hasLocalVideo { + ongoingAudioCallControls.removeFromSuperview() + ongoingCallControls.insertArrangedSubview(ongoingVideoCallControls, at: 0) + } else { + ongoingVideoCallControls.removeFromSuperview() + ongoingCallControls.insertArrangedSubview(ongoingAudioCallControls, at: 0) } // Also hide other controls if user has tapped to hide them. if shouldRemoteVideoControlsBeHidden && !remoteVideoView.isHidden { contactNameLabel.isHidden = true callStatusLabel.isHidden = true - ongoingCallView.isHidden = true + ongoingCallControls.isHidden = true } else { contactNameLabel.isHidden = false callStatusLabel.isHidden = false @@ -915,6 +855,16 @@ class CallViewController: OWSViewController, CallObserver, CallServiceObserver, callUIAdapter.setHasLocalVideo(call: call, hasLocalVideo: hasLocalVideo) } + func didPressFlipCamera(sender: UIButton) { + // toggle value + sender.isSelected = !sender.isSelected + + let useBackCamera = sender.isSelected + Logger.info("\(TAG) in \(#function) with useBackCamera: \(useBackCamera)") + + callUIAdapter.setCameraSource(call: call, useBackCamera: useBackCamera) + } + /** * Denies an incoming not-yet-connected call, Do not confuse with `didPressHangup`. */ diff --git a/Signal/src/call/CallService.swift b/Signal/src/call/CallService.swift index d996aee07f..414b368090 100644 --- a/Signal/src/call/CallService.swift +++ b/Signal/src/call/CallService.swift @@ -790,6 +790,8 @@ protocol CallServiceObserver: class { Logger.info("\(self.logTag) in \(#function): \(call.identifiersForLogs).") switch call.state { + case .remoteRinging: + Logger.debug("\(self.logTag) in \(#function) disconnect while ringing... we'll keep ringing") case .connected: call.state = .reconnecting default: @@ -1164,6 +1166,22 @@ protocol CallServiceObserver: class { self.setHasLocalVideo(hasLocalVideo: true) } + func setCameraSource(call: SignalCall, useBackCamera: Bool) { + SwiftAssertIsOnMainThread(#function) + + guard call == self.call else { + owsFail("\(logTag) in \(#function) for non-current call.") + return + } + + guard let peerConnectionClient = self.peerConnectionClient else { + owsFail("\(logTag) in \(#function) peerConnectionClient was unexpectedly nil") + return + } + + peerConnectionClient.setCameraSource(useBackCamera: useBackCamera) + } + /** * Local client received a message on the WebRTC data channel. * diff --git a/Signal/src/call/PeerConnectionClient.swift b/Signal/src/call/PeerConnectionClient.swift index 8c7c370188..0d94e3f87f 100644 --- a/Signal/src/call/PeerConnectionClient.swift +++ b/Signal/src/call/PeerConnectionClient.swift @@ -120,6 +120,8 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD private var videoCaptureSession: AVCaptureSession? private var videoSender: RTCRtpSender? private var localVideoTrack: RTCVideoTrack? + private var localVideoSource: RTCAVFoundationVideoSource? + // RTCVideoTrack is fragile and prone to throwing exceptions and/or // causing deadlock in its destructor. Therefore we take great care // with this property. @@ -203,6 +205,8 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD // TODO: Revisit the cameraConstraints. let videoSource = factory.avFoundationVideoSource(with: cameraConstraints) + self.localVideoSource = videoSource + self.videoCaptureSession = videoSource.captureSession videoSource.useBackCamera = false @@ -220,6 +224,21 @@ class PeerConnectionClient: NSObject, RTCPeerConnectionDelegate, RTCDataChannelD self.videoSender = videoSender } + public func setCameraSource(useBackCamera: Bool) { + guard let localVideoSource = self.localVideoSource else { + owsFail("\(logTag) in \(#function) localVideoSource was unexpectedly nil") + return + } + + // certain devices, e.g. 16GB iPod touch don't have a back camera + guard localVideoSource.canUseBackCamera else { + owsFail("\(logTag) in \(#function) canUseBackCamera was unexpectedly false") + return + } + + localVideoSource.useBackCamera = useBackCamera + } + public func setLocalVideoEnabled(enabled: Bool) { SwiftAssertIsOnMainThread(#function) diff --git a/Signal/src/call/UserInterface/CallUIAdapter.swift b/Signal/src/call/UserInterface/CallUIAdapter.swift index d7a755658c..d280ec7976 100644 --- a/Signal/src/call/UserInterface/CallUIAdapter.swift +++ b/Signal/src/call/UserInterface/CallUIAdapter.swift @@ -87,11 +87,14 @@ extension CallUIAdaptee { private let adaptee: CallUIAdaptee private let contactsManager: OWSContactsManager internal let audioService: CallAudioService + internal let callService: CallService required init(callService: CallService, contactsManager: OWSContactsManager, notificationsAdapter: CallNotificationsAdapter) { SwiftAssertIsOnMainThread(#function) self.contactsManager = contactsManager + self.callService = callService + if Platform.isSimulator { // CallKit doesn't seem entirely supported in simulator. // e.g. you can't receive calls in the call screen. @@ -247,6 +250,12 @@ extension CallUIAdaptee { call.audioSource = audioSource } + internal func setCameraSource(call: SignalCall, useBackCamera: Bool) { + SwiftAssertIsOnMainThread(#function) + + callService.setCameraSource(call: call, useBackCamera: useBackCamera) + } + // CallKit handles ringing state on it's own. But for non-call kit we trigger ringing start/stop manually. internal var hasManualRinger: Bool { SwiftAssertIsOnMainThread(#function)