Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 22 additions & 31 deletions Capturer.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,6 @@
4B2D586A25346C730080E958 /* Frameworks */,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically just apply recommended changes and swift 6

4B2D586B25346C730080E958 /* Resources */,
4B2D5D8625347B7C0080E958 /* Embed Frameworks */,
D04BD3091FCA399F86FC6D03 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
Expand Down Expand Up @@ -439,8 +438,9 @@
4B2D585425346C400080E958 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastSwiftUpdateCheck = 1200;
LastUpgradeCheck = 1300;
LastUpgradeCheck = 1620;
TargetAttributes = {
4B2D585C25346C410080E958 = {
CreatedOnToolsVersion = 12.0;
Expand Down Expand Up @@ -564,23 +564,6 @@
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
D04BD3091FCA399F86FC6D03 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-CapturerDemoSwiftUI/Pods-CapturerDemoSwiftUI-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-CapturerDemoSwiftUI/Pods-CapturerDemoSwiftUI-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-CapturerDemoSwiftUI/Pods-CapturerDemoSwiftUI-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
Expand Down Expand Up @@ -706,6 +689,7 @@
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
Expand Down Expand Up @@ -770,6 +754,7 @@
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
Expand All @@ -795,25 +780,28 @@
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_MODULE_VERIFIER = YES;
INFOPLIST_FILE = Capturer/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
PRODUCT_BUNDLE_IDENTIFIER = app.muukii.ComponentCamera;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
Expand All @@ -823,24 +811,27 @@
buildSettings = {
APPLICATION_EXTENSION_API_ONLY = NO;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "";
CODE_SIGN_STYLE = Automatic;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_MODULE_VERIFIER = YES;
INFOPLIST_FILE = Capturer/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14";
PRODUCT_BUNDLE_IDENTIFIER = app.muukii.ComponentCamera;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SKIP_INSTALL = YES;
SUPPORTS_MACCATALYST = NO;
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
Expand All @@ -860,14 +851,14 @@
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = ComponentCameraDemoSwiftUI/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = app.muukii.ComponentCameraDemoSwiftUI;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
Expand All @@ -887,14 +878,14 @@
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = ComponentCameraDemoSwiftUI/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = app.muukii.ComponentCameraDemoSwiftUI;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
Expand All @@ -912,14 +903,14 @@
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = ComponentCameraDemoUIKit/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = app.muukii.ComponentCameraDemoUIKit;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
Expand All @@ -937,14 +928,14 @@
"$(PROJECT_DIR)",
);
INFOPLIST_FILE = ComponentCameraDemoUIKit/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = app.muukii.ComponentCameraDemoUIKit;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 6.0;
TARGETED_DEVICE_FAMILY = 1;
};
name = Release;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1620"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down Expand Up @@ -34,6 +34,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableAddressSanitizer = "YES"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
Expand Down
97 changes: 60 additions & 37 deletions Capturer/Basic/CaptureBody.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import AVFoundation
import Foundation

public actor CaptureBody {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's illegal to perform start/stop running in between session configurations. Session is also non sendable. Ensure all access is done sequentially.
It also should happen in background (previously could be performed on main thread).
Technically it can still hop on different threads, which could be an issue ?

public final class CaptureBody: @unchecked Sendable {
private let backgroundExecutionQueue = DispatchQueue(label: "Capturer.CaptureBody.backgroundExecutionQueue", qos: .userInitiated)

public struct Configuration: Sendable {

Expand Down Expand Up @@ -41,22 +42,28 @@ public actor CaptureBody {

public var handlers: Handlers = .init()

public let session: AVCaptureSession
private lazy var session: AVCaptureSession = {
backgroundExecutionQueue.sync {
.init()
}
}()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should be let, not lazy?


private var inputNode: InputNodeType?
private var outputNodes: [OutputNodeType] = []

public init(
configuration: Configuration
) {
session = .init()
guard ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] != "1" else { return }
_ = self.session

assert(Utils.checkIfCanUseCameraAccordingToPrivacySensitiveData() == true)
backgroundExecutionQueue.async {

session.performConfiguration {
$0.sessionPreset = configuration.sessionPreset
$0.automaticallyConfiguresCaptureDeviceForWideColor = true
self.session.performConfiguration {
$0.sessionPreset = configuration.sessionPreset
$0.automaticallyConfiguresCaptureDeviceForWideColor = true
}
}

}
Expand All @@ -65,36 +72,42 @@ public actor CaptureBody {

Log.debug(.capture, "Session started")

session.startRunning()
backgroundExecutionQueue.async {
self.session.startRunning()
}
}

public func stop() {

Log.debug(.capture, "Session stopped")

session.stopRunning()
backgroundExecutionQueue.async {
self.session.stopRunning()
}
}

public func batchAttaching(
input newInputNode: InputNodeType,
outputs newOutputNodes: [OutputNodeType]
) {

session.performConfiguration { session in
backgroundExecutionQueue.async {

if let currentInputNode = inputNode {
currentInputNode.tearDown(sessionInConfiguring: session)
}
self.session.performConfiguration { session in

inputNode = newInputNode
if let currentInputNode = self.inputNode {
currentInputNode.tearDown(sessionInConfiguring: session)
}

self.inputNode = newInputNode

newInputNode.setUp(sessionInConfiguring: session)
newInputNode.setUp(sessionInConfiguring: session)

outputNodes.append(contentsOf: newOutputNodes)
self.outputNodes.append(contentsOf: newOutputNodes)

session.performConfiguration { session in
newOutputNodes.forEach {
$0.setUp(sessionInConfiguring: session)
session.performConfiguration { session in
newOutputNodes.forEach {
$0.setUp(sessionInConfiguring: session)
}
}
}
}
Expand All @@ -107,18 +120,19 @@ public actor CaptureBody {
public func attach(input newInputNode: some DeviceInputNodeType) {

Log.debug(.capture, "Attach input \(newInputNode)")
backgroundExecutionQueue.async {

session.performConfiguration { session in
self.session.performConfiguration { session in

if let currentNode = inputNode {
currentNode.tearDown(sessionInConfiguring: session)
}
if let currentNode = self.inputNode {
currentNode.tearDown(sessionInConfiguring: session)
}

inputNode = newInputNode
self.inputNode = newInputNode

newInputNode.setUp(sessionInConfiguring: session)
newInputNode.setUp(sessionInConfiguring: session)
}
}

}

public func attach(output component: some OutputNodeType) {
Expand All @@ -127,10 +141,12 @@ public actor CaptureBody {

outputNodes.append(component)

session.performConfiguration {
component.setUp(sessionInConfiguring: $0)
}
backgroundExecutionQueue.async {

self.session.performConfiguration {
component.setUp(sessionInConfiguring: $0)
}
}
}

public func removeCurrentInput() {
Expand All @@ -141,22 +157,29 @@ public actor CaptureBody {

inputNode = nil

session.performConfiguration {
currentInput.tearDown(sessionInConfiguring: $0)
backgroundExecutionQueue.async {

self.session.performConfiguration {
currentInput.tearDown(sessionInConfiguring: $0)
}
}
}

deinit {

Log.debug(.capture, "\(self) deinitializes")

session.performConfiguration { [inputNode] in
inputNode?.tearDown(sessionInConfiguring: $0)
}

session.performConfiguration { [outputNodes] session in
outputNodes.forEach { output in
output.tearDown(sessionInConfiguring: session)
backgroundExecutionQueue.async { [inputNode, outputNodes, session] in

session.performConfiguration { [inputNode] in
inputNode?.tearDown(sessionInConfiguring: $0)
}

session.performConfiguration { [outputNodes] session in
outputNodes.forEach { output in
output.tearDown(sessionInConfiguring: session)
}
}
}

Expand Down
Loading
Loading