Add iOS
This commit is contained in:
22
ios/Extensions/AVCaptureDevice+isMultiCam.swift
Normal file
22
ios/Extensions/AVCaptureDevice+isMultiCam.swift
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// AVCaptureDevice+isMultiCam.swift
|
||||
// Cuvent
|
||||
//
|
||||
// Created by Marc Rousavy on 07.01.21.
|
||||
// Copyright © 2021 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
|
||||
extension AVCaptureDevice {
|
||||
/**
|
||||
Returns true if the device is a virtual multi-cam, false otherwise.
|
||||
*/
|
||||
var isMultiCam: Bool {
|
||||
if #available(iOS 13.0, *) {
|
||||
return self.isVirtualDevice
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
32
ios/Extensions/AVCaptureDevice+neutralZoom.swift
Normal file
32
ios/Extensions/AVCaptureDevice+neutralZoom.swift
Normal file
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// AVCaptureDevice+neutralZoom.swift
|
||||
// Cuvent
|
||||
//
|
||||
// Created by Marc Rousavy on 10.01.21.
|
||||
// Copyright © 2021 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
|
||||
extension AVCaptureDevice {
|
||||
var neutralZoomFactor: CGFloat {
|
||||
if #available(iOS 13.0, *) {
|
||||
if let indexOfWideAngle = self.constituentDevices.firstIndex(where: { $0.deviceType == .builtInWideAngleCamera }) {
|
||||
if let zoomFactor = self.virtualDeviceSwitchOverVideoZoomFactors[safe: indexOfWideAngle - 1] {
|
||||
return CGFloat(zoomFactor.doubleValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1.0
|
||||
}
|
||||
|
||||
/**
|
||||
Get the value at which the Zoom value is neutral, in percent (0.0-1.0)
|
||||
|
||||
* On single-camera physical devices, this value will always be 0.0
|
||||
* On devices with multiple cameras, e.g. triple-camera, this value will be a value between 0.0 and 1.0, where the field-of-view and zoom looks "neutral"
|
||||
*/
|
||||
var neutralZoomPercent: CGFloat {
|
||||
return (neutralZoomFactor - minAvailableVideoZoomFactor) / (maxAvailableVideoZoomFactor - minAvailableVideoZoomFactor)
|
||||
}
|
||||
}
|
22
ios/Extensions/AVCaptureDevice+physicalDevices.swift
Normal file
22
ios/Extensions/AVCaptureDevice+physicalDevices.swift
Normal file
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// AVCaptureDevice+physicalDevices.swift
|
||||
// Cuvent
|
||||
//
|
||||
// Created by Marc Rousavy on 10.01.21.
|
||||
// Copyright © 2021 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
|
||||
extension AVCaptureDevice {
|
||||
/**
|
||||
If the device is a virtual multi-cam, this returns `constituentDevices`, otherwise this returns an array of a single element, `self`
|
||||
*/
|
||||
var physicalDevices: [AVCaptureDevice] {
|
||||
if #available(iOS 13.0, *), isVirtualDevice {
|
||||
return self.constituentDevices
|
||||
} else {
|
||||
return [self]
|
||||
}
|
||||
}
|
||||
}
|
47
ios/Extensions/AVCaptureDevice.Format+isBetterThan.swift
Normal file
47
ios/Extensions/AVCaptureDevice.Format+isBetterThan.swift
Normal file
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// AVCaptureDevice.Format+isBetterThan.swift
|
||||
// Cuvent
|
||||
//
|
||||
// Created by Marc Rousavy on 19.12.20.
|
||||
// Copyright © 2020 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
|
||||
extension AVCaptureDevice.Format {
|
||||
/** Compares the current Format to the given format and returns true if the current format has either:
|
||||
* 1. Higher still image capture dimensions
|
||||
* 2. Higher video format dimensions (iOS 13.0)
|
||||
* 3. Higher FPS
|
||||
*/
|
||||
func isBetterThan(_ other: AVCaptureDevice.Format) -> Bool {
|
||||
// compare still image dimensions
|
||||
let leftDimensions = highResolutionStillImageDimensions
|
||||
let rightDimensions = other.highResolutionStillImageDimensions
|
||||
if leftDimensions.height * leftDimensions.width > rightDimensions.height * rightDimensions.width
|
||||
{
|
||||
return true
|
||||
}
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
// compare video dimensions
|
||||
let leftVideo = self.formatDescription.presentationDimensions()
|
||||
let rightVideo = other.formatDescription.presentationDimensions()
|
||||
if leftVideo.height * leftVideo.width > rightVideo.height * rightVideo.width
|
||||
{
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// compare max fps
|
||||
if let leftMaxFps = videoSupportedFrameRateRanges.max(by: { $0.maxFrameRate > $1.maxFrameRate }),
|
||||
let rightMaxFps = other.videoSupportedFrameRateRanges.max(by: { $0.maxFrameRate > $1.maxFrameRate })
|
||||
{
|
||||
if leftMaxFps.maxFrameRate > rightMaxFps.maxFrameRate {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
102
ios/Extensions/AVCaptureDevice.Format+matchesFilter.swift
Normal file
102
ios/Extensions/AVCaptureDevice.Format+matchesFilter.swift
Normal file
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// AVCaptureDevice.Format+matchesFilter.swift
|
||||
// Cuvent
|
||||
//
|
||||
// Created by Marc Rousavy on 15.01.21.
|
||||
// Copyright © 2021 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
|
||||
extension AVCaptureDevice.Format {
|
||||
/**
|
||||
* Checks whether the given filter (NSDictionary, JSON Object) matches the given AVCaptureDevice Format.
|
||||
* The `dictionary` dictionary must be of type `CameraDeviceFormat` (from `CameraDevice.d.ts`)
|
||||
*/
|
||||
func matchesFilter(_ filter: NSDictionary) -> Bool {
|
||||
if let photoHeight = filter.value(forKey: "photoHeight") as? NSNumber {
|
||||
if highResolutionStillImageDimensions.height != photoHeight.intValue {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if let photoWidth = filter.value(forKey: "photoWidth") as? NSNumber {
|
||||
if highResolutionStillImageDimensions.width != photoWidth.intValue {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
if let videoHeight = filter.value(forKey: "videoHeight") as? NSNumber {
|
||||
if self.formatDescription.presentationDimensions().height != CGFloat(videoHeight.doubleValue) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if let videoWidth = filter.value(forKey: "videoWidth") as? NSNumber {
|
||||
if self.formatDescription.presentationDimensions().width != CGFloat(videoWidth.doubleValue) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if let isHighestPhotoQualitySupported = filter.value(forKey: "isHighestPhotoQualitySupported") as? Bool {
|
||||
if self.isHighestPhotoQualitySupported != isHighestPhotoQualitySupported {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
if let maxISO = filter.value(forKey: "maxISO") as? NSNumber {
|
||||
if self.maxISO != maxISO.floatValue {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if let minISO = filter.value(forKey: "minISO") as? NSNumber {
|
||||
if self.minISO != minISO.floatValue {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if let fieldOfView = filter.value(forKey: "fieldOfView") as? NSNumber {
|
||||
if videoFieldOfView != fieldOfView.floatValue {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if let maxZoom = filter.value(forKey: "maxZoom") as? NSNumber {
|
||||
if videoMaxZoomFactor != CGFloat(maxZoom.floatValue) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if let colorSpaces = filter.value(forKey: "colorSpaces") as? [String] {
|
||||
let avColorSpaces = colorSpaces.map { try? AVCaptureColorSpace(string: $0) }
|
||||
let allColorSpacesIncluded = supportedColorSpaces.allSatisfy { avColorSpaces.contains($0) }
|
||||
if !allColorSpacesIncluded {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if let frameRateRanges = filter.value(forKey: "frameRateRanges") as? [NSDictionary] {
|
||||
let allFrameRateRangesIncluded = videoSupportedFrameRateRanges.allSatisfy { (range) -> Bool in
|
||||
frameRateRanges.contains { (dict) -> Bool in
|
||||
guard let max = dict.value(forKey: "maxFrameRate") as? NSNumber,
|
||||
let min = dict.value(forKey: "minFrameRate") as? NSNumber
|
||||
else {
|
||||
return false
|
||||
}
|
||||
return range.maxFrameRate == max.doubleValue && range.minFrameRate == min.doubleValue
|
||||
}
|
||||
}
|
||||
if !allFrameRateRangesIncluded {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if let autoFocusSystem = filter.value(forKey: "autoFocusSystem") as? String, let avAutoFocusSystem = try? AVCaptureDevice.Format.AutoFocusSystem(withString: autoFocusSystem) {
|
||||
if self.autoFocusSystem != avAutoFocusSystem {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if let videoStabilizationModes = filter.value(forKey: "videoStabilizationModes") as? [String] {
|
||||
let avVideoStabilizationModes = videoStabilizationModes.map { try? AVCaptureVideoStabilizationMode(withString: $0) }
|
||||
let allStabilizationModesIncluded = self.videoStabilizationModes.allSatisfy { avVideoStabilizationModes.contains($0) }
|
||||
if !allStabilizationModesIncluded {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
51
ios/Extensions/AVCaptureDevice.Format+toDictionary.swift
Normal file
51
ios/Extensions/AVCaptureDevice.Format+toDictionary.swift
Normal file
@@ -0,0 +1,51 @@
|
||||
//
|
||||
// AVCaptureDevice.Format+toDictionary.swift
|
||||
// Cuvent
|
||||
//
|
||||
// Created by Marc Rousavy on 15.01.21.
|
||||
// Copyright © 2021 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
|
||||
private func getAllVideoStabilizationModes() -> [AVCaptureVideoStabilizationMode] {
|
||||
var modes: [AVCaptureVideoStabilizationMode] = [.auto, .cinematic, .off, .standard]
|
||||
if #available(iOS 13, *) {
|
||||
modes.append(.cinematicExtended)
|
||||
}
|
||||
return modes
|
||||
}
|
||||
|
||||
extension AVCaptureDevice.Format {
|
||||
var videoStabilizationModes: [AVCaptureVideoStabilizationMode] {
|
||||
return getAllVideoStabilizationModes().filter { self.isVideoStabilizationModeSupported($0) }
|
||||
}
|
||||
|
||||
func toDictionary() -> [String: Any] {
|
||||
var dict: [String: Any] = [
|
||||
"videoStabilizationModes": videoStabilizationModes.map { $0.descriptor },
|
||||
"autoFocusSystem": autoFocusSystem.descriptor,
|
||||
"photoHeight": highResolutionStillImageDimensions.height,
|
||||
"photoWidth": highResolutionStillImageDimensions.width,
|
||||
"maxISO": maxISO,
|
||||
"minISO": minISO,
|
||||
"fieldOfView": videoFieldOfView,
|
||||
"maxZoom": videoMaxZoomFactor,
|
||||
"colorSpaces": supportedColorSpaces.map { $0.descriptor },
|
||||
"supportsVideoHDR": isVideoHDRSupported,
|
||||
"supportsPhotoHDR": false,
|
||||
"frameRateRanges": videoSupportedFrameRateRanges.map {
|
||||
[
|
||||
"minFrameRate": $0.minFrameRate,
|
||||
"maxFrameRate": $0.maxFrameRate,
|
||||
]
|
||||
},
|
||||
]
|
||||
if #available(iOS 13.0, *) {
|
||||
dict["isHighestPhotoQualitySupported"] = self.isHighestPhotoQualitySupported
|
||||
dict["videoHeight"] = self.formatDescription.presentationDimensions().height
|
||||
dict["videoWidth"] = self.formatDescription.presentationDimensions().width
|
||||
}
|
||||
return dict
|
||||
}
|
||||
}
|
19
ios/Extensions/AVCaptureMovieFileOutput+mirror.swift
Normal file
19
ios/Extensions/AVCaptureMovieFileOutput+mirror.swift
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// AVCaptureMovieFileOutput+mirror.swift
|
||||
// Cuvent
|
||||
//
|
||||
// Created by Marc Rousavy on 18.01.21.
|
||||
// Copyright © 2021 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
|
||||
extension AVCaptureMovieFileOutput {
|
||||
func mirror() {
|
||||
connections.forEach { (connection) in
|
||||
if connection.isVideoMirroringSupported {
|
||||
connection.isVideoMirrored = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
ios/Extensions/AVCapturePhotoOutput+mirror.swift
Normal file
19
ios/Extensions/AVCapturePhotoOutput+mirror.swift
Normal file
@@ -0,0 +1,19 @@
|
||||
//
|
||||
// AVCapturePhotoOutput+mirror.swift
|
||||
// Cuvent
|
||||
//
|
||||
// Created by Marc Rousavy on 18.01.21.
|
||||
// Copyright © 2021 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
|
||||
extension AVCapturePhotoOutput {
|
||||
func mirror() {
|
||||
connections.forEach { (connection) in
|
||||
if connection.isVideoMirroringSupported {
|
||||
connection.isVideoMirrored = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
ios/Extensions/AVFrameRateRange+includes.swift
Normal file
15
ios/Extensions/AVFrameRateRange+includes.swift
Normal file
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// AVFrameRateRange+includes.swift
|
||||
// Cuvent
|
||||
//
|
||||
// Created by Marc Rousavy on 15.01.21.
|
||||
// Copyright © 2021 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
import AVFoundation
|
||||
|
||||
extension AVFrameRateRange {
|
||||
func includes(fps: Double) -> Bool {
|
||||
return fps >= minFrameRate && fps <= maxFrameRate
|
||||
}
|
||||
}
|
18
ios/Extensions/Collection+safe.swift
Normal file
18
ios/Extensions/Collection+safe.swift
Normal file
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// Collection+safe.swift
|
||||
// Cuvent
|
||||
//
|
||||
// Created by Marc Rousavy on 10.01.21.
|
||||
// Copyright © 2021 Facebook. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Collection {
|
||||
/**
|
||||
Returns the element at the specified index if it is within bounds, otherwise nil.
|
||||
*/
|
||||
subscript(safe index: Index) -> Element? {
|
||||
return indices.contains(index) ? self[index] : nil
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user