Skip to content

Commit

Permalink
create virtualcam
Browse files Browse the repository at this point in the history
  • Loading branch information
jcm committed Dec 21, 2023
1 parent a331ad5 commit c657e95
Show file tree
Hide file tree
Showing 10 changed files with 559 additions and 7 deletions.
3 changes: 2 additions & 1 deletion CaptureSample/CaptureEngine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import AVFAudio
import ScreenCaptureKit
import OSLog
import VideoToolbox
import com_jcm_Record_RecordCameraExtension

/// A structure that contains the video data to render.
struct CapturedFrame {
Expand Down Expand Up @@ -140,7 +141,7 @@ class CaptureEngineStreamOutput: NSObject, SCStreamOutput, SCStreamDelegate {
/// We assume that we don't want to perform lots of work on these queues, so they
/// can be maximally available to handle new frames as they're delivered by SCK.
/// Therefore, immediately dispatch to the frame handler queue.

self.frameHandlerQueue.schedule {
guard sampleBuffer.isValid else {
self.logger.notice("ScreenCaptureKit emitted an invalid frame; skipping it. Timestamp: \(sampleBuffer.presentationTimeStamp.seconds, privacy: .public)")
Expand Down
4 changes: 3 additions & 1 deletion CaptureSample/CaptureSample.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array/>
<array>
<string>$(TeamIdentifierPrefix)com.jcm.Record</string>
</array>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
</dict>
Expand Down
32 changes: 32 additions & 0 deletions CaptureSample/CaptureSampleApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The entry point into this app.
*/
import SwiftUI
import OSLog
import SystemExtensions

let startupTime = Date()

Expand All @@ -22,8 +23,12 @@ struct CaptureSampleApp: App {
@State var selectedPreset: OptionsStorable!

var logger = Logger.application

var extensionActivated = false

@StateObject var screenRecorder = ScreenRecorder()

var requestDelegate = CameraExtensionRequestDelegate()

@State private var currentLog: TextDocument?

Expand Down Expand Up @@ -110,3 +115,30 @@ struct CaptureSampleApp: App {
}
}
}

class CameraExtensionRequestDelegate: NSObject, OSSystemExtensionRequestDelegate {
func request(_ request: OSSystemExtensionRequest, actionForReplacingExtension existing: OSSystemExtensionProperties, withExtension ext: OSSystemExtensionProperties) -> OSSystemExtensionRequest.ReplacementAction {
return .replace
}

func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) {
Logger.application.info("Camera extension requires user approval.")
}

func request(_ request: OSSystemExtensionRequest, didFinishWithResult result: OSSystemExtensionRequest.Result) {
switch result {
case .completed:
Logger.application.info("Camera extension installation is complete.")
case .willCompleteAfterReboot:
Logger.application.info("Camera extension installation will complete after reboot.")
default:
Logger.application.info("poop.")
}
}

func request(_ request: OSSystemExtensionRequest, didFailWithError error: Error) {
Logger.application.error("Camera extension installation failed with error \(error, privacy: .public)")
}


}
10 changes: 6 additions & 4 deletions CaptureSample/ScreenRecorder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class ScreenRecorder: ObservableObject {

private var extensionActivated = false

var requestDelegate = CameraExtensionRequestDelegate()

//MARK: event tap

//private var providerSource: RecordVirtualCamProviderSource!
Expand Down Expand Up @@ -546,15 +548,15 @@ class ScreenRecorder: ObservableObject {
if self.showsEncodePreview {
self.updateEncodePreview()
}
/*if !self.extensionActivated {
let identifier = "com.example.apple-samplecode.CustomCamera.CameraExtension"
if !self.extensionActivated {
let identifier = "com.jcm.Record.RecordCameraExtension"


// Submit an activation request.
let activationRequest = OSSystemExtensionRequest.activationRequest(forExtensionWithIdentifier: identifier, queue: .main)
activationRequest.delegate = self
activationRequest.delegate = self.requestDelegate
OSSystemExtensionManager.shared.submitRequest(activationRequest)
}*/
}
}

/// - Tag: UpdateFilter
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Record is in active development and uses rolling releases. Download the most rec
<video src="https://github.com/jcm93/Record/assets/6864788/53d590c5-e4da-45e7-adf2-8b54d585175b" controls="controls" style="max-width: 730px;"></video>

# Roadmap
* menu bar mode / run in background
* audio settings / audio metering / audio only
* advanced capture options (include some apps, not others)
* compositing?
Expand Down
161 changes: 160 additions & 1 deletion Record.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
0D4875402A81A20800F6D832 /* Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D48753F2A81A20800F6D832 /* Enums.swift */; };
0D52D73F2AB9283F0091AD97 /* EventTap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D52D73E2AB9283F0091AD97 /* EventTap.swift */; };
0D5C529C2AD60B8E00D11280 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D5C529B2AD60B8E00D11280 /* Logging.swift */; };
0D6933012B34B1850019368E /* RecordCameraExtensionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D6933002B34B1850019368E /* RecordCameraExtensionProvider.swift */; };
0D6933032B34B1850019368E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D6933022B34B1850019368E /* main.swift */; };
0D6933082B34B1850019368E /* com.jcm.Record.RecordCameraExtension.systemextension in Embed System Extensions */ = {isa = PBXBuildFile; fileRef = 0D6932FE2B34B1850019368E /* com.jcm.Record.RecordCameraExtension.systemextension */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
0DE5C82F2A95EDC60054AC23 /* PickerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DE5C82E2A95EDC60054AC23 /* PickerView.swift */; };
C470F0812811C5CB00D29309 /* ScreenRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = C470F0802811C5CB00D29309 /* ScreenRecorder.swift */; };
C471DFFB2809F440001D24C9 /* PowerMeter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C471DFF92809F440001D24C9 /* PowerMeter.swift */; };
Expand All @@ -44,13 +47,24 @@
C4EB90D428108656006A595C /* ConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4EB90D328108656006A595C /* ConfigurationView.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
0D6933062B34B1850019368E /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = C4B0DA9F276BA4460015082A /* Project object */;
proxyType = 1;
remoteGlobalIDString = 0D6932FD2B34B1850019368E;
remoteInfo = RecordCameraExtension;
};
/* End PBXContainerItemProxy section */

/* Begin PBXCopyFilesBuildPhase section */
0DBF97E42AEB29CF00D21A33 /* Embed System Extensions */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "$(SYSTEM_EXTENSIONS_FOLDER_PATH)";
dstSubfolderSpec = 16;
files = (
0D6933082B34B1850019368E /* com.jcm.Record.RecordCameraExtension.systemextension in Embed System Extensions */,
);
name = "Embed System Extensions";
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -82,6 +96,11 @@
0D52D73E2AB9283F0091AD97 /* EventTap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventTap.swift; sourceTree = "<group>"; };
0D5C529B2AD60B8E00D11280 /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; };
0D64B0E62B1699E200A8CAFE /* Record-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "Record-Info.plist"; sourceTree = "<group>"; };
0D6932FE2B34B1850019368E /* com.jcm.Record.RecordCameraExtension.systemextension */ = {isa = PBXFileReference; explicitFileType = "wrapper.system-extension"; includeInIndex = 0; path = com.jcm.Record.RecordCameraExtension.systemextension; sourceTree = BUILT_PRODUCTS_DIR; };
0D6933002B34B1850019368E /* RecordCameraExtensionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordCameraExtensionProvider.swift; sourceTree = "<group>"; };
0D6933022B34B1850019368E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
0D6933042B34B1850019368E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
0D6933052B34B1850019368E /* RecordCameraExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RecordCameraExtension.entitlements; sourceTree = "<group>"; };
0DE5C82E2A95EDC60054AC23 /* PickerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickerView.swift; sourceTree = "<group>"; };
0DF11FDD2A6ECBA500B45306 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.markdown; };
7C6C99F1D4B6E3EBA3A7B7DF /* LICENSE.txt */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = LICENSE.txt; sourceTree = "<group>"; };
Expand All @@ -101,6 +120,13 @@
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
0D6932FB2B34B1850019368E /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
C4B0DAA4276BA4460015082A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
Expand Down Expand Up @@ -147,6 +173,17 @@
path = "Test Pattern";
sourceTree = "<group>";
};
0D6932FF2B34B1850019368E /* RecordCameraExtension */ = {
isa = PBXGroup;
children = (
0D6933002B34B1850019368E /* RecordCameraExtensionProvider.swift */,
0D6933022B34B1850019368E /* main.swift */,
0D6933042B34B1850019368E /* Info.plist */,
0D6933052B34B1850019368E /* RecordCameraExtension.entitlements */,
);
path = RecordCameraExtension;
sourceTree = "<group>";
};
58C6EBE29CE5FC0542EA3228 /* LICENSE */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -176,6 +213,7 @@
0D164AAE2A996C6E003F2F7E /* objective-c-xcode.yml */,
0DF11FDD2A6ECBA500B45306 /* README.md */,
C4B0DAA9276BA4460015082A /* CaptureSample */,
0D6932FF2B34B1850019368E /* RecordCameraExtension */,
C4B0DAA8276BA4460015082A /* Products */,
CFC39354E2BF335FE5D2CDFE /* Configuration */,
58C6EBE29CE5FC0542EA3228 /* LICENSE */,
Expand All @@ -186,6 +224,7 @@
isa = PBXGroup;
children = (
C4B0DAA7276BA4460015082A /* Record.app */,
0D6932FE2B34B1850019368E /* com.jcm.Record.RecordCameraExtension.systemextension */,
);
name = Products;
sourceTree = "<group>";
Expand Down Expand Up @@ -232,6 +271,23 @@
/* End PBXGroup section */

/* Begin PBXNativeTarget section */
0D6932FD2B34B1850019368E /* RecordCameraExtension */ = {
isa = PBXNativeTarget;
buildConfigurationList = 0D69330B2B34B1850019368E /* Build configuration list for PBXNativeTarget "RecordCameraExtension" */;
buildPhases = (
0D6932FA2B34B1850019368E /* Sources */,
0D6932FB2B34B1850019368E /* Frameworks */,
0D6932FC2B34B1850019368E /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = RecordCameraExtension;
productName = RecordCameraExtension;
productReference = 0D6932FE2B34B1850019368E /* com.jcm.Record.RecordCameraExtension.systemextension */;
productType = "com.apple.product-type.system-extension";
};
C4B0DAA6276BA4460015082A /* Record */ = {
isa = PBXNativeTarget;
buildConfigurationList = C4B0DAB6276BA4480015082A /* Build configuration list for PBXNativeTarget "Record" */;
Expand All @@ -244,6 +300,7 @@
buildRules = (
);
dependencies = (
0D6933072B34B1850019368E /* PBXTargetDependency */,
);
name = Record;
packageProductDependencies = (
Expand All @@ -260,10 +317,13 @@
attributes = {
BuildIndependentTargetsInParallel = 1;
DefaultBuildSystemTypeForWorkspace = Latest;
LastSwiftUpdateCheck = 1500;
LastSwiftUpdateCheck = 1510;
LastUpgradeCheck = 1500;
ORGANIZATIONNAME = jcm;
TargetAttributes = {
0D6932FD2B34B1850019368E = {
CreatedOnToolsVersion = 15.1;
};
C4B0DAA6276BA4460015082A = {
CreatedOnToolsVersion = 13.3;
LastSwiftMigration = 1330;
Expand All @@ -286,11 +346,19 @@
projectRoot = "";
targets = (
C4B0DAA6276BA4460015082A /* Record */,
0D6932FD2B34B1850019368E /* RecordCameraExtension */,
);
};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
0D6932FC2B34B1850019368E /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
C4B0DAA5276BA4460015082A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
Expand All @@ -304,6 +372,15 @@
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
0D6932FA2B34B1850019368E /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
0D6933032B34B1850019368E /* main.swift in Sources */,
0D6933012B34B1850019368E /* RecordCameraExtensionProvider.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
C4B0DAA3276BA4460015082A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
Expand Down Expand Up @@ -345,7 +422,80 @@
};
/* End PBXSourcesBuildPhase section */

/* Begin PBXTargetDependency section */
0D6933072B34B1850019368E /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 0D6932FD2B34B1850019368E /* RecordCameraExtension */;
targetProxy = 0D6933062B34B1850019368E /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */

/* Begin XCBuildConfiguration section */
0D6933092B34B1850019368E /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = RecordCameraExtension/RecordCameraExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 6E383BMF5Y;
ENABLE_HARDENED_RUNTIME = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = RecordCameraExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = RecordCameraExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 jcm. All rights reserved.";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.jcm.Record.RecordCameraExtension;
PRODUCT_NAME = "$(inherited)";
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
0D69330A2B34B1850019368E /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = RecordCameraExtension/RecordCameraExtension.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 6E383BMF5Y;
ENABLE_HARDENED_RUNTIME = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = RecordCameraExtension/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = RecordCameraExtension;
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 jcm. All rights reserved.";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
"@executable_path/../../../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.jcm.Record.RecordCameraExtension;
PRODUCT_NAME = "$(inherited)";
SKIP_INSTALL = YES;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Release;
};
C4B0DAB4276BA4480015082A /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
Expand Down Expand Up @@ -541,6 +691,15 @@
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
0D69330B2B34B1850019368E /* Build configuration list for PBXNativeTarget "RecordCameraExtension" */ = {
isa = XCConfigurationList;
buildConfigurations = (
0D6933092B34B1850019368E /* Debug */,
0D69330A2B34B1850019368E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C4B0DAA2276BA4460015082A /* Build configuration list for PBXProject "Record" */ = {
isa = XCConfigurationList;
buildConfigurations = (
Expand Down
Loading

0 comments on commit c657e95

Please sign in to comment.