feat: Support rotation (#301)
* feat: Android: Listen to rotation changes * Only change rotation on configuration change * feat: iOS: Support Rotation * Swift lint
This commit is contained in:
parent
159ff44de3
commit
ef455df865
@ -4,6 +4,7 @@ import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Configuration
|
||||
import android.hardware.camera2.*
|
||||
import android.util.Log
|
||||
import android.util.Range
|
||||
@ -99,6 +100,7 @@ class CameraView(context: Context) : FrameLayout(context), LifecycleOwner {
|
||||
internal var imageCapture: ImageCapture? = null
|
||||
internal var videoCapture: VideoCapture? = null
|
||||
private var imageAnalysis: ImageAnalysis? = null
|
||||
private var preview: Preview? = null
|
||||
|
||||
private var lastFrameProcessorCall = System.currentTimeMillis()
|
||||
|
||||
@ -107,10 +109,13 @@ class CameraView(context: Context) : FrameLayout(context), LifecycleOwner {
|
||||
private val scaleGestureListener: ScaleGestureDetector.SimpleOnScaleGestureListener
|
||||
private val scaleGestureDetector: ScaleGestureDetector
|
||||
private val touchEventListener: OnTouchListener
|
||||
private val orientationEventListener: OrientationEventListener
|
||||
|
||||
private val lifecycleRegistry: LifecycleRegistry
|
||||
private var hostLifecycleState: Lifecycle.State
|
||||
|
||||
private var rotation: Int = Surface.ROTATION_0
|
||||
|
||||
private var minZoom: Float = 1f
|
||||
private var maxZoom: Float = 1f
|
||||
|
||||
@ -164,6 +169,17 @@ class CameraView(context: Context) : FrameLayout(context), LifecycleOwner {
|
||||
}
|
||||
scaleGestureDetector = ScaleGestureDetector(context, scaleGestureListener)
|
||||
touchEventListener = OnTouchListener { _, event -> return@OnTouchListener scaleGestureDetector.onTouchEvent(event) }
|
||||
orientationEventListener = object : OrientationEventListener(context) {
|
||||
override fun onOrientationChanged(orientation : Int) {
|
||||
rotation = when (orientation) {
|
||||
in 45..134 -> Surface.ROTATION_270
|
||||
in 135..224 -> Surface.ROTATION_180
|
||||
in 225..314 -> Surface.ROTATION_90
|
||||
else -> Surface.ROTATION_0
|
||||
}
|
||||
}
|
||||
}
|
||||
orientationEventListener.enable()
|
||||
|
||||
hostLifecycleState = Lifecycle.State.INITIALIZED
|
||||
lifecycleRegistry = LifecycleRegistry(this)
|
||||
@ -186,8 +202,21 @@ class CameraView(context: Context) : FrameLayout(context), LifecycleOwner {
|
||||
})
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
override fun onConfigurationChanged(newConfig: Configuration?) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
|
||||
if (preview?.targetRotation != rotation) {
|
||||
preview?.targetRotation = rotation
|
||||
imageCapture?.targetRotation = rotation
|
||||
imageAnalysis?.targetRotation = rotation
|
||||
videoCapture?.setTargetRotation(rotation)
|
||||
}
|
||||
}
|
||||
|
||||
fun finalize() {
|
||||
mHybridData.resetNative()
|
||||
orientationEventListener.disable()
|
||||
}
|
||||
|
||||
private external fun initHybrid(): HybridData
|
||||
@ -304,8 +333,6 @@ class CameraView(context: Context) : FrameLayout(context), LifecycleOwner {
|
||||
}
|
||||
}
|
||||
|
||||
val rotation = previewView.display.rotation
|
||||
|
||||
val previewBuilder = Preview.Builder()
|
||||
.setTargetRotation(rotation)
|
||||
val imageCaptureBuilder = ImageCapture.Builder()
|
||||
@ -314,8 +341,8 @@ class CameraView(context: Context) : FrameLayout(context), LifecycleOwner {
|
||||
val videoCaptureBuilder = VideoCapture.Builder()
|
||||
.setTargetRotation(rotation)
|
||||
val imageAnalysisBuilder = ImageAnalysis.Builder()
|
||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||
.setTargetRotation(rotation)
|
||||
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
|
||||
.setBackgroundExecutor(CameraViewModule.FrameProcessorThread)
|
||||
|
||||
if (format == null) {
|
||||
@ -395,10 +422,10 @@ class CameraView(context: Context) : FrameLayout(context), LifecycleOwner {
|
||||
useCases.add(imageAnalysis!!)
|
||||
}
|
||||
|
||||
val preview = previewBuilder.build()
|
||||
preview = previewBuilder.build()
|
||||
Log.i(TAG, "Attaching ${useCases.size} use-cases...")
|
||||
camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview, *useCases.toTypedArray())
|
||||
preview.setSurfaceProvider(previewView.surfaceProvider)
|
||||
preview!!.setSurfaceProvider(previewView.surfaceProvider)
|
||||
|
||||
minZoom = camera!!.cameraInfo.zoomState.value?.minZoomRatio ?: 1f
|
||||
maxZoom = camera!!.cameraInfo.zoomState.value?.maxZoomRatio ?: 1f
|
||||
|
@ -322,7 +322,7 @@ PODS:
|
||||
- React
|
||||
- RNVectorIcons (8.1.0):
|
||||
- React-Core
|
||||
- VisionCamera (2.4.2-beta.7):
|
||||
- VisionCamera (2.4.2-beta.10):
|
||||
- React
|
||||
- React-callinvoker
|
||||
- React-Core
|
||||
@ -490,7 +490,7 @@ SPEC CHECKSUMS:
|
||||
RNReanimated: 9c13c86454bfd54dab7505c1a054470bfecd2563
|
||||
RNStaticSafeAreaInsets: 6103cf09647fa427186d30f67b0f5163c1ae8252
|
||||
RNVectorIcons: 31cebfcf94e8cf8686eb5303ae0357da64d7a5a4
|
||||
VisionCamera: d65c038e7a538fbd719f3ff3e7511a2c71111e3a
|
||||
VisionCamera: 1b59ab003dc1d96da223e04e460b702bf85fb866
|
||||
Yoga: 575c581c63e0d35c9a83f4b46d01d63abc1100ac
|
||||
|
||||
PODFILE CHECKSUM: 4b093c1d474775c2eac3268011e4b0b80929d3a2
|
||||
|
@ -59,6 +59,9 @@
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<true/>
|
||||
|
@ -135,9 +135,10 @@ extension CameraView {
|
||||
videoOutput!.setSampleBufferDelegate(self, queue: videoQueue)
|
||||
videoOutput!.alwaysDiscardsLateVideoFrames = false
|
||||
captureSession.addOutput(videoOutput!)
|
||||
videoOutput!.setOrientation(forCameraPosition: videoDeviceInput!.device.position)
|
||||
}
|
||||
|
||||
onOrientationChanged()
|
||||
|
||||
invokeOnInitialized()
|
||||
isReady = true
|
||||
ReactLogger.log(level: .info, message: "Session successfully configured!")
|
||||
|
@ -105,10 +105,20 @@ public final class CameraView: UIView {
|
||||
/// Specifies whether the frameProcessor() function is currently executing. used to drop late frames.
|
||||
internal var isRunningFrameProcessor = false
|
||||
|
||||
/// Returns whether the AVCaptureSession is currently running (reflected by isActive)
|
||||
var isRunning: Bool {
|
||||
return captureSession.isRunning
|
||||
}
|
||||
|
||||
/// Returns the current _interface_ orientation of the main window
|
||||
private var windowInterfaceOrientation: UIInterfaceOrientation {
|
||||
if #available(iOS 13.0, *) {
|
||||
return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation ?? .unknown
|
||||
} else {
|
||||
return UIApplication.shared.statusBarOrientation
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience wrapper to get layer as its statically known type.
|
||||
var videoPreviewLayer: AVCaptureVideoPreviewLayer {
|
||||
// swiftlint:disable force_cast
|
||||
@ -138,6 +148,10 @@ public final class CameraView: UIView {
|
||||
selector: #selector(audioSessionInterrupted),
|
||||
name: AVAudioSession.interruptionNotification,
|
||||
object: AVAudioSession.sharedInstance)
|
||||
NotificationCenter.default.addObserver(self,
|
||||
selector: #selector(onOrientationChanged),
|
||||
name: UIDevice.orientationDidChangeNotification,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
@ -155,6 +169,9 @@ public final class CameraView: UIView {
|
||||
NotificationCenter.default.removeObserver(self,
|
||||
name: AVAudioSession.interruptionNotification,
|
||||
object: AVAudioSession.sharedInstance)
|
||||
NotificationCenter.default.removeObserver(self,
|
||||
name: UIDevice.orientationDidChangeNotification,
|
||||
object: nil)
|
||||
}
|
||||
|
||||
// pragma MARK: Props updating
|
||||
@ -269,6 +286,31 @@ public final class CameraView: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
func onOrientationChanged() {
|
||||
// Updates the Orientation for all rotable connections (outputs) as well as for the preview layer
|
||||
DispatchQueue.main.async {
|
||||
// `windowInterfaceOrientation` and `videoPreviewLayer` should only be accessed from UI thread
|
||||
let isMirrored = self.videoDeviceInput?.device.position == .front
|
||||
let orientation = self.windowInterfaceOrientation
|
||||
|
||||
self.videoPreviewLayer.connection?.setInterfaceOrientation(orientation)
|
||||
|
||||
self.cameraQueue.async {
|
||||
// Run those updates on cameraQueue since they can be blocking.
|
||||
self.captureSession.outputs.forEach { output in
|
||||
output.connections.forEach { connection in
|
||||
if connection.isVideoMirroringSupported {
|
||||
connection.automaticallyAdjustsVideoMirroring = false
|
||||
connection.isVideoMirrored = isMirrored
|
||||
}
|
||||
connection.setInterfaceOrientation(orientation)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pragma MARK: Event Invokers
|
||||
internal final func invokeOnError(_ error: CameraError, cause: NSError? = nil) {
|
||||
ReactLogger.log(level: .error, message: "Invoking onError(): \(error.message)")
|
||||
|
@ -0,0 +1,34 @@
|
||||
//
|
||||
// AVCaptureConnection+setInterfaceOrientation.swift
|
||||
// VisionCamera
|
||||
//
|
||||
// Created by Marc Rousavy on 26.07.21.
|
||||
// Copyright © 2021 mrousavy. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
import Foundation
|
||||
|
||||
extension AVCaptureConnection {
|
||||
/**
|
||||
Sets the `videoOrientation` to the given `orientation` if video orientation setting is supported.
|
||||
*/
|
||||
func setInterfaceOrientation(_ orientation: UIInterfaceOrientation) {
|
||||
if isVideoOrientationSupported {
|
||||
switch orientation {
|
||||
case .portrait:
|
||||
videoOrientation = .portrait
|
||||
case .portraitUpsideDown:
|
||||
videoOrientation = .portraitUpsideDown
|
||||
case .landscapeLeft:
|
||||
videoOrientation = .landscapeLeft
|
||||
case .landscapeRight:
|
||||
videoOrientation = .landscapeRight
|
||||
case .unknown:
|
||||
fallthrough
|
||||
@unknown default:
|
||||
videoOrientation = .portrait
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
//
|
||||
// AVCaptureVideoDataOutput+setOrientation.swift
|
||||
// mrousavy
|
||||
//
|
||||
// Created by Marc Rousavy on 18.01.21.
|
||||
// Copyright © 2021 mrousavy. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
|
||||
extension AVCaptureVideoDataOutput {
|
||||
func setOrientation(forCameraPosition position: AVCaptureDevice.Position) {
|
||||
let isMirrored = position == .front
|
||||
connections.forEach { connection in
|
||||
if connection.isVideoMirroringSupported {
|
||||
connection.automaticallyAdjustsVideoMirroring = false
|
||||
connection.isVideoMirrored = isMirrored
|
||||
}
|
||||
if connection.isVideoOrientationSupported {
|
||||
connection.videoOrientation = .portrait
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
B86DC974260E310600FB17B2 /* CameraView+AVAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86DC973260E310600FB17B2 /* CameraView+AVAudioSession.swift */; };
|
||||
B86DC977260E315100FB17B2 /* CameraView+AVCaptureSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B86DC976260E315100FB17B2 /* CameraView+AVCaptureSession.swift */; };
|
||||
B8805067266798B600EAD7F2 /* JSConsoleHelper.mm in Sources */ = {isa = PBXBuildFile; fileRef = B8805066266798B600EAD7F2 /* JSConsoleHelper.mm */; };
|
||||
B882721026AEB1A100B14107 /* AVCaptureConnection+setInterfaceOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B882720F26AEB1A100B14107 /* AVCaptureConnection+setInterfaceOrientation.swift */; };
|
||||
B887518525E0102000DB86D6 /* PhotoCaptureDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887515C25E0102000DB86D6 /* PhotoCaptureDelegate.swift */; };
|
||||
B887518625E0102000DB86D6 /* CameraView+RecordVideo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887515D25E0102000DB86D6 /* CameraView+RecordVideo.swift */; };
|
||||
B887518725E0102000DB86D6 /* CameraViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = B887515F25E0102000DB86D6 /* CameraViewManager.m */; };
|
||||
@ -29,7 +30,6 @@
|
||||
B887518F25E0102000DB86D6 /* AVCapturePhotoOutput+mirror.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887516825E0102000DB86D6 /* AVCapturePhotoOutput+mirror.swift */; };
|
||||
B887519025E0102000DB86D6 /* AVCaptureDevice.Format+matchesFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887516925E0102000DB86D6 /* AVCaptureDevice.Format+matchesFilter.swift */; };
|
||||
B887519125E0102000DB86D6 /* AVCaptureDevice.Format+toDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887516A25E0102000DB86D6 /* AVCaptureDevice.Format+toDictionary.swift */; };
|
||||
B887519225E0102000DB86D6 /* AVCaptureVideoDataOutput+setOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887516B25E0102000DB86D6 /* AVCaptureVideoDataOutput+setOrientation.swift */; };
|
||||
B887519425E0102000DB86D6 /* MakeReactError.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887516E25E0102000DB86D6 /* MakeReactError.swift */; };
|
||||
B887519525E0102000DB86D6 /* ReactLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887516F25E0102000DB86D6 /* ReactLogger.swift */; };
|
||||
B887519625E0102000DB86D6 /* Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = B887517025E0102000DB86D6 /* Promise.swift */; };
|
||||
@ -94,6 +94,7 @@
|
||||
B86DC976260E315100FB17B2 /* CameraView+AVCaptureSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+AVCaptureSession.swift"; sourceTree = "<group>"; };
|
||||
B8805065266798AB00EAD7F2 /* JSConsoleHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSConsoleHelper.h; sourceTree = "<group>"; };
|
||||
B8805066266798B600EAD7F2 /* JSConsoleHelper.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = JSConsoleHelper.mm; sourceTree = "<group>"; };
|
||||
B882720F26AEB1A100B14107 /* AVCaptureConnection+setInterfaceOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureConnection+setInterfaceOrientation.swift"; sourceTree = "<group>"; };
|
||||
B887515C25E0102000DB86D6 /* PhotoCaptureDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoCaptureDelegate.swift; sourceTree = "<group>"; };
|
||||
B887515D25E0102000DB86D6 /* CameraView+RecordVideo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CameraView+RecordVideo.swift"; sourceTree = "<group>"; };
|
||||
B887515E25E0102000DB86D6 /* CameraBridge.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CameraBridge.h; sourceTree = "<group>"; };
|
||||
@ -107,7 +108,6 @@
|
||||
B887516825E0102000DB86D6 /* AVCapturePhotoOutput+mirror.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AVCapturePhotoOutput+mirror.swift"; sourceTree = "<group>"; };
|
||||
B887516925E0102000DB86D6 /* AVCaptureDevice.Format+matchesFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AVCaptureDevice.Format+matchesFilter.swift"; sourceTree = "<group>"; };
|
||||
B887516A25E0102000DB86D6 /* AVCaptureDevice.Format+toDictionary.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AVCaptureDevice.Format+toDictionary.swift"; sourceTree = "<group>"; };
|
||||
B887516B25E0102000DB86D6 /* AVCaptureVideoDataOutput+setOrientation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AVCaptureVideoDataOutput+setOrientation.swift"; sourceTree = "<group>"; };
|
||||
B887516E25E0102000DB86D6 /* MakeReactError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MakeReactError.swift; sourceTree = "<group>"; };
|
||||
B887516F25E0102000DB86D6 /* ReactLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReactLogger.swift; sourceTree = "<group>"; };
|
||||
B887517025E0102000DB86D6 /* Promise.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Promise.swift; sourceTree = "<group>"; };
|
||||
@ -192,6 +192,7 @@
|
||||
B8D22CDB2642DB4D00234472 /* AVAssetWriterInputPixelBufferAdaptor+initWithVideoSettings.swift */,
|
||||
B86DC970260E2D5200FB17B2 /* AVAudioSession+trySetAllowHaptics.swift */,
|
||||
B80E069F266632F000728644 /* AVAudioSession+updateCategory.swift */,
|
||||
B882720F26AEB1A100B14107 /* AVCaptureConnection+setInterfaceOrientation.swift */,
|
||||
B887516325E0102000DB86D6 /* AVCaptureDevice+neutralZoom.swift */,
|
||||
B887516425E0102000DB86D6 /* AVCaptureDevice.Format+isBetterThan.swift */,
|
||||
B887516525E0102000DB86D6 /* AVCaptureDevice+isMultiCam.swift */,
|
||||
@ -201,7 +202,6 @@
|
||||
B887516925E0102000DB86D6 /* AVCaptureDevice.Format+matchesFilter.swift */,
|
||||
B887516A25E0102000DB86D6 /* AVCaptureDevice.Format+toDictionary.swift */,
|
||||
B88B47462667C8E00091F538 /* AVCaptureSession+setVideoStabilizationMode.swift */,
|
||||
B887516B25E0102000DB86D6 /* AVCaptureVideoDataOutput+setOrientation.swift */,
|
||||
B887516225E0102000DB86D6 /* Collection+safe.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
@ -390,13 +390,13 @@
|
||||
B8103E1C25FF553B007A1684 /* FrameProcessorUtils.mm in Sources */,
|
||||
B887518E25E0102000DB86D6 /* AVFrameRateRange+includes.swift in Sources */,
|
||||
B88751A125E0102000DB86D6 /* AVCaptureDevice.Position+descriptor.swift in Sources */,
|
||||
B882721026AEB1A100B14107 /* AVCaptureConnection+setInterfaceOrientation.swift in Sources */,
|
||||
B86DC977260E315100FB17B2 /* CameraView+AVCaptureSession.swift in Sources */,
|
||||
B887518A25E0102000DB86D6 /* AVCaptureDevice+neutralZoom.swift in Sources */,
|
||||
B88751A325E0102000DB86D6 /* AVCaptureDevice.FlashMode+descriptor.swift in Sources */,
|
||||
B8A751D82609E4B30011C623 /* FrameProcessorRuntimeManager.mm in Sources */,
|
||||
B887519A25E0102000DB86D6 /* AVVideoCodecType+descriptor.swift in Sources */,
|
||||
B88751A825E0102000DB86D6 /* CameraError.swift in Sources */,
|
||||
B887519225E0102000DB86D6 /* AVCaptureVideoDataOutput+setOrientation.swift in Sources */,
|
||||
B88751A625E0102000DB86D6 /* CameraViewManager.swift in Sources */,
|
||||
B887519F25E0102000DB86D6 /* AVCaptureDevice.DeviceType+descriptor.swift in Sources */,
|
||||
B8D22CDC2642DB4D00234472 /* AVAssetWriterInputPixelBufferAdaptor+initWithVideoSettings.swift in Sources */,
|
||||
|
Loading…
Reference in New Issue
Block a user