diff --git a/CameraSample/CameraSample.xcodeproj/project.pbxproj b/CameraSample/CameraSample.xcodeproj/project.pbxproj new file mode 100644 index 0000000..979de19 --- /dev/null +++ b/CameraSample/CameraSample.xcodeproj/project.pbxproj @@ -0,0 +1,357 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + B589BEF325BE747000EA2B10 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B589BEF225BE747000EA2B10 /* AppDelegate.swift */; }; + B589BEF525BE747000EA2B10 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B589BEF425BE747000EA2B10 /* ContentView.swift */; }; + B589BEF725BE747100EA2B10 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B589BEF625BE747100EA2B10 /* Assets.xcassets */; }; + B589BEFA25BE747100EA2B10 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B589BEF925BE747100EA2B10 /* Preview Assets.xcassets */; }; + B589BEFD25BE747100EA2B10 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B589BEFB25BE747100EA2B10 /* Main.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + B589BEEF25BE747000EA2B10 /* CameraSample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CameraSample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + B589BEF225BE747000EA2B10 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + B589BEF425BE747000EA2B10 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + B589BEF625BE747100EA2B10 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + B589BEF925BE747100EA2B10 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + B589BEFC25BE747100EA2B10 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + B589BEFE25BE747100EA2B10 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + B589BEFF25BE747100EA2B10 /* CameraSample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = CameraSample.entitlements; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + B589BEEC25BE747000EA2B10 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + B589BEE625BE747000EA2B10 = { + isa = PBXGroup; + children = ( + B589BEF125BE747000EA2B10 /* CameraSample */, + B589BEF025BE747000EA2B10 /* Products */, + ); + sourceTree = ""; + }; + B589BEF025BE747000EA2B10 /* Products */ = { + isa = PBXGroup; + children = ( + B589BEEF25BE747000EA2B10 /* CameraSample.app */, + ); + name = Products; + sourceTree = ""; + }; + B589BEF125BE747000EA2B10 /* CameraSample */ = { + isa = PBXGroup; + children = ( + B589BEF225BE747000EA2B10 /* AppDelegate.swift */, + B589BEF425BE747000EA2B10 /* ContentView.swift */, + B589BEF625BE747100EA2B10 /* Assets.xcassets */, + B589BEFB25BE747100EA2B10 /* Main.storyboard */, + B589BEFE25BE747100EA2B10 /* Info.plist */, + B589BEFF25BE747100EA2B10 /* CameraSample.entitlements */, + B589BEF825BE747100EA2B10 /* Preview Content */, + ); + path = CameraSample; + sourceTree = ""; + }; + B589BEF825BE747100EA2B10 /* Preview Content */ = { + isa = PBXGroup; + children = ( + B589BEF925BE747100EA2B10 /* Preview Assets.xcassets */, + ); + path = "Preview Content"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + B589BEEE25BE747000EA2B10 /* CameraSample */ = { + isa = PBXNativeTarget; + buildConfigurationList = B589BF0225BE747100EA2B10 /* Build configuration list for PBXNativeTarget "CameraSample" */; + buildPhases = ( + B589BEEB25BE747000EA2B10 /* Sources */, + B589BEEC25BE747000EA2B10 /* Frameworks */, + B589BEED25BE747000EA2B10 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = CameraSample; + productName = Recoord; + productReference = B589BEEF25BE747000EA2B10 /* CameraSample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + B589BEE725BE747000EA2B10 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1200; + LastUpgradeCheck = 1310; + TargetAttributes = { + B589BEEE25BE747000EA2B10 = { + CreatedOnToolsVersion = 12.0.1; + }; + }; + }; + buildConfigurationList = B589BEEA25BE747000EA2B10 /* Build configuration list for PBXProject "CameraSample" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = B589BEE625BE747000EA2B10; + productRefGroup = B589BEF025BE747000EA2B10 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + B589BEEE25BE747000EA2B10 /* CameraSample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + B589BEED25BE747000EA2B10 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B589BEFD25BE747100EA2B10 /* Main.storyboard in Resources */, + B589BEFA25BE747100EA2B10 /* Preview Assets.xcassets in Resources */, + B589BEF725BE747100EA2B10 /* Assets.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + B589BEEB25BE747000EA2B10 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + B589BEF525BE747000EA2B10 /* ContentView.swift in Sources */, + B589BEF325BE747000EA2B10 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + B589BEFB25BE747100EA2B10 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + B589BEFC25BE747100EA2B10 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + B589BF0025BE747100EA2B10 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + B589BF0125BE747100EA2B10 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + B589BF0325BE747100EA2B10 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = CameraSample/CameraSample.entitlements; + CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_ASSET_PATHS = "\"CameraSample/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = CameraSample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PRODUCT_BUNDLE_IDENTIFIER = com.benoitpasquier.CameraSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + B589BF0425BE747100EA2B10 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = CameraSample/CameraSample.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Manual; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_ASSET_PATHS = "\"CameraSample/Preview Content\""; + DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_PREVIEWS = YES; + INFOPLIST_FILE = CameraSample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.15; + PRODUCT_BUNDLE_IDENTIFIER = com.benoitpasquier.CameraSample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + B589BEEA25BE747000EA2B10 /* Build configuration list for PBXProject "CameraSample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B589BF0025BE747100EA2B10 /* Debug */, + B589BF0125BE747100EA2B10 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + B589BF0225BE747100EA2B10 /* Build configuration list for PBXNativeTarget "CameraSample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + B589BF0325BE747100EA2B10 /* Debug */, + B589BF0425BE747100EA2B10 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = B589BEE725BE747000EA2B10 /* Project object */; +} diff --git a/CameraSample/CameraSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/CameraSample/CameraSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..7ce1a3b --- /dev/null +++ b/CameraSample/CameraSample.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/CameraSample/CameraSample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/CameraSample/CameraSample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/CameraSample/CameraSample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/CameraSample/CameraSample.xcodeproj/xcshareddata/xcschemes/CameraSample.xcscheme b/CameraSample/CameraSample.xcodeproj/xcshareddata/xcschemes/CameraSample.xcscheme new file mode 100644 index 0000000..2bee439 --- /dev/null +++ b/CameraSample/CameraSample.xcodeproj/xcshareddata/xcschemes/CameraSample.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CameraSample/CameraSample/AppDelegate.swift b/CameraSample/CameraSample/AppDelegate.swift new file mode 100644 index 0000000..88d0669 --- /dev/null +++ b/CameraSample/CameraSample/AppDelegate.swift @@ -0,0 +1,117 @@ +// +// AppDelegate.swift +// Recoord +// +// Created by Benoit Pasquier on 25/1/21. +// + +import Cocoa +import SwiftUI + +@NSApplicationMain +class AppDelegate: NSObject, NSApplicationDelegate { + + var popover: NSPopover! + var window: NSWindow! + var statusBarItem: NSStatusItem! + let settings = UserSettings(userDefault: .standard) + + func applicationDidFinishLaunching(_ aNotification: Notification) { + // Create the SwiftUI view that provides the window contents. + let contentView = ContentView().environmentObject(settings) + + window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), + styleMask: [.closable, .resizable], + backing: .buffered, defer: false) + window.isReleasedWhenClosed = true + window.center() + window.setFrameAutosaveName("Main Window") + window.contentView = NSHostingView(rootView: contentView) + window.makeKeyAndOrderFront(nil) + window.level = .floating + window.backgroundColor = .clear + window.isMovable = true + window.isMovableByWindowBackground = true + + statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength)) + statusBarItem.menu = makeMenu() + + if let button = self.statusBarItem.button { + button.title = "Recoord" + } + } + + func makeMenu() -> NSMenu { + let menu = NSMenu(title: "Recoord") + + let rectangleItem = NSMenuItem(title: "Rectangle Shape", action: #selector(makeRectangleCamera), keyEquivalent: "") + rectangleItem.state = settings.shape == .rectangle ? .on : .off + menu.addItem(rectangleItem) + + let circleItem = NSMenuItem(title: "Circle Shape", action: #selector(makeCircleCamera), keyEquivalent: "") + circleItem.state = settings.shape == .circle ? .on : .off + menu.addItem(circleItem) + + menu.addItem(.separator()) + + let mirrorItem = NSMenuItem(title: "Mirror camera", action: #selector(mirrorCamera), keyEquivalent: "") + mirrorItem.state = settings.isMirroring ? .on : .off + menu.addItem(mirrorItem) + menu.addItem(.separator()) + + menu.addItem(withTitle: "Quit", action: #selector(NSApplication.shared.terminate(_:)), keyEquivalent: "") + + return menu + } + + @objc func makeRectangleCamera() { + settings.shape = .rectangle + statusBarItem.menu?.item(withTitle: "Rectangle Shape")?.state = .on + statusBarItem.menu?.item(withTitle: "Circle Shape")?.state = .off + + } + + @objc func makeCircleCamera() { + settings.shape = .circle + + statusBarItem.menu?.item(withTitle: "Rectangle Shape")?.state = .off + statusBarItem.menu?.item(withTitle: "Circle Shape")?.state = .on + } + + @objc func mirrorCamera() { + settings.isMirroring.toggle() + + let state: NSControl.StateValue = settings.isMirroring ? .on : .off + statusBarItem.menu?.item(withTitle: "Mirror camera")?.state = state + } + + func applicationWillTerminate(_ aNotification: Notification) { + // Insert code here to tear down your application + } + +} + +class UserSettings: ObservableObject { + @Published var shape = Shape.circle { + didSet { + UserDefaults.standard.setValue(shape.rawValue, forKey: "camera.shape") + } + } + + @Published var isMirroring = false { + didSet { + UserDefaults.standard.setValue(isMirroring, forKey: "camera.isMirroring") + } + } + + enum Shape: String { + case circle + case rectangle + } + + init(userDefault: UserDefaults) { + self.shape = Shape(rawValue: userDefault.string(forKey: "camera.shape") ?? "") ?? .circle + self.isMirroring = userDefault.bool(forKey: "camera.isMirroring") + } +} diff --git a/CameraSample/CameraSample/Assets.xcassets/AccentColor.colorset/Contents.json b/CameraSample/CameraSample/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..eb87897 --- /dev/null +++ b/CameraSample/CameraSample/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CameraSample/CameraSample/Assets.xcassets/AppIcon.appiconset/Contents.json b/CameraSample/CameraSample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..3f00db4 --- /dev/null +++ b/CameraSample/CameraSample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,58 @@ +{ + "images" : [ + { + "idiom" : "mac", + "scale" : "1x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "16x16" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "32x32" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "128x128" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "256x256" + }, + { + "idiom" : "mac", + "scale" : "1x", + "size" : "512x512" + }, + { + "idiom" : "mac", + "scale" : "2x", + "size" : "512x512" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CameraSample/CameraSample/Assets.xcassets/Contents.json b/CameraSample/CameraSample/Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/CameraSample/CameraSample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/CameraSample/CameraSample/Base.lproj/Main.storyboard b/CameraSample/CameraSample/Base.lproj/Main.storyboard new file mode 100644 index 0000000..a99b722 --- /dev/null +++ b/CameraSample/CameraSample/Base.lproj/Main.storyboard @@ -0,0 +1,683 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + Default + + + + + + + Left to Right + + + + + + + Right to Left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CameraSample/CameraSample/CameraSample.entitlements b/CameraSample/CameraSample/CameraSample.entitlements new file mode 100644 index 0000000..d85e002 --- /dev/null +++ b/CameraSample/CameraSample/CameraSample.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.device.audio-input + + com.apple.security.device.camera + + com.apple.security.files.user-selected.read-only + + + diff --git a/CameraSample/CameraSample/ContentView.swift b/CameraSample/CameraSample/ContentView.swift new file mode 100644 index 0000000..ce2d127 --- /dev/null +++ b/CameraSample/CameraSample/ContentView.swift @@ -0,0 +1,212 @@ +// +// ContentView.swift +// Recoord +// +// Created by Benoit Pasquier on 25/1/21. +// + +import AVFoundation +import CoreMediaIO +import Combine +import SwiftUI + +class PlayerView: NSView { + + private weak var settings: UserSettings? + private var previewLayer: AVCaptureVideoPreviewLayer? + private lazy var cancellables = Set() + + init(captureSession: AVCaptureSession, settings: UserSettings? = nil) { + self.settings = settings + previewLayer = AVCaptureVideoPreviewLayer(session: captureSession) + super.init(frame: .zero) + + setupLayer() + } + + func setupLayer() { + + previewLayer?.frame = self.frame + previewLayer?.contentsGravity = .resizeAspectFill + previewLayer?.videoGravity = .resizeAspectFill + previewLayer?.connection?.automaticallyAdjustsVideoMirroring = false + + layer = previewLayer + + settings?.$isMirroring + .subscribe(on: RunLoop.main) + .sink { [weak self] isMirroring in + self?.previewLayer?.connection?.isVideoMirrored = isMirroring + } + .store(in: &cancellables) + } + + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +final class PlayerContainerView: NSViewRepresentable { + typealias NSViewType = PlayerView + + let settings: UserSettings + let captureSession: AVCaptureSession + + init(captureSession: AVCaptureSession, settings: UserSettings) { + self.captureSession = captureSession + self.settings = settings + } + + func makeNSView(context: Context) -> PlayerView { + return PlayerView(captureSession: captureSession, settings: settings) + } + + func updateNSView(_ nsView: PlayerView, context: Context) { } +} + +enum PlayerAction { + case play + case pause +} + +class ContentViewModel: ObservableObject { + + @Published var isGranted: Bool = false + var captureSession: AVCaptureSession! + private var cancellables = Set() + + init() { + captureSession = AVCaptureSession() + setupBindings() + } + + func setupBindings() { + $isGranted + .sink { [weak self] isGranted in + if isGranted { + self?.prepareCampera() + } else { + self?.stopSession() + } + } + .store(in: &cancellables) + + NotificationCenter.default + .publisher(for: NSNotification.Name.AVCaptureDeviceWasConnected) + .sink { [weak self] notification in + print("new device \(notification)") + if let device = notification.object as? AVCaptureDevice { + self?.startSessionForDevice(device) + } + } + .store(in: &cancellables) + } + + func checkAuthorization() { + switch AVCaptureDevice.authorizationStatus(for: .video) { + case .authorized: // The user has previously granted access to the camera. + self.isGranted = true + + case .notDetermined: // The user has not yet been asked for camera access. + AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in + if granted { + DispatchQueue.main.async { + self?.isGranted = granted + } + } + } + + case .denied: // The user has previously denied access. + self.isGranted = false + return + + case .restricted: // The user can't grant access due to restrictions. + self.isGranted = false + return + @unknown default: + fatalError() + } + } + + func startSession() { + guard !captureSession.isRunning else { return } + captureSession.startRunning() + } + + func stopSession() { + guard captureSession.isRunning else { return } + captureSession.stopRunning() + } + + func prepareCampera() { + captureSession.sessionPreset = .high + + if let device = AVCaptureDevice.default(for: .video) { + startSessionForDevice(device) + } + } + + func startSessionForDevice(_ device: AVCaptureDevice) { + do { + let input = try AVCaptureDeviceInput(device: device) + addInput(input) + startSession() + } + catch { + print("Something went wrong - ", error.localizedDescription) + } + } + + func addInput(_ input: AVCaptureInput) { + guard captureSession.canAddInput(input) == true else { + return + } + captureSession.addInput(input) + } +} + +struct ContentView: View { + + @ObservedObject var viewModel = ContentViewModel() + @EnvironmentObject var settings: UserSettings + + init() { + viewModel.checkAuthorization() + } + + var body: some View { + PlayerContainerView(captureSession: viewModel.captureSession, settings: settings) + .clipShape(shape) + } + + var shape: some Shape { + switch settings.shape { + case .circle: + return AnyShape(Circle()) + case .rectangle: + return AnyShape(RoundedRectangle(cornerRadius: 25.0)) + } + } +} + +struct AnyShape: Shape { + init(_ wrapped: S) { + _path = { rect in + let path = wrapped.path(in: rect) + return path + } + } + + func path(in rect: CGRect) -> Path { + return _path(rect) + } + + private let _path: (CGRect) -> Path +} + +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + ContentView() + } +} diff --git a/CameraSample/CameraSample/Info.plist b/CameraSample/CameraSample/Info.plist new file mode 100644 index 0000000..c207ced --- /dev/null +++ b/CameraSample/CameraSample/Info.plist @@ -0,0 +1,36 @@ + + + + + LSUIElement + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSMainStoryboardFile + Main + NSPrincipalClass + NSApplication + NSCameraUsageDescription + This app requires camera access to publish content + NSMicrophoneUsageDescription + This app requires audio access to publish content + + diff --git a/CameraSample/CameraSample/Preview Content/Preview Assets.xcassets/Contents.json b/CameraSample/CameraSample/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/CameraSample/CameraSample/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/README.md b/README.md index 601d52b..9ca3120 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,10 @@ _Swift MVVM Template project has been moved here 👉 [Template Project](https:/ * PrivateWeb - [Create a web browser with WebKit and SwiftUI](https://benoitpasquier.com/create-webview-in-swiftui/) * NumberSample - [Currency TextField in SwiftUI](https://benoitpasquier.com/currency-textfield-in-swiftui/) +### macOS Feature 💻 + +* CameraSample - [Creating a webcam utility app for macOS in SwiftUI](https://benoitpasquier.com/webcam-utility-app-macos-swiftui/) + Wanted to share an improvements? Noticing a typo? I'm open to external contributions :) _Feel free to follow me on [Twitter](https://twitter.com/benoitpasquier_) to get latest update posts and projects._