Skip to content

Commit 31d0e55

Browse files
authored
Merge pull request #2261 from nextcloud/feat/noid/mirror-local-video-with-front-camera
feat: Mirror local video when using front camera
2 parents 73b636f + bcf7f36 commit 31d0e55

File tree

2 files changed

+55
-19
lines changed

2 files changed

+55
-19
lines changed

NextcloudTalk/Calls/CallViewController.xib

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,6 @@
339339
</subviews>
340340
</view>
341341
</subviews>
342-
<color key="backgroundColor" systemColor="systemBackgroundColor"/>
343342
</view>
344343
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fSY-ZT-xIC" userLabel="Sidebar View">
345344
<rect key="frame" x="666" y="32" width="350" height="1306"/>

NextcloudTalk/Calls/NCCameraController.swift

Lines changed: 55 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class NCCameraController: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate
2222
// State
2323
private var backgroundBlurEnabled = NCUserDefaults.backgroundBlurEnabled()
2424
private var usingFrontCamera = true
25+
private var previousOrientationBeforeUpsideDown: UIDeviceOrientation?
2526
private var deviceOrientation: UIDeviceOrientation = UIDevice.current.orientation
2627
private var videoRotation: RTCVideoRotation = ._0
2728
private var firstLocalViewFrameDrawn = false
@@ -92,26 +93,32 @@ class NCCameraController: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate
9293
}
9394

9495
func switchCamera() {
95-
var newInput: AVCaptureDeviceInput
96+
DispatchQueue.global(qos: .userInitiated).async { [weak self] in
97+
guard let self = self else { return }
98+
var newInput: AVCaptureDeviceInput
9699

97-
if self.usingFrontCamera {
98-
newInput = getBackCameraInput()
99-
} else {
100-
newInput = getFrontCameraInput()
101-
}
100+
if self.usingFrontCamera {
101+
newInput = getBackCameraInput()
102+
} else {
103+
newInput = getFrontCameraInput()
104+
}
102105

103-
if let firstInput = session?.inputs.first {
104-
session?.removeInput(firstInput)
105-
}
106+
if let firstInput = session?.inputs.first {
107+
session?.removeInput(firstInput)
108+
}
106109

107-
// Stop and restart the session to prevent a weird glitch when rotating our local view
108-
self.session?.stopRunning()
109-
self.session?.addInput(newInput)
110+
// Stop and restart the session to prevent a weird glitch when rotating our local view
111+
self.session?.stopRunning()
112+
self.session?.addInput(newInput)
113+
114+
// We need to set the orientation again, because otherweise after switching the video is turned
115+
self.session?.outputs.first?.connections.first?.videoOrientation = .portrait
116+
self.session?.startRunning()
110117

111-
// We need to set the orientation again, because otherweise after switching the video is turned
112-
self.session?.outputs.first?.connections.first?.videoOrientation = .portrait
113-
self.session?.startRunning()
114-
self.usingFrontCamera = !self.usingFrontCamera
118+
// Toggle usingFrontCamera flag and update video rotation
119+
self.usingFrontCamera.toggle()
120+
self.updateVideoRotationBasedOnDeviceOrientation()
121+
}
115122
}
116123

117124
// See ARDCaptureController from the WebRTC project
@@ -326,12 +333,31 @@ class NCCameraController: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate
326333
// Correctly rotate the local image
327334
if videoRotation == ._180 {
328335
ciImage = ciImage.oriented(.down)
336+
337+
// Rotate the local image on iPhones to match the previous landscape orientation when in UpsideDown.
338+
// Since the localView frame doesn't change when transitioning from landscape to UpsideDown.
339+
if UIDevice.current.userInterfaceIdiom == .phone,
340+
let previousLandscapeOrientation = previousOrientationBeforeUpsideDown {
341+
342+
if previousLandscapeOrientation == .landscapeLeft {
343+
ciImage = usingFrontCamera ? ciImage.oriented(.left) : ciImage.oriented(.right)
344+
} else if previousLandscapeOrientation == .landscapeRight {
345+
ciImage = usingFrontCamera ? ciImage.oriented(.right) : ciImage.oriented(.left)
346+
}
347+
}
348+
329349
} else if videoRotation == ._90 {
330350
ciImage = ciImage.oriented(.right)
331351
} else if videoRotation == ._270 {
332352
ciImage = ciImage.oriented(.left)
333353
}
334354

355+
// Mirror local image when using front camera
356+
if usingFrontCamera {
357+
let mirrorTransform = CGAffineTransform(translationX: ciImage.extent.width, y: 0).scaledBy(x: -1, y: 1)
358+
ciImage = ciImage.transformed(by: mirrorTransform)
359+
}
360+
335361
// make sure the image is full screen
336362
let drawSize = localView.drawableSize
337363
let scaleX = drawSize.width / ciImage.extent.width
@@ -366,8 +392,19 @@ class NCCameraController: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate
366392
// MARK: - Notifications
367393

368394
func deviceOrientationDidChangeNotification() {
369-
self.deviceOrientation = UIDevice.current.orientation
370-
self.updateVideoRotationBasedOnDeviceOrientation()
395+
let currentOrientation = UIDevice.current.orientation
396+
397+
// The app doesn't support UpsideDown orientation on iPhones, so the local view stays in landscape when
398+
// transitioning from a landscape orientation to UpsideDown.
399+
// We need to track the last landscape orientation to rotate the local view correctly.
400+
if currentOrientation == .portraitUpsideDown, deviceOrientation.isLandscape {
401+
previousOrientationBeforeUpsideDown = deviceOrientation
402+
} else if currentOrientation != .portraitUpsideDown {
403+
previousOrientationBeforeUpsideDown = nil
404+
}
405+
406+
deviceOrientation = currentOrientation
407+
updateVideoRotationBasedOnDeviceOrientation()
371408
}
372409

373410
func updateVideoRotationBasedOnDeviceOrientation() {

0 commit comments

Comments
 (0)