Compare commits
139 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b923fac2f9 | |||
| 9e103afc7b | |||
| b7e1facded | |||
| 63c9ebb500 | |||
| 3162fa72fb | |||
| 0c9a76a77a | |||
| e2ba1ac1c3 | |||
| 092413fb42 | |||
| 7596d066a6 | |||
| 275f850486 | |||
| e150e00203 | |||
| e2d5e537e3 | |||
| 1ef5e99fe7 | |||
| 62a6c6a89a | |||
| e8dae8c989 | |||
| 2d87e3e7e7 | |||
| 74a3159c70 | |||
| 589e6e77d4 | |||
| d1c840e383 | |||
| ae9c620621 | |||
| 8eabd31931 | |||
| 6c0465b521 | |||
| d77170b2ba | |||
| 419c434503 | |||
| d952f66094 | |||
| c6b94a485a | |||
| 24d9220fad | |||
| 62ed6bc6b7 | |||
| 187c505359 | |||
| 2c1a98a12a | |||
| 2eef6c5b44 | |||
| cabc5535f0 | |||
| e9a88d4a2b | |||
| 7cc44e3eae | |||
| dcddd6b7c3 | |||
| 8e6f8bd448 | |||
| a56f372be8 | |||
| e01e55fa65 | |||
| 3b74f31e22 | |||
| 5d19bc6959 | |||
| b87b020996 | |||
| 7acb7a2dfd | |||
| 84c501810f | |||
| f2f64a6580 | |||
| e388eaa21e | |||
| 95994f707f | |||
| 507ec687c3 | |||
| d79e2d4829 | |||
| 387c864e71 | |||
| c44194230b | |||
| 7870134192 | |||
| 3cb80cba01 | |||
| 658fb4ca07 | |||
| 94e3dddfea | |||
| d3f52c26f4 | |||
| 4dd890a885 | |||
| a320a51e90 | |||
| 1ab12173f4 | |||
| a513e4157f | |||
| 54ad883307 | |||
| 11b1d4738b | |||
| 7a63dbe34a | |||
| 05ee09b1ce | |||
| 30afffd79f | |||
| 3b6e4f60c8 | |||
| 7b5326adcb | |||
| e6d24ec1b4 | |||
| 5c419c3031 | |||
| 0653f11cfd | |||
| bc70751aff | |||
| ebb2d0a485 | |||
| 483bf4563b | |||
| 2015b4db0e | |||
| 85e846ef25 | |||
| 8420a3808f | |||
| 1fb3a03593 | |||
| cd0d7f34cf | |||
| 6a4290e17b | |||
| 3379dbaba4 | |||
| 8e7eafb797 | |||
| 62f90d09ef | |||
| f0c0c393a0 | |||
| c798581992 | |||
| aa625e8a56 | |||
| 0b09ad4483 | |||
| 15124d26cc | |||
| 525c802ca4 | |||
| 1b786d4c3d | |||
| 5d20554db4 | |||
| f79760470e | |||
| 469d1fcc70 | |||
| a7bf5a0894 | |||
| d27f999341 | |||
| de69cfaaf6 | |||
| e6eddcce30 | |||
| eae0dcacd5 | |||
| 81f5045f7c | |||
| 3c3326129e | |||
| 8299d7c0f8 | |||
| d624343a30 | |||
| edcadab554 | |||
| 42613ed528 | |||
| 6a9e2393c6 | |||
| 5786fffaf7 | |||
| 87101908c8 | |||
| 31ef66b903 | |||
| 67fc993dc4 | |||
| 1e9968c1a3 | |||
| 2cac116d16 | |||
| cc7019f687 | |||
| 359e3dcbda | |||
| 7ff20805b0 | |||
| d94ae2125a | |||
| 49ea34ac8b | |||
| dba2398267 | |||
| 71094b27e2 | |||
| 6505e7346d | |||
| 9894d2a9f8 | |||
| ed3468f487 | |||
| f7f98881c3 | |||
| 232dab4f57 | |||
| 6bb55a923b | |||
| 054d23ca50 | |||
| 7fb760dab0 | |||
| 6693e32ecf | |||
| b79af89eb6 | |||
| b0b7f41904 | |||
| 105e8360e4 | |||
| 71f2f27d11 | |||
| 6d1624c730 | |||
| 6aa443b3d9 | |||
| 30087d53d3 | |||
| c7ad4295a1 | |||
| f9cabf3ea4 | |||
| 85f1f00a91 | |||
| a14ef8d6cc | |||
| 0c4bae5fb6 | |||
| f5c87b097c | |||
| 4af1d53984 |
@@ -65,3 +65,4 @@ fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
.DS_Store
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "ARVideoKit"
|
||||
s.version = "1.6.0"
|
||||
s.summary = "Capture & record ARKit videos 📹, photos 🌄, Live Photos 🎇, and GIFs 🎆."
|
||||
s.description = "Enabling developers to capture videos 📹, photos 🌄, Live Photos 🎇, and GIFs 🎆 with augmented reality components."
|
||||
s.homepage = "https://github.com/AFathi/ARVideoKit"
|
||||
s.screenshots = "http://www.ahmedbekhit.com/SK_PREV.gif", "http://www.ahmedbekhit.com/SCN_PREVIEW.gif"
|
||||
s.swift_version = '5.0'
|
||||
|
||||
|
||||
s.license = { :type => "Apache 2.0", :file => "LICENSE" }
|
||||
|
||||
|
||||
s.author = { "Ahmed Fathi Bekhit" => "me@ahmedbekhit.com" }
|
||||
s.social_media_url = "http://ahmedbekhit.com"
|
||||
|
||||
s.platform = :ios, "11.0"
|
||||
|
||||
# ARVideoKit for Swift 5.0
|
||||
s.source = { :git => "https://github.com/AFathi/ARVideoKit.git", :tag => "1.6.0" }
|
||||
s.source_files = "ARVideoKit", "ARVideoKit/**/*.{h,m,swift}"
|
||||
end
|
||||
@@ -7,9 +7,10 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
B466A8B82279E34C00BD7070 /* WeakProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B466A8B72279E34C00BD7070 /* WeakProxy.swift */; };
|
||||
FB2E36891FAE29C00035B8D6 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = FB2E36881FAE29BF0035B8D6 /* LICENSE */; };
|
||||
FB404FFE20D72A190056EA1D /* JPEG.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB404FFD20D72A190056EA1D /* JPEG.swift */; };
|
||||
FBD604DF1FA969DD00EC9804 /* ARVideoKit.h in Headers */ = {isa = PBXBuildFile; fileRef = FBD604DD1FA969DD00EC9804 /* ARVideoKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
FBD604EB1FA96B1C00EC9804 /* video.scnassets in Resources */ = {isa = PBXBuildFile; fileRef = FBD604EA1FA96B1C00EC9804 /* video.scnassets */; };
|
||||
FBD604EE1FA96B2700EC9804 /* ARVideoOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD604EC1FA96B2700EC9804 /* ARVideoOptions.swift */; };
|
||||
FBD604EF1FA96B2700EC9804 /* ARInputViewOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD604ED1FA96B2700EC9804 /* ARInputViewOptions.swift */; };
|
||||
FBD604F51FA96B3300EC9804 /* UIImage+VideoBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD604F01FA96B3300EC9804 /* UIImage+VideoBuffer.swift */; };
|
||||
@@ -22,7 +23,6 @@
|
||||
FBD605001FA96B5500EC9804 /* Generate+GIF.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD604FF1FA96B5500EC9804 /* Generate+GIF.swift */; };
|
||||
FBD605071FA96B6B00EC9804 /* LoveLiver_LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = FBD605021FA96B6B00EC9804 /* LoveLiver_LICENSE */; };
|
||||
FBD605081FA96B6B00EC9804 /* Generate+LivePhoto.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD605031FA96B6B00EC9804 /* Generate+LivePhoto.swift */; };
|
||||
FBD605091FA96B6B00EC9804 /* JPEG.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD605041FA96B6B00EC9804 /* JPEG.swift */; };
|
||||
FBD6050A1FA96B6B00EC9804 /* QuickTimeMov.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD605051FA96B6B00EC9804 /* QuickTimeMov.swift */; };
|
||||
FBD6050B1FA96B6B00EC9804 /* PHLivePhotoPlus.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD605061FA96B6B00EC9804 /* PHLivePhotoPlus.swift */; };
|
||||
FBD605111FA96BA100EC9804 /* ARView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBD6050D1FA96BA100EC9804 /* ARView.swift */; };
|
||||
@@ -33,11 +33,12 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
B466A8B72279E34C00BD7070 /* WeakProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakProxy.swift; sourceTree = "<group>"; };
|
||||
FB2E36881FAE29BF0035B8D6 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
|
||||
FB404FFD20D72A190056EA1D /* JPEG.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JPEG.swift; sourceTree = "<group>"; };
|
||||
FBD604DA1FA969DD00EC9804 /* ARVideoKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ARVideoKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
FBD604DD1FA969DD00EC9804 /* ARVideoKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ARVideoKit.h; sourceTree = "<group>"; };
|
||||
FBD604DE1FA969DD00EC9804 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
FBD604EA1FA96B1C00EC9804 /* video.scnassets */ = {isa = PBXFileReference; lastKnownFileType = wrapper.scnassets; path = video.scnassets; sourceTree = "<group>"; };
|
||||
FBD604EC1FA96B2700EC9804 /* ARVideoOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARVideoOptions.swift; sourceTree = "<group>"; };
|
||||
FBD604ED1FA96B2700EC9804 /* ARInputViewOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARInputViewOptions.swift; sourceTree = "<group>"; };
|
||||
FBD604F01FA96B3300EC9804 /* UIImage+VideoBuffer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImage+VideoBuffer.swift"; sourceTree = "<group>"; };
|
||||
@@ -50,7 +51,6 @@
|
||||
FBD604FF1FA96B5500EC9804 /* Generate+GIF.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Generate+GIF.swift"; sourceTree = "<group>"; };
|
||||
FBD605021FA96B6B00EC9804 /* LoveLiver_LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LoveLiver_LICENSE; sourceTree = "<group>"; };
|
||||
FBD605031FA96B6B00EC9804 /* Generate+LivePhoto.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Generate+LivePhoto.swift"; sourceTree = "<group>"; };
|
||||
FBD605041FA96B6B00EC9804 /* JPEG.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JPEG.swift; sourceTree = "<group>"; };
|
||||
FBD605051FA96B6B00EC9804 /* QuickTimeMov.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QuickTimeMov.swift; sourceTree = "<group>"; };
|
||||
FBD605061FA96B6B00EC9804 /* PHLivePhotoPlus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PHLivePhotoPlus.swift; sourceTree = "<group>"; };
|
||||
FBD6050D1FA96BA100EC9804 /* ARView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARView.swift; sourceTree = "<group>"; };
|
||||
@@ -71,6 +71,14 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
B466A8B62279E33800BD7070 /* Utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B466A8B72279E34C00BD7070 /* WeakProxy.swift */,
|
||||
);
|
||||
path = Utils;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FBA0AA0A1FAD9D9D006C481B /* Writer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -99,7 +107,7 @@
|
||||
FBD604DC1FA969DD00EC9804 /* ARVideoKit */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FBD604E51FA96ACD00EC9804 /* Assets */,
|
||||
B466A8B62279E33800BD7070 /* Utils */,
|
||||
FBD604E61FA96AD400EC9804 /* Enumerations */,
|
||||
FBD604E71FA96AE500EC9804 /* Extensions */,
|
||||
FBD604E81FA96AEC00EC9804 /* Protocols */,
|
||||
@@ -111,14 +119,6 @@
|
||||
path = ARVideoKit;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FBD604E51FA96ACD00EC9804 /* Assets */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FBD604EA1FA96B1C00EC9804 /* video.scnassets */,
|
||||
);
|
||||
path = Assets;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FBD604E61FA96AD400EC9804 /* Enumerations */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -171,11 +171,9 @@
|
||||
FBD605011FA96B5900EC9804 /* Live Photo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FBD605021FA96B6B00EC9804 /* LoveLiver_LICENSE */,
|
||||
FBDBF6BA20F3E009004E757D /* LoveLiver */,
|
||||
FBD605031FA96B6B00EC9804 /* Generate+LivePhoto.swift */,
|
||||
FBD605041FA96B6B00EC9804 /* JPEG.swift */,
|
||||
FBD605061FA96B6B00EC9804 /* PHLivePhotoPlus.swift */,
|
||||
FBD605051FA96B6B00EC9804 /* QuickTimeMov.swift */,
|
||||
);
|
||||
path = "Live Photo";
|
||||
sourceTree = "<group>";
|
||||
@@ -190,6 +188,16 @@
|
||||
path = Sources;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FBDBF6BA20F3E009004E757D /* LoveLiver */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FBD605021FA96B6B00EC9804 /* LoveLiver_LICENSE */,
|
||||
FB404FFD20D72A190056EA1D /* JPEG.swift */,
|
||||
FBD605051FA96B6B00EC9804 /* QuickTimeMov.swift */,
|
||||
);
|
||||
path = LoveLiver;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
@@ -228,12 +236,12 @@
|
||||
FBD604D11FA969DD00EC9804 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0920;
|
||||
LastUpgradeCheck = 0940;
|
||||
ORGANIZATIONNAME = "Ahmed Fathit Bekhit";
|
||||
TargetAttributes = {
|
||||
FBD604D91FA969DD00EC9804 = {
|
||||
CreatedOnToolsVersion = 9.1;
|
||||
LastSwiftMigration = 0910;
|
||||
LastSwiftMigration = 1020;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
@@ -244,6 +252,7 @@
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = FBD604D01FA969DD00EC9804;
|
||||
productRefGroup = FBD604DB1FA969DD00EC9804 /* Products */;
|
||||
@@ -261,7 +270,6 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FB2E36891FAE29C00035B8D6 /* LICENSE in Resources */,
|
||||
FBD604EB1FA96B1C00EC9804 /* video.scnassets in Resources */,
|
||||
FBD605071FA96B6B00EC9804 /* LoveLiver_LICENSE in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -276,11 +284,13 @@
|
||||
FBD604F81FA96B3300EC9804 /* UIView+isType.swift in Sources */,
|
||||
FBD604F71FA96B3300EC9804 /* RecordAR+PhotoRender.swift in Sources */,
|
||||
FBD6050A1FA96B6B00EC9804 /* QuickTimeMov.swift in Sources */,
|
||||
FB404FFE20D72A190056EA1D /* JPEG.swift in Sources */,
|
||||
FBD604FD1FA96B3E00EC9804 /* RenderARDelegate.swift in Sources */,
|
||||
FBD605131FA96BA100EC9804 /* WritAR.swift in Sources */,
|
||||
FBD604F51FA96B3300EC9804 /* UIImage+VideoBuffer.swift in Sources */,
|
||||
FBD605111FA96BA100EC9804 /* ARView.swift in Sources */,
|
||||
FBD604F61FA96B3300EC9804 /* CGImage+Resize.swift in Sources */,
|
||||
B466A8B82279E34C00BD7070 /* WeakProxy.swift in Sources */,
|
||||
FBD604F91FA96B3300EC9804 /* UIViewController+hasType.swift in Sources */,
|
||||
FBD605141FA96BA100EC9804 /* ViewAR.swift in Sources */,
|
||||
FBD604EE1FA96B2700EC9804 /* ARVideoOptions.swift in Sources */,
|
||||
@@ -291,7 +301,6 @@
|
||||
FBD604FC1FA96B3E00EC9804 /* RecordARDelegate.swift in Sources */,
|
||||
FBD604EF1FA96B2700EC9804 /* ARInputViewOptions.swift in Sources */,
|
||||
FBD6050B1FA96B6B00EC9804 /* PHLivePhotoPlus.swift in Sources */,
|
||||
FBD605091FA96B6B00EC9804 /* JPEG.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -302,6 +311,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
BITCODE_GENERATION_MODE = marker;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
@@ -312,6 +322,7 @@
|
||||
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;
|
||||
@@ -319,6 +330,7 @@
|
||||
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_RANGE_LOOP_ANALYSIS = YES;
|
||||
@@ -350,6 +362,7 @@
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
OTHER_CFLAGS = "-fembed-bitcode-marker";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
@@ -362,6 +375,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||
BITCODE_GENERATION_MODE = bitcode;
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
|
||||
@@ -372,6 +386,7 @@
|
||||
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;
|
||||
@@ -379,6 +394,7 @@
|
||||
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_RANGE_LOOP_ANALYSIS = YES;
|
||||
@@ -403,6 +419,7 @@
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
OTHER_CFLAGS = "-fembed-bitcode";
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
VALIDATE_PRODUCT = YES;
|
||||
@@ -434,7 +451,7 @@
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -459,7 +476,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ahmedbekhit.ARVideoKit;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0920"
|
||||
LastUpgradeVersion = "0940"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -26,7 +26,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
@@ -37,7 +36,6 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
language = ""
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0940"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "FBD604D91FA969DD00EC9804"
|
||||
BuildableName = "ARVideoKit.framework"
|
||||
BlueprintName = "ARVideoKit"
|
||||
ReferencedContainer = "container:ARVideoKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "FBD604D91FA969DD00EC9804"
|
||||
BuildableName = "ARVideoKit.framework"
|
||||
BlueprintName = "ARVideoKit"
|
||||
ReferencedContainer = "container:ARVideoKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "FBD604D91FA969DD00EC9804"
|
||||
BuildableName = "ARVideoKit.framework"
|
||||
BlueprintName = "ARVideoKit"
|
||||
ReferencedContainer = "container:ARVideoKit.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
Binary file not shown.
@@ -5,6 +5,7 @@
|
||||
// Created by Ahmed Bekhit on 10/18/17.
|
||||
// Copyright © 2017 Ahmed Fathi Bekhit. All rights reserved.
|
||||
//
|
||||
import Foundation
|
||||
|
||||
/// Allows specifying the final video orientation.
|
||||
@objc public enum ARFrameMode: Int {
|
||||
@@ -12,7 +13,9 @@
|
||||
case aspectFit
|
||||
/// Recommended for iPhone X
|
||||
case aspectFill
|
||||
case viewAspectRatio
|
||||
}
|
||||
|
||||
/// Allows specifying the video rendering frame per second `FPS` rate.
|
||||
@objc public enum ARVideoFrameRate: Int {
|
||||
/// The framework automatically sets the most appropriate `FPS` based on the device support.
|
||||
@@ -22,6 +25,7 @@
|
||||
/// Sets the `FPS` to 60 frames per second.
|
||||
case fps60 = 60
|
||||
}
|
||||
|
||||
/// Allows specifying the final video orientation.
|
||||
@objc public enum ARVideoOrientation: Int {
|
||||
/// The framework automatically sets the video orientation based on the active `ARInputViewOrientation` orientations.
|
||||
@@ -31,6 +35,7 @@
|
||||
/// Sets the video orientation to always landscape.
|
||||
case alwaysLandscape
|
||||
}
|
||||
|
||||
/// Allows specifying when to request Microphone access.
|
||||
@objc public enum RecordARMicrophonePermission: Int {
|
||||
/// The framework automatically requests Microphone access when needed.
|
||||
@@ -38,6 +43,7 @@
|
||||
/// Allows manual permission request.
|
||||
case manual
|
||||
}
|
||||
|
||||
/// An object that returns the AR recorder current status.
|
||||
@objc public enum RecordARStatus: Int {
|
||||
/// The current status of the recorder is unknown.
|
||||
@@ -49,6 +55,7 @@
|
||||
/// The current recorder is paused.
|
||||
case paused
|
||||
}
|
||||
|
||||
/// An object that returns the current Microphone status.
|
||||
@objc public enum RecordARMicrophoneStatus: Int {
|
||||
// The current status of the Microphone access is unknown.
|
||||
|
||||
@@ -8,19 +8,16 @@
|
||||
|
||||
import CoreGraphics
|
||||
|
||||
internal extension CGImage {
|
||||
internal func resize(with ratio:Float) -> CGImage? {
|
||||
let imageWidth = Float(self.width)
|
||||
let imageHeight = Float(self.height)
|
||||
|
||||
let width = imageWidth * ratio
|
||||
let height = imageHeight * ratio
|
||||
extension CGImage {
|
||||
func resize(with ratio: Float) -> CGImage? {
|
||||
let imageWidth: Int = Int(Float(self.width) * ratio)
|
||||
let imageHeight: Int = Int(Float(self.height) * ratio)
|
||||
|
||||
guard let colorSpace = self.colorSpace else { return nil }
|
||||
guard let context = CGContext(data: nil, width: Int(width), height: Int(height), bitsPerComponent: self.bitsPerComponent, bytesPerRow: self.bytesPerRow, space: colorSpace, bitmapInfo: self.alphaInfo.rawValue) else { return nil }
|
||||
guard let context = CGContext(data: nil, width: imageWidth, height: imageHeight, bitsPerComponent: self.bitsPerComponent, bytesPerRow: self.bytesPerRow, space: colorSpace, bitmapInfo: self.alphaInfo.rawValue) else { return nil }
|
||||
|
||||
context.interpolationQuality = .low
|
||||
context.draw(self, in: CGRect(x: 0, y: 0, width: Int(width), height: Int(height)))
|
||||
context.draw(self, in: CGRect(x: 0, y: 0, width: imageWidth, height: imageHeight))
|
||||
|
||||
return context.makeImage()
|
||||
}
|
||||
|
||||
@@ -8,14 +8,16 @@
|
||||
|
||||
import AVFoundation
|
||||
import Photos
|
||||
import UIKit
|
||||
|
||||
@available(iOS 11.0, *)
|
||||
internal extension RecordAR {
|
||||
extension RecordAR {
|
||||
|
||||
internal func adjustTime(current:CMTime, resume:CMTime, pause:CMTime) -> CMTime {
|
||||
func adjustTime(current: CMTime, resume: CMTime, pause: CMTime) -> CMTime {
|
||||
return CMTimeSubtract(current, CMTimeSubtract(resume, pause))
|
||||
}
|
||||
|
||||
internal func imageFromBuffer(buffer:CVPixelBuffer) -> UIImage {
|
||||
func imageFromBuffer(buffer: CVPixelBuffer) -> UIImage {
|
||||
let coreImg = CIImage(cvPixelBuffer: buffer)
|
||||
let context = CIContext()
|
||||
let cgImg = context.createCGImage(coreImg, from: coreImg.extent)
|
||||
@@ -29,8 +31,8 @@ internal extension RecordAR {
|
||||
return false
|
||||
}
|
||||
|
||||
var recentAngle:CGFloat = 0
|
||||
var rotationAngle:CGFloat = 0
|
||||
var recentAngle: CGFloat = 0
|
||||
var rotationAngle: CGFloat = 0
|
||||
switch UIDevice.current.orientation {
|
||||
case .landscapeLeft:
|
||||
rotationAngle = -90
|
||||
@@ -53,8 +55,7 @@ internal extension RecordAR {
|
||||
case .alwaysPortrait:
|
||||
rotationAngle = 0
|
||||
case .alwaysLandscape:
|
||||
if rotationAngle == 90 || rotationAngle == -90 {
|
||||
}else{
|
||||
if rotationAngle != 90 || rotationAngle != -90 {
|
||||
rotationAngle = -90
|
||||
}
|
||||
default:
|
||||
@@ -64,7 +65,7 @@ internal extension RecordAR {
|
||||
return UIImage(cgImage: cgImg!).rotate(by: rotationAngle, flip: false)
|
||||
}
|
||||
|
||||
@objc internal func appWillEnterBackground() {
|
||||
@objc func appWillEnterBackground() {
|
||||
delegate?.recorder(willEnterBackground: status)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
|
||||
import CoreVideo
|
||||
import UIKit
|
||||
internal extension UIImage
|
||||
|
||||
extension UIImage
|
||||
{
|
||||
internal func rotate(by degrees: CGFloat, flip:Bool?=nil) -> UIImage
|
||||
func rotate(by degrees: CGFloat, flip: Bool? = nil) -> UIImage
|
||||
{
|
||||
let radians = CGFloat(degrees * (CGFloat.pi / 180.0))
|
||||
|
||||
@@ -26,10 +27,10 @@ internal extension UIImage
|
||||
if let isFlipped = flip {
|
||||
if !isFlipped {
|
||||
bitmap?.scaleBy(x: 1.0, y: -1.0)
|
||||
}else{
|
||||
} else {
|
||||
bitmap?.scaleBy(x: -1.0, y: -1.0)
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
bitmap?.scaleBy(x: -1.0, y: -1.0)
|
||||
}
|
||||
bitmap?.draw(self.cgImage!, in: CGRect(origin: CGPoint(x: -self.size.width / 2, y: -self.size.height / 2), size: self.size))
|
||||
@@ -41,7 +42,7 @@ internal extension UIImage
|
||||
|
||||
var buffer: CVPixelBuffer? {
|
||||
let attrs = [kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue, kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue] as CFDictionary
|
||||
var pixelBuffer : CVPixelBuffer?
|
||||
var pixelBuffer: CVPixelBuffer?
|
||||
let status = CVPixelBufferCreate(kCFAllocatorDefault, Int(self.size.width), Int(self.size.height), kCVPixelFormatType_32ARGB, attrs, &pixelBuffer)
|
||||
guard (status == kCVReturnSuccess) else {
|
||||
return nil
|
||||
|
||||
@@ -8,17 +8,18 @@
|
||||
|
||||
import UIKit
|
||||
import ARKit
|
||||
|
||||
@available(iOS 11.0, *)
|
||||
internal extension UIScreen {
|
||||
extension UIScreen {
|
||||
/**
|
||||
`isiPhone10` is a boolean that returns if the device is iPhone X or not.
|
||||
*/
|
||||
internal var isiPhone10: Bool {
|
||||
return self.nativeBounds.size == CGSize(width: 1125, height: 2436)
|
||||
var isNotch: Bool {
|
||||
return UIApplication.shared.keyWindow?.safeAreaInsets != UIEdgeInsets.zero
|
||||
}
|
||||
}
|
||||
@available(iOS 11.0, *)
|
||||
internal extension UIView {
|
||||
extension UIView {
|
||||
var parent: UIViewController? {
|
||||
var responder: UIResponder? = self
|
||||
while responder != nil {
|
||||
@@ -31,20 +32,10 @@ internal extension UIView {
|
||||
}
|
||||
|
||||
var isButton: Bool {
|
||||
if let _ = self as? UIButton {
|
||||
return true
|
||||
}else{
|
||||
return false
|
||||
}
|
||||
return (self is UIButton)
|
||||
}
|
||||
|
||||
var isARView: Bool {
|
||||
if let _ = self as? ARSCNView {
|
||||
return true
|
||||
}else if let _ = self as? ARSKView {
|
||||
return true
|
||||
}else {
|
||||
return false
|
||||
}
|
||||
return (self is ARSCNView) || (self is ARSKView)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,17 +8,14 @@
|
||||
|
||||
import UIKit
|
||||
import ARKit
|
||||
|
||||
@available(iOS 11.0, *)
|
||||
internal extension UIViewController {
|
||||
internal var hasARView: Bool {
|
||||
extension UIViewController {
|
||||
var hasARView: Bool {
|
||||
let views = self.view.subviews
|
||||
for v in views {
|
||||
if let _ = v as? ARSCNView {
|
||||
if v is ARSCNView || v is ARSKView {
|
||||
return true
|
||||
}else if let _ = v as? ARSKView {
|
||||
return true
|
||||
}else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.2</string>
|
||||
<string>1.32</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -10,6 +10,7 @@ import Foundation
|
||||
import CoreVideo
|
||||
import CoreMedia
|
||||
import ARKit
|
||||
|
||||
/**
|
||||
The recorder protocol.
|
||||
|
||||
@@ -26,13 +27,26 @@ import ARKit
|
||||
- parameter path: A `URL` object that returns the video file path.
|
||||
- parameter noError: A boolean that returns true when the recorder ends without errors. Otherwise, it returns false.
|
||||
*/
|
||||
func recorder(didEndRecording path:URL, with noError:Bool)
|
||||
func recorder(didEndRecording path: URL, with noError: Bool)
|
||||
|
||||
/**
|
||||
A protocol method that is triggered when a recorder fails recording.
|
||||
- parameter error: An `Error` object that returns the error value.
|
||||
- parameter status: A string that returns the reason of the recorder failure in a string literal format.
|
||||
*/
|
||||
func recorder(didFailRecording error:Error?, and status:String)
|
||||
func recorder(didFailRecording error: Error?, and status: String)
|
||||
|
||||
/**
|
||||
A protocol method that is triggered when a recorder is cancelled.
|
||||
- parameter status: A string that returns the reason the of recorder cancelation in a string literal format.
|
||||
*/
|
||||
@objc optional func recorder(didCancelRecording status: String)
|
||||
|
||||
/**
|
||||
A protocol method that is triggered when a recorder is modified.
|
||||
- parameter duration: A double that returns the duration of current recording
|
||||
*/
|
||||
@objc optional func recorder(didUpdateRecording duration: TimeInterval)
|
||||
|
||||
/**
|
||||
A protocol method that is triggered when the application will resign active.
|
||||
@@ -41,5 +55,5 @@ import ARKit
|
||||
|
||||
- NOTE: Check [applicationWillResignActive(_:)](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1622950-applicationwillresignactive) for more information.
|
||||
*/
|
||||
@objc func recorder(willEnterBackground status:RecordARStatus)
|
||||
@objc func recorder(willEnterBackground status: RecordARStatus)
|
||||
}
|
||||
|
||||
@@ -28,5 +28,5 @@ import ARKit
|
||||
- parameter time: A `CMTime` object that returns the time a buffer was rendered with.
|
||||
- parameter rawBuffer: A `CVPixelBuffer` object that returns the raw buffer.
|
||||
*/
|
||||
func frame(didRender buffer:CVPixelBuffer, with time:CMTime, using rawBuffer:CVPixelBuffer)
|
||||
func frame(didRender buffer: CVPixelBuffer, with time: CMTime, using rawBuffer: CVPixelBuffer)
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ import AVFoundation
|
||||
import ImageIO
|
||||
import MobileCoreServices
|
||||
|
||||
internal class GIFGenerator {
|
||||
internal let gifQueue = DispatchQueue(label:"com.ahmedbekhit.GIFQueue", attributes: .concurrent)
|
||||
fileprivate var currentGIFPath:URL?
|
||||
fileprivate var newGIFPath:URL {
|
||||
class GIFGenerator {
|
||||
let gifQueue = DispatchQueue(label:"com.ahmedbekhit.GIFQueue", attributes: .concurrent)
|
||||
private var currentGIFPath: URL?
|
||||
private var newGIFPath: URL {
|
||||
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
|
||||
let documentsDirectory = paths[0]
|
||||
|
||||
@@ -29,29 +29,32 @@ internal class GIFGenerator {
|
||||
return URL(fileURLWithPath: gifPath, isDirectory: false)
|
||||
}
|
||||
|
||||
internal func generate(gif images:[UIImage], with delay:Float, loop count:Int = 0, adjust:Bool, _ finished: ((_ status: Bool, _ path: URL?) -> Void)? = nil) {
|
||||
func generate(gif images:[UIImage], with delay: Float, loop count: Int = 0, adjust: Bool, _ finished: ((_ status: Bool, _ path: URL?) -> Void)? = nil) {
|
||||
currentGIFPath = newGIFPath
|
||||
gifQueue.async {
|
||||
let gifSettings = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFLoopCount as String: count]]
|
||||
let imageSettings = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFDelayTime as String: delay]]
|
||||
|
||||
guard let path = self.currentGIFPath else{return}
|
||||
guard let destination = CGImageDestinationCreateWithURL(path as CFURL, kUTTypeGIF, images.count, nil) else{finished?(false, nil);return}
|
||||
print(destination)
|
||||
guard let path = self.currentGIFPath else { return }
|
||||
guard let destination = CGImageDestinationCreateWithURL(path as CFURL, kUTTypeGIF, images.count, nil) else {
|
||||
finished?(false, nil)
|
||||
return
|
||||
}
|
||||
logAR.message("\(destination)")
|
||||
CGImageDestinationSetProperties(destination, gifSettings as CFDictionary)
|
||||
for image in images {
|
||||
if let imageRef = image.cgImage {
|
||||
var ratio:Float = 0.0
|
||||
if adjust{ratio=0.5}else{ratio=1.0}
|
||||
var ratio: Float = 0.0
|
||||
if adjust { ratio = 0.5 } else { ratio = 1.0 }
|
||||
CGImageDestinationAddImage(destination, imageRef.resize(with: ratio)!, imageSettings as CFDictionary)
|
||||
}
|
||||
}
|
||||
|
||||
if !CGImageDestinationFinalize(destination){
|
||||
finished?(false, nil);
|
||||
finished?(false, nil)
|
||||
return
|
||||
}else{
|
||||
finished?(true, path);
|
||||
} else {
|
||||
finished?(true, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,32 +8,34 @@
|
||||
|
||||
import AVFoundation
|
||||
import Photos
|
||||
@available(iOS 11.0, *)
|
||||
internal class LivePhotoGenerator {
|
||||
fileprivate var keyPhotoPath:URL?
|
||||
import UIKit
|
||||
|
||||
fileprivate var finalKeyPhotoPath:URL?
|
||||
fileprivate var finalPairedVideoPath:URL?
|
||||
@available(iOS 11.0, *)
|
||||
class LivePhotoGenerator {
|
||||
private var keyPhotoPath: URL?
|
||||
|
||||
private var finalKeyPhotoPath: URL?
|
||||
private var finalPairedVideoPath: URL?
|
||||
|
||||
internal let livePhotoQueue = DispatchQueue(label:"com.ahmedbekhit.livePhotoQueue", attributes: .concurrent)
|
||||
let livePhotoQueue = DispatchQueue(label:"com.ahmedbekhit.livePhotoQueue", attributes: .concurrent)
|
||||
|
||||
internal func generate(livePhoto video:URL?, _ finished: ((_ status: Bool, _ photo: PHLivePhotoPlus?, _ pairedVideoPath:URL?, _ keyFramePath:URL?) -> Void)? = nil) {
|
||||
func generate(livePhoto video: URL?, _ finished: ((_ status: Bool, _ photo: PHLivePhotoPlus?, _ pairedVideoPath: URL?, _ keyFramePath: URL?) -> Void)? = nil) {
|
||||
livePhotoQueue.async {
|
||||
guard let liveFrames = video else{finished?(false, nil, nil, nil); return}
|
||||
guard let liveFrames = video else { finished?(false, nil, nil, nil); return }
|
||||
let asset = AVURLAsset(url: video!)
|
||||
let generator = AVAssetImageGenerator(asset: asset)
|
||||
generator.appliesPreferredTrackTransform = true
|
||||
|
||||
//retrieves the key photo frame from the middle of the video asset
|
||||
let time = NSValue(time: CMTimeMultiplyByFloat64(asset.duration, 0.5))
|
||||
|
||||
let time = NSValue(time: CMTimeMultiplyByFloat64(asset.duration, multiplier: 0.5))
|
||||
|
||||
//generates the key photo CGImage asynchronously
|
||||
generator.generateCGImagesAsynchronously(forTimes: [time], completionHandler:{_,image,_,_,_ in
|
||||
if let cgImg = image, let imgData = UIImagePNGRepresentation(UIImage(cgImage: cgImg)) {
|
||||
generator.generateCGImagesAsynchronously(forTimes: [time], completionHandler: { _, image, _, _, _ in
|
||||
if let cgImg = image, let imgData = UIImage(cgImage: cgImg).pngData() {
|
||||
do {
|
||||
self.keyPhotoPath = self.newPath(for: true, and: false)
|
||||
try imgData.write(to: self.keyPhotoPath!, options: [.atomic])
|
||||
}catch let error {
|
||||
} catch let error {
|
||||
self.keyPhotoPath = nil
|
||||
logAR.message("An error occurred while capturing a live photo: \(error)")
|
||||
finished?(false, nil, nil, nil)
|
||||
@@ -42,9 +44,9 @@ internal class LivePhotoGenerator {
|
||||
self.finalKeyPhotoPath = self.newPath(for: true, and: true)
|
||||
self.finalPairedVideoPath = self.newPath(for: false, and: true)
|
||||
|
||||
guard let keyFrame = self.keyPhotoPath else{finished?(false, nil, nil, nil); return}
|
||||
guard let keyLiveFrame = self.finalKeyPhotoPath else{finished?(false, nil, nil, nil); return}
|
||||
guard let keyLiveFrames = self.finalPairedVideoPath else{finished?(false, nil, nil, nil); return}
|
||||
guard let keyFrame = self.keyPhotoPath else { finished?(false, nil, nil, nil); return }
|
||||
guard let keyLiveFrame = self.finalKeyPhotoPath else { finished?(false, nil, nil, nil); return }
|
||||
guard let keyLiveFrames = self.finalPairedVideoPath else { finished?(false, nil, nil, nil); return }
|
||||
|
||||
let assetIdentifier = UUID().uuidString
|
||||
|
||||
@@ -61,7 +63,7 @@ internal class LivePhotoGenerator {
|
||||
finalPhoto.pairedVideoPath = keyLiveFrames
|
||||
finished?(true, finalPhoto, finalPhoto.pairedVideoPath, finalPhoto.keyPhotoPath)
|
||||
return
|
||||
}else{
|
||||
} else {
|
||||
let finalPhoto = PHLivePhotoPlus(photo: photo!)
|
||||
finalPhoto.keyPhotoPath = keyLiveFrame
|
||||
finalPhoto.pairedVideoPath = keyLiveFrames
|
||||
@@ -74,7 +76,7 @@ internal class LivePhotoGenerator {
|
||||
}
|
||||
}
|
||||
|
||||
func newPath(for JPEG:Bool, and live:Bool) -> URL {
|
||||
func newPath(for JPEG: Bool, and live: Bool) -> URL {
|
||||
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
|
||||
let documentsDirectory = paths[0]
|
||||
|
||||
@@ -96,9 +98,9 @@ internal class LivePhotoGenerator {
|
||||
|
||||
if JPEG && live {
|
||||
return URL(fileURLWithPath: "\(livePhotosFolder)/\(formatter.string(from: date))AR.jpg", isDirectory: false)
|
||||
}else if JPEG && !live {
|
||||
} else if JPEG && !live {
|
||||
return URL(fileURLWithPath: "\(documentsDirectory)/\(formatter.string(from: date))AR.jpg", isDirectory: false)
|
||||
}else{
|
||||
} else {
|
||||
return URL(fileURLWithPath: "\(livePhotosFolder)/\(formatter.string(from: date))AR.mov", isDirectory: false)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-11
@@ -10,27 +10,27 @@ import Foundation
|
||||
import MobileCoreServices
|
||||
import ImageIO
|
||||
|
||||
internal class JPEG {
|
||||
fileprivate let kFigAppleMakerNote_AssetIdentifier = "17"
|
||||
fileprivate let path : String
|
||||
class JPEG {
|
||||
private let kFigAppleMakerNote_AssetIdentifier = "17"
|
||||
private let path: String
|
||||
|
||||
init(path : String) {
|
||||
init(path: String) {
|
||||
self.path = path
|
||||
}
|
||||
|
||||
func read() -> String? {
|
||||
guard let makerNote = metadata()?.object(forKey: kCGImagePropertyMakerAppleDictionary) as! NSDictionary? else {
|
||||
guard let makerNote = metadata()?.object(forKey: kCGImagePropertyMakerAppleDictionary) as? NSDictionary? else {
|
||||
return nil
|
||||
}
|
||||
return makerNote.object(forKey: kFigAppleMakerNote_AssetIdentifier) as! String?
|
||||
return makerNote?.object(forKey: kFigAppleMakerNote_AssetIdentifier) as? String
|
||||
}
|
||||
|
||||
func write(_ dest : String, assetIdentifier : String) {
|
||||
func write(_ dest: String, assetIdentifier: String) {
|
||||
guard let dest = CGImageDestinationCreateWithURL(URL(fileURLWithPath: dest) as CFURL, kUTTypeJPEG, 1, nil)
|
||||
else { return }
|
||||
defer { CGImageDestinationFinalize(dest) }
|
||||
guard let imageSource = self.imageSource() else { return }
|
||||
guard let metadata = self.metadata()?.mutableCopy() as! NSMutableDictionary! else { return }
|
||||
guard let metadata = self.metadata()?.mutableCopy() as? NSMutableDictionary else { return }
|
||||
|
||||
let makerNote = NSMutableDictionary()
|
||||
makerNote.setObject(assetIdentifier, forKey: kFigAppleMakerNote_AssetIdentifier as NSCopying)
|
||||
@@ -38,19 +38,19 @@ internal class JPEG {
|
||||
CGImageDestinationAddImageFromSource(dest, imageSource, 0, metadata)
|
||||
}
|
||||
|
||||
fileprivate func metadata() -> NSDictionary? {
|
||||
private func metadata() -> NSDictionary? {
|
||||
return self.imageSource().flatMap {
|
||||
CGImageSourceCopyPropertiesAtIndex($0, 0, nil) as NSDictionary?
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func imageSource() -> CGImageSource? {
|
||||
private func imageSource() -> CGImageSource? {
|
||||
return self.data().flatMap {
|
||||
CGImageSourceCreateWithData($0 as CFData, nil)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func data() -> Data? {
|
||||
private func data() -> Data? {
|
||||
return (try? Data(contentsOf: URL(fileURLWithPath: path)))
|
||||
}
|
||||
}
|
||||
+39
-34
@@ -9,20 +9,21 @@
|
||||
|
||||
import Foundation
|
||||
import AVFoundation
|
||||
@available(iOS 11.0, *)
|
||||
internal class QuickTimeMov {
|
||||
fileprivate let kKeyContentIdentifier = "com.apple.quicktime.content.identifier"
|
||||
fileprivate let kKeyStillImageTime = "com.apple.quicktime.still-image-time"
|
||||
fileprivate let kKeySpaceQuickTimeMetadata = "mdta"
|
||||
fileprivate let path : String
|
||||
fileprivate let dummyTimeRange = CMTimeRangeMake(CMTimeMake(0, 1000), CMTimeMake(200, 3000))
|
||||
|
||||
fileprivate lazy var asset : AVURLAsset = {
|
||||
@available(iOS 11.0, *)
|
||||
class QuickTimeMov {
|
||||
private let kKeyContentIdentifier = "com.apple.quicktime.content.identifier"
|
||||
private let kKeyStillImageTime = "com.apple.quicktime.still-image-time"
|
||||
private let kKeySpaceQuickTimeMetadata = "mdta"
|
||||
private let path: String
|
||||
private let dummyTimeRange = CMTimeRange(start: CMTime(value: 0, timescale: 1000), duration: CMTime(value: 200, timescale: 3000))
|
||||
|
||||
private lazy var asset: AVURLAsset = {
|
||||
let url = URL(fileURLWithPath: self.path)
|
||||
return AVURLAsset(url: url)
|
||||
}()
|
||||
|
||||
init(path : String) {
|
||||
init(path: String) {
|
||||
self.path = path
|
||||
}
|
||||
|
||||
@@ -43,7 +44,7 @@ internal class QuickTimeMov {
|
||||
|
||||
while true {
|
||||
guard let buffer = output.copyNextSampleBuffer() else { return nil }
|
||||
if CMSampleBufferGetNumSamples(buffer) != 0 {
|
||||
if CMSampleBufferGetNumSamples(buffer) > 0 {
|
||||
let group = AVTimedMetadataGroup(sampleBuffer: buffer)
|
||||
for item in group?.items ?? [] {
|
||||
if item.key as? String == kKeyStillImageTime &&
|
||||
@@ -57,11 +58,11 @@ internal class QuickTimeMov {
|
||||
return nil
|
||||
}
|
||||
|
||||
func write(_ dest : String, assetIdentifier : String) {
|
||||
func write(_ dest: String, assetIdentifier: String) {
|
||||
|
||||
var audioReader : AVAssetReader? = nil
|
||||
var audioWriterInput : AVAssetWriterInput? = nil
|
||||
var audioReaderOutput : AVAssetReaderOutput? = nil
|
||||
var audioReader: AVAssetReader? = nil
|
||||
var audioWriterInput: AVAssetWriterInput? = nil
|
||||
var audioReaderOutput: AVAssetReaderOutput? = nil
|
||||
do {
|
||||
// --------------------------------------------------
|
||||
// reader for source video
|
||||
@@ -70,9 +71,11 @@ internal class QuickTimeMov {
|
||||
logAR.message("not found video track")
|
||||
return
|
||||
}
|
||||
let readerSettings: [String: AnyObject] = [
|
||||
kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA as AnyObject
|
||||
]
|
||||
let (reader, output) = try self.reader(track,
|
||||
settings: [kCVPixelBufferPixelFormatTypeKey as String:
|
||||
NSNumber(value: kCVPixelFormatType_32BGRA as UInt32)])
|
||||
settings: readerSettings)
|
||||
// --------------------------------------------------
|
||||
// writer for mov
|
||||
// --------------------------------------------------
|
||||
@@ -88,7 +91,7 @@ internal class QuickTimeMov {
|
||||
|
||||
|
||||
let url = URL(fileURLWithPath: self.path)
|
||||
let aAudioAsset : AVAsset = AVAsset(url: url)
|
||||
let aAudioAsset: AVAsset = AVAsset(url: url)
|
||||
|
||||
if aAudioAsset.tracks.count > 1 {
|
||||
logAR.message("Has Audio")
|
||||
@@ -100,15 +103,15 @@ internal class QuickTimeMov {
|
||||
writer.add(audioWriterInput!)
|
||||
}
|
||||
//setup audio reader
|
||||
let audioTrack:AVAssetTrack = aAudioAsset.tracks(withMediaType: AVMediaType.audio).first!
|
||||
let audioTrack: AVAssetTrack = aAudioAsset.tracks(withMediaType: AVMediaType.audio).first!
|
||||
audioReaderOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: nil)
|
||||
|
||||
do{
|
||||
audioReader = try AVAssetReader(asset: aAudioAsset)
|
||||
}catch{
|
||||
fatalError("Unable to read Asset: \(error) : ")
|
||||
} catch {
|
||||
fatalError("Unable to read Asset: \(error): ")
|
||||
}
|
||||
//let audioReader:AVAssetReader = AVAssetReader(asset: aAudioAsset, error: &error)
|
||||
//let audioReader: AVAssetReader = AVAssetReader(asset: aAudioAsset, error: &error)
|
||||
if (audioReader?.canAdd(audioReaderOutput!))! {
|
||||
audioReader?.add(audioReaderOutput!)
|
||||
} else {
|
||||
@@ -125,7 +128,7 @@ internal class QuickTimeMov {
|
||||
// --------------------------------------------------
|
||||
writer.startWriting()
|
||||
reader.startReading()
|
||||
writer.startSession(atSourceTime: kCMTimeZero)
|
||||
writer.startSession(atSourceTime: CMTime.zero)
|
||||
|
||||
// write metadata track
|
||||
adapter.append(AVTimedMetadataGroup(items: [metadataForStillImageTime()],
|
||||
@@ -145,7 +148,7 @@ internal class QuickTimeMov {
|
||||
input.markAsFinished()
|
||||
if reader.status == .completed && aAudioAsset.tracks.count > 1 {
|
||||
audioReader?.startReading()
|
||||
writer.startSession(atSourceTime: kCMTimeZero)
|
||||
writer.startSession(atSourceTime: CMTime.zero)
|
||||
let media_queue = DispatchQueue(label: "assetAudioWriterQueue", attributes: [])
|
||||
audioWriterInput?.requestMediaDataWhenReady(on: media_queue) {
|
||||
while (audioWriterInput?.isReadyForMoreMediaData)! {
|
||||
@@ -154,7 +157,7 @@ internal class QuickTimeMov {
|
||||
if !(audioWriterInput?.append(sampleBuffer2!))! {
|
||||
audioReader?.cancelReading()
|
||||
}
|
||||
}else {
|
||||
} else {
|
||||
audioWriterInput?.markAsFinished()
|
||||
logAR.message("Audio writer finish")
|
||||
writer.finishWriting() {
|
||||
@@ -192,36 +195,38 @@ internal class QuickTimeMov {
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func metadata() -> [AVMetadataItem] {
|
||||
private func metadata() -> [AVMetadataItem] {
|
||||
return asset.metadata(forFormat: AVMetadataFormat.quickTimeMetadata)
|
||||
}
|
||||
|
||||
fileprivate func track(_ mediaType : AVMediaType) -> AVAssetTrack? {
|
||||
private func track(_ mediaType: AVMediaType) -> AVAssetTrack? {
|
||||
return asset.tracks(withMediaType: mediaType).first
|
||||
}
|
||||
|
||||
fileprivate func reader(_ track : AVAssetTrack, settings: [String:AnyObject]?) throws -> (AVAssetReader, AVAssetReaderOutput) {
|
||||
private func reader(_ track: AVAssetTrack, settings: [String: AnyObject]?) throws -> (AVAssetReader, AVAssetReaderOutput) {
|
||||
let output = AVAssetReaderTrackOutput(track: track, outputSettings: settings)
|
||||
let reader = try AVAssetReader(asset: asset)
|
||||
reader.add(output)
|
||||
return (reader, output)
|
||||
}
|
||||
|
||||
fileprivate func metadataAdapter() -> AVAssetWriterInputMetadataAdaptor {
|
||||
let spec : NSDictionary = [
|
||||
private func metadataAdapter() -> AVAssetWriterInputMetadataAdaptor {
|
||||
let spec: NSDictionary = [
|
||||
kCMMetadataFormatDescriptionMetadataSpecificationKey_Identifier as NSString:
|
||||
"\(kKeySpaceQuickTimeMetadata)/\(kKeyStillImageTime)",
|
||||
kCMMetadataFormatDescriptionMetadataSpecificationKey_DataType as NSString:
|
||||
"com.apple.metadata.datatype.int8" ]
|
||||
|
||||
var desc : CMFormatDescription? = nil
|
||||
CMMetadataFormatDescriptionCreateWithMetadataSpecifications(kCFAllocatorDefault, kCMMetadataFormatType_Boxed, [spec] as CFArray, &desc)
|
||||
var desc: CMFormatDescription? = nil
|
||||
CMMetadataFormatDescriptionCreateWithMetadataSpecifications(allocator: kCFAllocatorDefault, metadataType: kCMMetadataFormatType_Boxed, metadataSpecifications: [spec] as CFArray, formatDescriptionOut: &desc)
|
||||
|
||||
// CMFormatDescription.createForMetadata(allocator: kCFAllocatorDefault, metadataType: kCMMetadataFormatType_Boxed, metadataSpecifications: [spec] as CFArray, formatDescriptionOut: &desc)
|
||||
let input = AVAssetWriterInput(mediaType: AVMediaType.metadata,
|
||||
outputSettings: nil, sourceFormatHint: desc)
|
||||
return AVAssetWriterInputMetadataAdaptor(assetWriterInput: input)
|
||||
}
|
||||
|
||||
fileprivate func videoSettings(_ size : CGSize) -> [String:AnyObject] {
|
||||
private func videoSettings(_ size: CGSize) -> [String: AnyObject] {
|
||||
return [
|
||||
AVVideoCodecKey: AVVideoCodecType.h264 as AnyObject,
|
||||
AVVideoWidthKey: size.width as AnyObject,
|
||||
@@ -229,7 +234,7 @@ internal class QuickTimeMov {
|
||||
]
|
||||
}
|
||||
|
||||
fileprivate func metadataFor(_ assetIdentifier: String) -> AVMetadataItem {
|
||||
private func metadataFor(_ assetIdentifier: String) -> AVMetadataItem {
|
||||
let item = AVMutableMetadataItem()
|
||||
item.key = kKeyContentIdentifier as (NSCopying & NSObjectProtocol)?
|
||||
item.keySpace = AVMetadataKeySpace(rawValue: kKeySpaceQuickTimeMetadata)
|
||||
@@ -238,7 +243,7 @@ internal class QuickTimeMov {
|
||||
return item
|
||||
}
|
||||
|
||||
fileprivate func metadataForStillImageTime() -> AVMetadataItem {
|
||||
private func metadataForStillImageTime() -> AVMetadataItem {
|
||||
let item = AVMutableMetadataItem()
|
||||
item.key = kKeyStillImageTime as (NSCopying & NSObjectProtocol)?
|
||||
item.keySpace = AVMetadataKeySpace(rawValue: kKeySpaceQuickTimeMetadata)
|
||||
@@ -7,6 +7,7 @@
|
||||
//
|
||||
|
||||
import Photos
|
||||
|
||||
/**
|
||||
A `PHLivePhotoPlus` object is a `PHLivePhoto` sub-class that contains objects to allow manual exporting of a live photo.
|
||||
|
||||
@@ -17,18 +18,18 @@ import Photos
|
||||
* [Email](mailto:me@ahmedbekhit.com)
|
||||
*/
|
||||
@available(iOS 9.1, *)
|
||||
@objc public class PHLivePhotoPlus: PHLivePhoto {
|
||||
internal var pairedVideoPath:URL?
|
||||
internal var keyPhotoPath:URL?
|
||||
@objc public class PHLivePhotoPlus: NSObject {
|
||||
var pairedVideoPath: URL?
|
||||
var keyPhotoPath: URL?
|
||||
|
||||
/// A `PHLivePhoto` object that returns the Live Photo content from `PHLivePhotoPlus`.
|
||||
@objc public var livePhoto:PHLivePhoto?
|
||||
@objc public var livePhoto: PHLivePhoto?
|
||||
|
||||
@objc public override init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
@objc public init(photo:PHLivePhoto) {
|
||||
@objc public init(photo: PHLivePhoto) {
|
||||
super.init()
|
||||
livePhoto = photo
|
||||
}
|
||||
|
||||
@@ -8,97 +8,123 @@
|
||||
|
||||
import Foundation
|
||||
import ARKit
|
||||
fileprivate var view:Any?
|
||||
fileprivate var renderEngine:SCNRenderer!
|
||||
|
||||
@available(iOS 11.0, *)
|
||||
internal struct RenderAR {
|
||||
internal var ARcontentMode:ARFrameMode!
|
||||
init(_ ARview:Any?, renderer:SCNRenderer, contentMode:ARFrameMode) {
|
||||
struct RenderAR {
|
||||
private var view: Any?
|
||||
private var renderEngine: SCNRenderer!
|
||||
var ARcontentMode: ARFrameMode!
|
||||
|
||||
init(_ ARview: Any?, renderer: SCNRenderer, contentMode: ARFrameMode) {
|
||||
view = ARview
|
||||
renderEngine = renderer
|
||||
ARcontentMode = contentMode
|
||||
}
|
||||
internal let pixelsQueue = DispatchQueue(label:"com.ahmedbekhit.PixelsQueue", attributes: .concurrent)
|
||||
internal var time:CFTimeInterval {return CACurrentMediaTime()}
|
||||
internal var rawBuffer:CVPixelBuffer? {
|
||||
|
||||
let pixelsQueue = DispatchQueue(label: "com.ahmedbekhit.PixelsQueue", attributes: .concurrent)
|
||||
var time: CFTimeInterval { return CACurrentMediaTime()}
|
||||
var rawBuffer: CVPixelBuffer? {
|
||||
if let view = view as? ARSCNView {
|
||||
guard let rawBuffer = view.session.currentFrame?.capturedImage else{return nil}
|
||||
guard let rawBuffer = view.session.currentFrame?.capturedImage else { return nil }
|
||||
return rawBuffer
|
||||
}else if let view = view as? ARSKView {
|
||||
guard let rawBuffer = view.session.currentFrame?.capturedImage else{return nil}
|
||||
} else if let view = view as? ARSKView {
|
||||
guard let rawBuffer = view.session.currentFrame?.capturedImage else { return nil }
|
||||
return rawBuffer
|
||||
} else if view is SCNView {
|
||||
return buffer
|
||||
}
|
||||
return nil
|
||||
}
|
||||
internal var bufferSize:CGSize? {
|
||||
guard let raw = rawBuffer else{return nil};
|
||||
var width = CVPixelBufferGetWidth(raw);
|
||||
var height = CVPixelBufferGetHeight(raw);
|
||||
|
||||
var bufferSize: CGSize? {
|
||||
guard let raw = rawBuffer else { return nil }
|
||||
var width = CVPixelBufferGetWidth(raw)
|
||||
var height = CVPixelBufferGetHeight(raw)
|
||||
|
||||
switch ARcontentMode {
|
||||
case .auto:
|
||||
if UIScreen.main.isiPhone10 {
|
||||
width = Int(UIScreen.main.nativeBounds.width)
|
||||
height = Int(UIScreen.main.nativeBounds.height)
|
||||
}
|
||||
case .aspectFit:
|
||||
width = CVPixelBufferGetWidth(raw);
|
||||
height = CVPixelBufferGetHeight(raw);
|
||||
case .aspectFill:
|
||||
width = Int(UIScreen.main.nativeBounds.width)
|
||||
height = Int(UIScreen.main.nativeBounds.height)
|
||||
default:
|
||||
if UIScreen.main.isiPhone10 {
|
||||
if let contentMode = ARcontentMode {
|
||||
switch contentMode {
|
||||
case .auto:
|
||||
if UIScreen.main.isNotch {
|
||||
width = Int(UIScreen.main.nativeBounds.width)
|
||||
height = Int(UIScreen.main.nativeBounds.height)
|
||||
}
|
||||
case .aspectFit:
|
||||
width = CVPixelBufferGetWidth(raw)
|
||||
height = CVPixelBufferGetHeight(raw)
|
||||
case .aspectFill:
|
||||
width = Int(UIScreen.main.nativeBounds.width)
|
||||
height = Int(UIScreen.main.nativeBounds.height)
|
||||
case .viewAspectRatio where view is UIView:
|
||||
let bufferWidth = CVPixelBufferGetWidth(raw)
|
||||
let bufferHeight = CVPixelBufferGetHeight(raw)
|
||||
let viewSize = (view as! UIView).bounds.size
|
||||
let targetSize = AVMakeRect(aspectRatio: viewSize, insideRect: CGRect(x: 0, y: 0, width: bufferWidth, height: bufferHeight)).size
|
||||
width = Int(targetSize.width)
|
||||
height = Int(targetSize.height)
|
||||
default:
|
||||
if UIScreen.main.isNotch {
|
||||
width = Int(UIScreen.main.nativeBounds.width)
|
||||
height = Int(UIScreen.main.nativeBounds.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if width > height {
|
||||
return CGSize(width: height, height: width)
|
||||
}else{
|
||||
} else {
|
||||
return CGSize(width: width, height: height)
|
||||
}
|
||||
}
|
||||
|
||||
internal var bufferSizeFill:CGSize? {
|
||||
guard let raw = rawBuffer else{return nil};
|
||||
let width = CVPixelBufferGetWidth(raw);
|
||||
let height = CVPixelBufferGetHeight(raw);
|
||||
var bufferSizeFill: CGSize? {
|
||||
guard let raw = rawBuffer else { return nil }
|
||||
let width = CVPixelBufferGetWidth(raw)
|
||||
let height = CVPixelBufferGetHeight(raw)
|
||||
if width > height {
|
||||
return CGSize(width: height, height: width)
|
||||
}else{
|
||||
} else {
|
||||
return CGSize(width: width, height: height)
|
||||
}
|
||||
}
|
||||
|
||||
internal var buffer:CVPixelBuffer? {
|
||||
if let _ = view as? ARSCNView {
|
||||
guard let size = bufferSize else{return nil};
|
||||
var buffer: CVPixelBuffer? {
|
||||
if view is ARSCNView {
|
||||
guard let size = bufferSize else { return nil }
|
||||
//UIScreen.main.bounds.size
|
||||
var renderedFrame:UIImage?
|
||||
var renderedFrame: UIImage?
|
||||
pixelsQueue.sync {
|
||||
renderedFrame = renderEngine.snapshot(atTime: self.time, with: size, antialiasingMode: .none);
|
||||
renderedFrame = renderEngine.snapshot(atTime: self.time, with: size, antialiasingMode: .none)
|
||||
}
|
||||
if let _ = renderedFrame {
|
||||
}else{
|
||||
renderedFrame = renderEngine.snapshot(atTime: time, with: size, antialiasingMode: .none);
|
||||
} else {
|
||||
renderedFrame = renderEngine.snapshot(atTime: time, with: size, antialiasingMode: .none)
|
||||
}
|
||||
guard let buffer = renderedFrame!.buffer else{return nil};
|
||||
return buffer;
|
||||
}else if let _ = view as? ARSKView {
|
||||
guard let size = bufferSize else{return nil};
|
||||
var renderedFrame:UIImage?
|
||||
guard let buffer = renderedFrame!.buffer else { return nil }
|
||||
return buffer
|
||||
} else if view is ARSKView {
|
||||
guard let size = bufferSize else { return nil }
|
||||
var renderedFrame: UIImage?
|
||||
pixelsQueue.sync {
|
||||
renderedFrame = renderEngine.snapshot(atTime: self.time, with: size, antialiasingMode: .none).rotate(by: 180);
|
||||
renderedFrame = renderEngine.snapshot(atTime: self.time, with: size, antialiasingMode: .none).rotate(by: 180)
|
||||
}
|
||||
if renderedFrame == nil {
|
||||
renderedFrame = renderEngine.snapshot(atTime: time, with: size, antialiasingMode: .none).rotate(by: 180)
|
||||
}
|
||||
guard let buffer = renderedFrame!.buffer else { return nil }
|
||||
return buffer;
|
||||
} else if view is SCNView {
|
||||
let size = UIScreen.main.bounds.size
|
||||
var renderedFrame: UIImage?
|
||||
pixelsQueue.sync {
|
||||
renderedFrame = renderEngine.snapshot(atTime: self.time, with: size, antialiasingMode: .none)
|
||||
}
|
||||
if let _ = renderedFrame {
|
||||
}else{
|
||||
renderedFrame = renderEngine.snapshot(atTime: time, with: size, antialiasingMode: .none).rotate(by: 180);
|
||||
} else {
|
||||
renderedFrame = renderEngine.snapshot(atTime: time, with: size, antialiasingMode: .none)
|
||||
}
|
||||
guard let buffer = renderedFrame!.buffer else{return nil};
|
||||
return buffer;
|
||||
guard let buffer = renderedFrame!.buffer else { return nil }
|
||||
return buffer
|
||||
}
|
||||
return nil;
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,37 +9,38 @@
|
||||
import AVFoundation
|
||||
import CoreImage
|
||||
import UIKit
|
||||
|
||||
@available(iOS 11.0, *)
|
||||
internal class WritAR:NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
|
||||
fileprivate var assetWriter: AVAssetWriter!
|
||||
fileprivate var videoInput: AVAssetWriterInput!
|
||||
fileprivate var audioInput: AVAssetWriterInput!
|
||||
fileprivate var session: AVCaptureSession!
|
||||
class WritAR: NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
|
||||
private var assetWriter: AVAssetWriter!
|
||||
private var videoInput: AVAssetWriterInput!
|
||||
private var audioInput: AVAssetWriterInput!
|
||||
private var session: AVCaptureSession!
|
||||
|
||||
fileprivate var pixelBufferInput: AVAssetWriterInputPixelBufferAdaptor!
|
||||
fileprivate var videoOutputSettings: Dictionary<String, AnyObject>!
|
||||
fileprivate var audioSettings: [String : Any]?
|
||||
private var pixelBufferInput: AVAssetWriterInputPixelBufferAdaptor!
|
||||
private var videoOutputSettings: Dictionary<String, AnyObject>!
|
||||
private var audioSettings: [String: Any]?
|
||||
|
||||
internal let audioBufferQueue = DispatchQueue(label: "com.ahmedbekhit.AudioBufferQueue")
|
||||
let audioBufferQueue = DispatchQueue(label: "com.ahmedbekhit.AudioBufferQueue")
|
||||
|
||||
fileprivate var isRecording:Bool = false
|
||||
private var isRecording: Bool = false
|
||||
|
||||
internal var delegate:RecordARDelegate?
|
||||
internal var videoInputOrientation:ARVideoOrientation = .auto
|
||||
weak var delegate: RecordARDelegate?
|
||||
var videoInputOrientation: ARVideoOrientation = .auto
|
||||
|
||||
init(output:URL, width:Int, height:Int, adjustForSharing:Bool, audioEnabled:Bool, orientaions:[ARInputViewOrientation], queue:DispatchQueue, allowMix:Bool){
|
||||
init(output: URL, width: Int, height: Int, adjustForSharing: Bool, audioEnabled: Bool, orientaions:[ARInputViewOrientation], queue: DispatchQueue, allowMix: Bool) {
|
||||
super.init()
|
||||
do {
|
||||
assetWriter = try AVAssetWriter(outputURL: output, fileType: AVFileType.mp4)
|
||||
}catch{
|
||||
} catch {
|
||||
// FIXME: handle when failed to allocate AVAssetWriter.
|
||||
return
|
||||
}
|
||||
if audioEnabled {
|
||||
if allowMix {
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: [AVAudioSessionCategoryOptions.mixWithOthers , AVAudioSessionCategoryOptions.allowBluetooth, AVAudioSessionCategoryOptions.defaultToSpeaker, AVAudioSessionCategoryOptions.interruptSpokenAudioAndMixWithOthers])
|
||||
try AVAudioSession.sharedInstance().setActive(true)
|
||||
}catch{}
|
||||
let audioOptions: AVAudioSession.CategoryOptions = [.mixWithOthers , .allowBluetooth, .defaultToSpeaker, .interruptSpokenAudioAndMixWithOthers]
|
||||
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, mode: AVAudioSession.Mode.spokenAudio, options: audioOptions)
|
||||
try? AVAudioSession.sharedInstance().setActive(true)
|
||||
}
|
||||
AVAudioSession.sharedInstance().requestRecordPermission({ permitted in
|
||||
if permitted {
|
||||
@@ -51,19 +52,19 @@ internal class WritAR:NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
|
||||
//HEVC file format only supports A10 Fusion Chip or higher.
|
||||
//to support HEVC, make sure to check if the device is iPhone 7 or higher
|
||||
videoOutputSettings = [
|
||||
AVVideoCodecKey : AVVideoCodecType.h264 as AnyObject,
|
||||
AVVideoWidthKey : width as AnyObject,
|
||||
AVVideoHeightKey : height as AnyObject
|
||||
AVVideoCodecKey: AVVideoCodecType.h264 as AnyObject,
|
||||
AVVideoWidthKey: width as AnyObject,
|
||||
AVVideoHeightKey: height as AnyObject
|
||||
]
|
||||
|
||||
let attributes : [String:Bool] = [
|
||||
kCVPixelBufferCGImageCompatibilityKey as String : true,
|
||||
kCVPixelBufferCGBitmapContextCompatibilityKey as String : true
|
||||
let attributes: [String: Bool] = [
|
||||
kCVPixelBufferCGImageCompatibilityKey as String: true,
|
||||
kCVPixelBufferCGBitmapContextCompatibilityKey as String: true
|
||||
]
|
||||
videoInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoOutputSettings)
|
||||
|
||||
videoInput.expectsMediaDataInRealTime = true
|
||||
pixelBufferInput = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoInput, sourcePixelBufferAttributes: attributes)
|
||||
pixelBufferInput = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoInput, sourcePixelBufferAttributes: nil)
|
||||
|
||||
var angleEnabled: Bool {
|
||||
for v in orientaions {
|
||||
@@ -74,8 +75,8 @@ internal class WritAR:NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
|
||||
return false
|
||||
}
|
||||
|
||||
var recentAngle:CGFloat = 0
|
||||
var rotationAngle:CGFloat = 0
|
||||
var recentAngle: CGFloat = 0
|
||||
var rotationAngle: CGFloat = 0
|
||||
switch UIDevice.current.orientation {
|
||||
case .landscapeLeft:
|
||||
rotationAngle = -90
|
||||
@@ -98,14 +99,14 @@ internal class WritAR:NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
|
||||
|
||||
switch videoInputOrientation {
|
||||
case .auto:
|
||||
t = t.rotated(by:((rotationAngle*CGFloat.pi)/180))
|
||||
t = t.rotated(by: ((rotationAngle*CGFloat.pi) / 180))
|
||||
case .alwaysPortrait:
|
||||
t = t.rotated(by:0)
|
||||
t = t.rotated(by: 0)
|
||||
case .alwaysLandscape:
|
||||
if rotationAngle == 90 || rotationAngle == -90 {
|
||||
t = t.rotated(by:((rotationAngle*CGFloat.pi)/180))
|
||||
}else{
|
||||
t = t.rotated(by:((-90*CGFloat.pi)/180))
|
||||
t = t.rotated(by: ((rotationAngle * CGFloat.pi) / 180))
|
||||
} else {
|
||||
t = t.rotated(by: ((-90 * CGFloat.pi) / 180))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,19 +114,19 @@ internal class WritAR:NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
|
||||
|
||||
if assetWriter.canAdd(videoInput) {
|
||||
assetWriter.add(videoInput)
|
||||
}else{
|
||||
} else {
|
||||
delegate?.recorder(didFailRecording: assetWriter.error, and: "An error occurred while adding video input.")
|
||||
isWritingWithoutError = false
|
||||
}
|
||||
assetWriter.shouldOptimizeForNetworkUse = adjustForSharing
|
||||
}
|
||||
|
||||
internal func prepareAudioDevice(with queue:DispatchQueue) {
|
||||
func prepareAudioDevice(with queue: DispatchQueue) {
|
||||
let device: AVCaptureDevice = AVCaptureDevice.default(for: .audio)!
|
||||
var audioDeviceInput:AVCaptureDeviceInput?
|
||||
var audioDeviceInput: AVCaptureDeviceInput?
|
||||
do {
|
||||
audioDeviceInput = try AVCaptureDeviceInput(device: device)
|
||||
}catch{
|
||||
} catch {
|
||||
audioDeviceInput = nil
|
||||
}
|
||||
|
||||
@@ -145,13 +146,13 @@ internal class WritAR:NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
|
||||
}
|
||||
|
||||
|
||||
audioSettings = audioDataOutput.recommendedAudioSettingsForAssetWriter(writingTo: .m4v) as? [String : Any]
|
||||
audioSettings = audioDataOutput.recommendedAudioSettingsForAssetWriter(writingTo: .m4v) as? [String: Any]
|
||||
|
||||
audioInput = AVAssetWriterInput(mediaType: .audio, outputSettings: audioSettings)
|
||||
audioInput.expectsMediaDataInRealTime = true
|
||||
|
||||
audioBufferQueue.async {
|
||||
self.session.startRunning()
|
||||
self.session?.startRunning()
|
||||
}
|
||||
|
||||
if assetWriter.canAdd(audioInput) {
|
||||
@@ -159,49 +160,37 @@ internal class WritAR:NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
internal var startingVideoTime:CMTime?
|
||||
internal var isWritingWithoutError: Bool?
|
||||
var startingVideoTime: CMTime?
|
||||
var isWritingWithoutError: Bool?
|
||||
var currentDuration: TimeInterval = 0 // Seconds
|
||||
|
||||
internal func insert(pixel buffer:CVPixelBuffer, with intervals:CFTimeInterval) {
|
||||
var time:CMTime {return CMTimeMakeWithSeconds(intervals, 1000000);}
|
||||
if assetWriter.status == .unknown {
|
||||
if let _ = startingVideoTime {isWritingWithoutError = false; return}else{startingVideoTime = time}
|
||||
if assetWriter.startWriting() {
|
||||
assetWriter.startSession(atSourceTime: startingVideoTime!)
|
||||
session.startRunning()
|
||||
isRecording = true
|
||||
isWritingWithoutError = true
|
||||
}else{
|
||||
delegate?.recorder(didFailRecording: assetWriter.error, and: "An error occurred while starting the video session.")
|
||||
isWritingWithoutError = false
|
||||
}
|
||||
}else if assetWriter.status == .failed {
|
||||
delegate?.recorder(didFailRecording: assetWriter.error, and: "Video session failed while recording.")
|
||||
logAR.message("An error occurred while recording the video, status: \(assetWriter.status.rawValue), error: \(assetWriter.error!.localizedDescription)")
|
||||
isWritingWithoutError = false
|
||||
return
|
||||
}
|
||||
if videoInput.isReadyForMoreMediaData {
|
||||
append(pixel: buffer, with: time)
|
||||
isWritingWithoutError = true
|
||||
}
|
||||
func insert(pixel buffer: CVPixelBuffer, with intervals: CFTimeInterval) {
|
||||
let time: CMTime = CMTime(seconds: intervals, preferredTimescale: 1000000)
|
||||
insert(pixel: buffer, with: time)
|
||||
}
|
||||
|
||||
internal func insert(pixel buffer:CVPixelBuffer, with time:CMTime) {
|
||||
func insert(pixel buffer: CVPixelBuffer, with time: CMTime) {
|
||||
if assetWriter.status == .unknown {
|
||||
if let _ = startingVideoTime {isWritingWithoutError = false; return}else{startingVideoTime = time}
|
||||
guard startingVideoTime == nil else {
|
||||
isWritingWithoutError = false
|
||||
return
|
||||
}
|
||||
startingVideoTime = time
|
||||
if assetWriter.startWriting() {
|
||||
assetWriter.startSession(atSourceTime: startingVideoTime!)
|
||||
currentDuration = 0
|
||||
isRecording = true
|
||||
isWritingWithoutError = true
|
||||
}else{
|
||||
} else {
|
||||
delegate?.recorder(didFailRecording: assetWriter.error, and: "An error occurred while starting the video session.")
|
||||
currentDuration = 0
|
||||
isRecording = false
|
||||
isWritingWithoutError = false
|
||||
}
|
||||
}else if assetWriter.status == .failed {
|
||||
} else if assetWriter.status == .failed {
|
||||
delegate?.recorder(didFailRecording: assetWriter.error, and: "Video session failed while recording.")
|
||||
logAR.message("An error occurred while recording the video, status: \(assetWriter.status.rawValue), error: \(assetWriter.error!.localizedDescription)")
|
||||
currentDuration = 0
|
||||
isRecording = false
|
||||
isWritingWithoutError = false
|
||||
return
|
||||
@@ -209,15 +198,20 @@ internal class WritAR:NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
|
||||
|
||||
if videoInput.isReadyForMoreMediaData {
|
||||
append(pixel: buffer, with: time)
|
||||
currentDuration = time.seconds - startingVideoTime!.seconds
|
||||
isRecording = true
|
||||
isWritingWithoutError = true
|
||||
delegate?.recorder?(didUpdateRecording: currentDuration)
|
||||
}
|
||||
}
|
||||
|
||||
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
|
||||
if let input = audioInput {
|
||||
audioBufferQueue.async { [weak self] in
|
||||
if input.isReadyForMoreMediaData && (self?.isRecording)! {
|
||||
if let isRecording = self?.isRecording,
|
||||
let session = self?.session,
|
||||
input.isReadyForMoreMediaData && isRecording
|
||||
&& session.isRunning {
|
||||
input.append(sampleBuffer)
|
||||
}
|
||||
}
|
||||
@@ -227,30 +221,47 @@ internal class WritAR:NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
|
||||
func pause() {
|
||||
isRecording = false
|
||||
}
|
||||
func end(writing finished: @escaping () -> Void){
|
||||
|
||||
func end(writing finished: @escaping () -> Void) {
|
||||
if let session = session {
|
||||
if session.isRunning {
|
||||
session.stopRunning()
|
||||
}
|
||||
}
|
||||
assetWriter.finishWriting(completionHandler: finished)
|
||||
|
||||
if assetWriter.status == .writing {
|
||||
isRecording = false
|
||||
assetWriter.finishWriting(completionHandler: finished)
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
if let session = session {
|
||||
if session.isRunning {
|
||||
session.stopRunning()
|
||||
}
|
||||
}
|
||||
isRecording = false
|
||||
assetWriter.cancelWriting()
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 11.0, *)
|
||||
fileprivate extension WritAR {
|
||||
func append(pixel buffer:CVPixelBuffer, with time: CMTime) {
|
||||
private extension WritAR {
|
||||
func append(pixel buffer: CVPixelBuffer, with time: CMTime) {
|
||||
pixelBufferInput.append(buffer, withPresentationTime: time)
|
||||
}
|
||||
}
|
||||
|
||||
//Simple Logging to show logs only while debugging.
|
||||
internal class logAR{
|
||||
internal class func message(_ message: String) {
|
||||
class logAR {
|
||||
class func message(_ message: String) {
|
||||
#if DEBUG
|
||||
print("ARVideoKit @ \(Date().timeIntervalSince1970):- \(message)")
|
||||
#endif
|
||||
}
|
||||
internal class func remove(from path: URL?) {
|
||||
|
||||
class func remove(from path: URL?) {
|
||||
if let file = path?.path {
|
||||
let manager = FileManager.default
|
||||
if manager.fileExists(atPath: file) {
|
||||
|
||||
@@ -9,9 +9,6 @@
|
||||
import UIKit
|
||||
import ARKit
|
||||
|
||||
fileprivate var parentVC:UIViewController?
|
||||
fileprivate var recentAngle = 0
|
||||
|
||||
/**
|
||||
A class that configures the Augmented Reality View orientations.
|
||||
|
||||
@@ -23,24 +20,27 @@ fileprivate var recentAngle = 0
|
||||
*/
|
||||
@available(iOS 11.0, *)
|
||||
@objc public class ARView: NSObject {
|
||||
fileprivate var ivo:[ARInputViewOrientation] = []
|
||||
private weak var parentVC: UIViewController?
|
||||
private var recentAngle = 0
|
||||
private var inputViewOrientation:[ARInputViewOrientation] = []
|
||||
|
||||
/// An array of `ARInputViewOrientation` objects that allow customizing the accepted orientations in a `UIViewController` that contains Augmented Reality scenes.
|
||||
public var inputViewOrientations:[ARInputViewOrientation] {
|
||||
public var inputViewOrientations: [ARInputViewOrientation] {
|
||||
get{
|
||||
return ivo
|
||||
return inputViewOrientation
|
||||
}
|
||||
set{
|
||||
if newValue.count == 0 {
|
||||
ivo = [.portrait]
|
||||
}else{
|
||||
ivo = newValue
|
||||
inputViewOrientation = [.portrait]
|
||||
} else {
|
||||
inputViewOrientation = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var ivom:ARInputViewOrientationMode = .auto
|
||||
private var ivom: ARInputViewOrientationMode = .auto
|
||||
/// An object that allow customizing which subviews will rotate in a `UIViewController` that contains Augmented Reality scenes.
|
||||
public var inputViewOrientationMode:ARInputViewOrientationMode {
|
||||
public var inputViewOrientationMode: ARInputViewOrientationMode {
|
||||
get{
|
||||
return ivom
|
||||
}
|
||||
@@ -50,9 +50,9 @@ fileprivate var recentAngle = 0
|
||||
}
|
||||
|
||||
|
||||
@objc init?(ARSceneKit:ARSCNView) {
|
||||
@objc init?(ARSceneKit: ARSCNView) {
|
||||
super.init()
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(deviceDidRotate), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(deviceDidRotate), name: UIDevice.orientationDidChangeNotification, object: nil)
|
||||
|
||||
let value = UIInterfaceOrientation.portrait.rawValue
|
||||
UIDevice.current.setValue(value, forKey: "orientation")
|
||||
@@ -66,9 +66,9 @@ fileprivate var recentAngle = 0
|
||||
parentVC = vc
|
||||
}
|
||||
|
||||
@objc init?(ARSpriteKit:ARSKView) {
|
||||
@objc init?(ARSpriteKit: ARSKView) {
|
||||
super.init()
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(deviceDidRotate), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(deviceDidRotate), name: UIDevice.orientationDidChangeNotification, object: nil)
|
||||
|
||||
let value = UIInterfaceOrientation.portrait.rawValue
|
||||
UIDevice.current.setValue(value, forKey: "orientation")
|
||||
@@ -80,7 +80,23 @@ fileprivate var recentAngle = 0
|
||||
parentVC = vc
|
||||
}
|
||||
|
||||
@objc fileprivate func deviceDidRotate() {
|
||||
@objc init?(SceneKit: SCNView) {
|
||||
super.init()
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(deviceDidRotate), name: UIDevice.orientationDidChangeNotification, object: nil)
|
||||
|
||||
let value = UIInterfaceOrientation.portrait.rawValue
|
||||
UIDevice.current.setValue(value, forKey: "orientation")
|
||||
|
||||
ViewAR.orientation = .portrait
|
||||
|
||||
guard let vc = SceneKit.parent else {
|
||||
return
|
||||
}
|
||||
|
||||
parentVC = vc
|
||||
}
|
||||
|
||||
@objc private func deviceDidRotate() {
|
||||
guard var views = parentVC?.view.subviews else {
|
||||
return
|
||||
}
|
||||
@@ -89,11 +105,11 @@ fileprivate var recentAngle = 0
|
||||
|
||||
switch inputViewOrientationMode {
|
||||
case .auto:
|
||||
views = views.filter{!$0.isARView && $0.isButton}
|
||||
views = views.filter { !$0.isARView && $0.isButton }
|
||||
case .all:
|
||||
views = views.filter{!$0.isARView}
|
||||
views = views.filter { !$0.isARView }
|
||||
case .manual(let subviews):
|
||||
views = subviews.filter{!$0.isARView}
|
||||
views = subviews.filter { !$0.isARView }
|
||||
case .disabled:
|
||||
views = []
|
||||
}
|
||||
|
||||
+314
-221
@@ -12,13 +12,6 @@ import ARKit
|
||||
import Photos
|
||||
import PhotosUI
|
||||
|
||||
fileprivate var view:Any?
|
||||
fileprivate var renderEngine:SCNRenderer!
|
||||
fileprivate var gpuLoop:CADisplayLink!
|
||||
fileprivate var isResting = false
|
||||
fileprivate var ARcontentMode:ARFrameMode!
|
||||
@available(iOS 11.0, *)
|
||||
fileprivate var renderer:RenderAR!
|
||||
/**
|
||||
This class renders the `ARSCNView` or `ARSKView` content with the device's camera stream to generate a video 📹, photo 🌄, live photo 🎇 or GIF 🎆.
|
||||
|
||||
@@ -34,59 +27,103 @@ fileprivate var renderer:RenderAR!
|
||||
/**
|
||||
An object that passes the AR recorder errors and status in the protocol methods.
|
||||
*/
|
||||
@objc public var delegate:RecordARDelegate?
|
||||
@objc weak public var delegate: RecordARDelegate?
|
||||
/**
|
||||
An object that passes the AR rendered content in the protocol method.
|
||||
*/
|
||||
@objc public var renderAR: RenderARDelegate?
|
||||
@objc weak public var renderAR: RenderARDelegate?
|
||||
/**
|
||||
An object that returns the AR recorder current status.
|
||||
*/
|
||||
@objc public internal(set)var status:RecordARStatus = .unknown
|
||||
@objc public internal(set)var status: RecordARStatus = .unknown
|
||||
/**
|
||||
An object that returns the current Microphone status.
|
||||
*/
|
||||
@objc public internal(set)var micStatus:RecordARMicrophoneStatus = .unknown
|
||||
@objc public internal(set)var micStatus: RecordARMicrophoneStatus = .unknown
|
||||
/**
|
||||
An object that allow customizing when to ask for Microphone permission, if needed. Default is `.auto`.
|
||||
An object that allow customizing when to ask for Microphone permission, if needed. Default is `.manual`.
|
||||
*/
|
||||
@objc public var requestMicPermission:RecordARMicrophonePermission = .auto
|
||||
@objc public var requestMicPermission: RecordARMicrophonePermission = .manual {
|
||||
didSet {
|
||||
switch self.requestMicPermission {
|
||||
case .auto:
|
||||
if self.enableAudio {
|
||||
self.requestMicrophonePermission()
|
||||
}
|
||||
case .manual:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
An object that allow customizing the video frame per second rate. Default is `.auto`.
|
||||
*/
|
||||
@objc public var fps:ARVideoFrameRate = .auto
|
||||
@objc public var fps: ARVideoFrameRate = .auto
|
||||
/**
|
||||
An object that allow customizing the video orientation. Default is `.auto`.
|
||||
*/
|
||||
@objc public var videoOrientation:ARVideoOrientation = .auto
|
||||
@objc public var videoOrientation: ARVideoOrientation = .auto
|
||||
/**
|
||||
An object that allow customizing the AR content mode. Default is `.auto`.
|
||||
*/
|
||||
@objc public var contentMode:ARFrameMode = .auto
|
||||
@objc public var contentMode: ARFrameMode = .auto
|
||||
/**
|
||||
A boolean that enables or disables AR content rendering before recording for image & video processing. Default is `true`.
|
||||
*/
|
||||
@objc public var onlyRenderWhileRecording:Bool = true
|
||||
@objc public var onlyRenderWhileRecording: Bool = true {
|
||||
didSet {
|
||||
self.onlyRenderWhileRec = self.onlyRenderWhileRecording
|
||||
}
|
||||
}
|
||||
/**
|
||||
A boolean that enables or disables audio recording. Default is `true`.
|
||||
*/
|
||||
@objc public var enableAudio:Bool = true
|
||||
@objc public var enableAudio: Bool = true {
|
||||
didSet {
|
||||
self.requestMicPermission = (self.requestMicPermission == .manual) ? .manual: .auto
|
||||
}
|
||||
}
|
||||
/**
|
||||
A boolean that enables or disables audio `mixWithOthers` if audio recording is enabled. This allows playing music and recording audio at the same time. Default is `true`.
|
||||
*/
|
||||
@objc public var enableMixWithOthers:Bool = true
|
||||
@objc public var enableMixWithOthers: Bool = true
|
||||
/**
|
||||
A boolean that enables or disables adjusting captured media for sharing online. Default is `true`.
|
||||
*/
|
||||
@objc public var adjustVideoForSharing:Bool = true
|
||||
@objc public var adjustVideoForSharing: Bool = true
|
||||
/**
|
||||
A boolean that enables or disables adjusting captured GIFs for sharing online. Default is `true`.
|
||||
*/
|
||||
@objc public var adjustGIFForSharing:Bool = true
|
||||
@objc public var adjustGIFForSharing: Bool = true
|
||||
/**
|
||||
A boolean that enables or disables clearing cached media after exporting to Camera Roll. Default is `true`.
|
||||
*/
|
||||
@objc public var deleteCacheWhenExported:Bool = true
|
||||
@objc public var deleteCacheWhenExported: Bool = true
|
||||
/**
|
||||
A boolean that enables or disables using envronment light rendering. Default is `false`.
|
||||
*/
|
||||
@objc public var enableAdjustEnvironmentLighting: Bool = false {
|
||||
didSet{
|
||||
if (renderEngine != nil) {
|
||||
renderEngine.autoenablesDefaultLighting = enableAdjustEnvironmentLighting
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
A boolean that indicates whether render engine should retain SCNTechnique used in the view. Default is `false`.
|
||||
*/
|
||||
@objc public var retainTechnique: Bool = false {
|
||||
didSet {
|
||||
if retainTechnique {
|
||||
guard let techniqueSupportingView = view as? SCNTechniqueSupport else {
|
||||
return
|
||||
}
|
||||
renderEngine?.technique = techniqueSupportingView.technique
|
||||
} else {
|
||||
renderEngine?.technique = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Public initialization methods
|
||||
/**
|
||||
@@ -106,56 +143,72 @@ fileprivate var renderer:RenderAR!
|
||||
view = ARSpriteKit
|
||||
scnView = SCNView(frame: UIScreen.main.bounds)
|
||||
|
||||
let bundle = Bundle(for: RecordAR.self)
|
||||
let url = bundle.url(forResource: "video.scnassets/vid", withExtension: "scn")
|
||||
|
||||
do {
|
||||
let scene = try SCNScene(url: url!, options: nil)
|
||||
scnView.scene = scene
|
||||
setup()
|
||||
}catch let error {
|
||||
logAR.message("Error occurred while loading SK Video Assets : \(error). Please download \"video.scnassets\" from\nwww.ahmedbekhit.com/ARVideoKitAssets")
|
||||
}
|
||||
let scene = SCNScene()
|
||||
scnView.scene = scene
|
||||
setup()
|
||||
}
|
||||
//MARK: - Internal threads
|
||||
internal let writerQueue = DispatchQueue(label:"com.ahmedbekhit.WriterQueue")
|
||||
internal let gifWriterQueue = DispatchQueue(label: "com.ahmedbekhit.GIFWriterQueue", attributes: .concurrent)
|
||||
internal let audioSessionQueue = DispatchQueue(label: "com.ahmedbekhit.AudioSessionQueue", attributes: .concurrent)
|
||||
|
||||
//MARK: - Internal Objects
|
||||
fileprivate var scnView:SCNView!
|
||||
fileprivate var fileCount = 0
|
||||
/**
|
||||
Initialize 🌞🍳 `RecordAR` with an `SCNView` 🚀.
|
||||
*/
|
||||
@objc override public init?(SceneKit: SCNView) {
|
||||
super.init(SceneKit: SceneKit)
|
||||
view = SceneKit
|
||||
setup()
|
||||
}
|
||||
|
||||
//MARK: - Deinit
|
||||
deinit {
|
||||
gpuLoop.invalidate()
|
||||
}
|
||||
|
||||
internal var parent:UIViewController? {
|
||||
//MARK: - threads
|
||||
let writerQueue = DispatchQueue(label:"com.ahmedbekhit.WriterQueue")
|
||||
let gifWriterQueue = DispatchQueue(label: "com.ahmedbekhit.GIFWriterQueue", attributes: .concurrent)
|
||||
let audioSessionQueue = DispatchQueue(label: "com.ahmedbekhit.AudioSessionQueue", attributes: .concurrent)
|
||||
|
||||
//MARK: - Objects
|
||||
private var view: Any?
|
||||
private var renderEngine: SCNRenderer!
|
||||
private var gpuLoop: CADisplayLink!
|
||||
private var isResting = false
|
||||
private var renderer: RenderAR!
|
||||
|
||||
private var scnView: SCNView!
|
||||
private var fileCount = 0
|
||||
|
||||
var parent: UIViewController? {
|
||||
if let view = view as? ARSCNView {
|
||||
return view.parent!
|
||||
}else if let view = view as? ARSKView {
|
||||
} else if let view = view as? ARSKView {
|
||||
return view.parent!
|
||||
} else if let view = view as? SCNView {
|
||||
return view.parent!
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//Used for gif capturing
|
||||
internal var gifImages:[UIImage] = []
|
||||
var gifImages:[UIImage] = []
|
||||
//Used for checking current recorder status
|
||||
internal var isCapturingPhoto = false
|
||||
internal var isRecordingGIF = false
|
||||
internal var isRecording = false
|
||||
internal var adjustPausedTime = false
|
||||
internal var backFromPause = false
|
||||
internal var recordingWithLimit = false
|
||||
internal var onlyRenderWhileRec = false
|
||||
var isCapturingPhoto = false
|
||||
var isRecordingGIF = false
|
||||
var isRecording = false
|
||||
var adjustPausedTime = false
|
||||
var backFromPause = false
|
||||
var recordingWithLimit = false
|
||||
var onlyRenderWhileRec = true
|
||||
//Used to modify video time when paused
|
||||
internal var pausedFrameTime:CMTime?
|
||||
internal var resumeFrameTime:CMTime?
|
||||
var pausedFrameTime: CMTime?
|
||||
var resumeFrameTime: CMTime?
|
||||
//Used to locate the path of the video recording
|
||||
internal var currentVideoPath:URL?
|
||||
var currentVideoPath: URL?
|
||||
//Used to locate the path of the audio recording
|
||||
internal var currentAudioPath:URL?
|
||||
var currentAudioPath: URL?
|
||||
//Used to initialize the video writer
|
||||
internal var writer:WritAR?
|
||||
var writer: WritAR?
|
||||
//Used to generate a new video path
|
||||
internal var newVideoPath:URL {
|
||||
var newVideoPath: URL {
|
||||
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
|
||||
let documentsDirectory = paths[0]
|
||||
|
||||
@@ -169,20 +222,28 @@ fileprivate var renderer:RenderAR!
|
||||
let vidPath = "\(documentsDirectory)/\(formatter.string(from: date))ARVideo.mp4"
|
||||
return URL(fileURLWithPath: vidPath, isDirectory: false)
|
||||
}
|
||||
//MARK: - Internal Video Setup
|
||||
internal func setup() {
|
||||
|
||||
//MARK: - Video Setup
|
||||
func setup() {
|
||||
if let view = view as? ARSCNView {
|
||||
guard let mtlDevice = MTLCreateSystemDefaultDevice() else {logAR.message("ERROR:- This device does not support Metal");return}
|
||||
guard let mtlDevice = MTLCreateSystemDefaultDevice() else {
|
||||
logAR.message("ERROR:- This device does not support Metal")
|
||||
return
|
||||
}
|
||||
renderEngine = SCNRenderer(device: mtlDevice, options: nil)
|
||||
renderEngine.scene = view.scene
|
||||
|
||||
gpuLoop = CADisplayLink(target: self, selector: #selector(renderFrame))
|
||||
|
||||
gpuLoop = CADisplayLink(target: WeakProxy(target: self),
|
||||
selector: #selector(renderFrame))
|
||||
gpuLoop.preferredFramesPerSecond = fps.rawValue
|
||||
gpuLoop.add(to: .main, forMode: .commonModes)
|
||||
gpuLoop.add(to: .main, forMode: .common)
|
||||
|
||||
status = .readyToRecord
|
||||
}else if let view = view as? ARSKView {
|
||||
guard let mtlDevice = MTLCreateSystemDefaultDevice() else {logAR.message("ERROR:- This device does not support Metal");return}
|
||||
} else if let view = view as? ARSKView {
|
||||
guard let mtlDevice = MTLCreateSystemDefaultDevice() else {
|
||||
logAR.message("ERROR:- This device does not support Metal")
|
||||
return
|
||||
}
|
||||
let material = SCNMaterial()
|
||||
material.diffuse.contents = view.scene
|
||||
|
||||
@@ -195,33 +256,37 @@ fileprivate var renderer:RenderAR!
|
||||
|
||||
renderEngine = SCNRenderer(device: mtlDevice, options: nil)
|
||||
renderEngine.scene = scnView.scene
|
||||
|
||||
gpuLoop = CADisplayLink(target: self, selector: #selector(renderFrame))
|
||||
|
||||
gpuLoop = CADisplayLink(target: WeakProxy(target: self),
|
||||
selector: #selector(renderFrame))
|
||||
gpuLoop.preferredFramesPerSecond = fps.rawValue
|
||||
gpuLoop.add(to: .main, forMode: .commonModes)
|
||||
gpuLoop.add(to: .main, forMode: .common)
|
||||
|
||||
status = .readyToRecord
|
||||
}
|
||||
} else if let view = view as? SCNView {
|
||||
guard let mtlDevice = MTLCreateSystemDefaultDevice() else {
|
||||
logAR.message("ERROR:- This device does not support Metal")
|
||||
return
|
||||
}
|
||||
renderEngine = SCNRenderer(device: mtlDevice, options: nil)
|
||||
renderEngine.scene = view.scene
|
||||
|
||||
switch requestMicPermission {
|
||||
case .auto:
|
||||
AVAudioSession.sharedInstance().requestRecordPermission({ permitted in
|
||||
if permitted {
|
||||
self.micStatus = .enabled
|
||||
}else{
|
||||
self.micStatus = .disabled
|
||||
}
|
||||
})
|
||||
default:
|
||||
break
|
||||
gpuLoop = CADisplayLink(target: WeakProxy(target: self),
|
||||
selector: #selector(renderFrame))
|
||||
gpuLoop.preferredFramesPerSecond = fps.rawValue
|
||||
gpuLoop.add(to: .main, forMode: .common)
|
||||
|
||||
status = .readyToRecord
|
||||
}
|
||||
|
||||
onlyRenderWhileRec = onlyRenderWhileRecording
|
||||
|
||||
renderer = RenderAR(view, renderer: renderEngine, contentMode: contentMode)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterBackground), name: Notification.Name.UIApplicationWillResignActive, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterBackground), name: UIApplication.willResignActiveNotification, object: nil)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//MARK: - Public methods for capturing videos, photos, Live Photos, and GIFs
|
||||
@@ -236,7 +301,7 @@ fileprivate var renderer:RenderAR!
|
||||
/**
|
||||
A method that renders a `PHLivePhoto` 🎇 and returns `PHLivePhotoPlus` in the completion handler.
|
||||
|
||||
In order to manually export the `PHLivePhotoPlus`, use `export(live photo:PHLivePhotoPlus)` method.
|
||||
In order to manually export the `PHLivePhotoPlus`, use `export(live photo: PHLivePhotoPlus)` method.
|
||||
- parameter export: A boolean that enables or disables automatically exporting the `PHLivePhotoPlus` when ready.
|
||||
- parameter finished: A block that will be called when Live Photo rendering is complete.
|
||||
|
||||
@@ -254,9 +319,9 @@ fileprivate var renderer:RenderAR!
|
||||
`exported`
|
||||
A boolean that returns `true` when a `PHLivePhotoPlus` is successfully exported to the Photo Library. Otherwise, it returns `false`.
|
||||
*/
|
||||
@objc public func livePhoto(export:Bool, _ finished: ((_ status:Bool, _ livePhoto:PHLivePhotoPlus, _ permissionStatus:PHAuthorizationStatus, _ exported:Bool) -> Swift.Void)? = nil) {
|
||||
@objc public func livePhoto(export: Bool, _ finished: ((_ status: Bool, _ livePhoto: PHLivePhotoPlus, _ permissionStatus: PHAuthorizationStatus, _ exported: Bool) -> Swift.Void)? = nil) {
|
||||
self.record(forDuration: 3.0) { path in
|
||||
let generator:LivePhotoGenerator? = LivePhotoGenerator()
|
||||
let generator: LivePhotoGenerator? = LivePhotoGenerator()
|
||||
generator?.generate(livePhoto: path) { success, photo, frames, keyFrame in
|
||||
if success && export {
|
||||
if self.fileCount == 0 {
|
||||
@@ -265,7 +330,7 @@ fileprivate var renderer:RenderAR!
|
||||
finished?(true, photo!, status, done)
|
||||
}
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
finished?(success, photo!, PHAuthorizationStatus.notDetermined, false)
|
||||
}
|
||||
}
|
||||
@@ -274,7 +339,7 @@ fileprivate var renderer:RenderAR!
|
||||
/**
|
||||
A method that generates a GIF 🎆 image and returns its local path (`URL`) in the completion handler.
|
||||
|
||||
In order to manually export the GIF image `URL`, use `func export(image path:URL)` method.
|
||||
In order to manually export the GIF image `URL`, use `func export(image path: URL)` method.
|
||||
- parameter duration: A `TimeInterval` object that can be set to the duration specified in seconds.
|
||||
- parameter export: A boolean that enables or disables automatically exporting the GIF image `URL` when ready.
|
||||
- parameter finished: A block that will be called when GIF image rendering is complete.
|
||||
@@ -293,23 +358,24 @@ fileprivate var renderer:RenderAR!
|
||||
`exported`
|
||||
A boolean that returns `true` when a GIF image `URL` is successfully exported to the Photo Library. Otherwise, it returns `false`.
|
||||
*/
|
||||
@objc public func gif(forDuration duration:TimeInterval, export:Bool, _ finished: ((_ status:Bool, _ gifPath: URL, _ permissionStatus:PHAuthorizationStatus, _ exported:Bool) -> Swift.Void)? = nil) {
|
||||
@objc public func gif(forDuration duration: TimeInterval, export: Bool, _ finished: ((_ status: Bool, _ gifPath: URL, _ permissionStatus: PHAuthorizationStatus, _ exported: Bool) -> Swift.Void)? = nil) {
|
||||
writerQueue.sync {
|
||||
self.isRecordingGIF = true
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
|
||||
self.isRecordingGIF = false
|
||||
let generator:GIFGenerator? = GIFGenerator()
|
||||
generator?.generate(gif: self.gifImages, with: 0.1, loop: 0, adjust: self.adjustGIFForSharing) {ready, path in
|
||||
let generator: GIFGenerator? = GIFGenerator()
|
||||
generator?.generate(gif: self.gifImages, with: 0.1, loop: 0, adjust: self.adjustGIFForSharing) { ready, path in
|
||||
// FIXME: `path` may be nil
|
||||
if ready {
|
||||
self.gifImages.removeAll()
|
||||
if export {
|
||||
self.export(image: path!) { done, status in
|
||||
finished?(ready, path!, status, done)
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
finished?(ready, path!, .notDetermined, false)
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
self.gifImages.removeAll()
|
||||
finished?(ready, path!, .notDetermined, false)
|
||||
}
|
||||
@@ -320,8 +386,15 @@ fileprivate var renderer:RenderAR!
|
||||
///A method that starts or resumes ⏯ recording a video 📹.
|
||||
@objc public func record() {
|
||||
writerQueue.sync {
|
||||
self.isRecording = true
|
||||
self.status = .recording
|
||||
if self.enableAudio && micStatus == .unknown {
|
||||
self.requestMicrophonePermission { _ in
|
||||
self.isRecording = true
|
||||
self.status = .recording
|
||||
}
|
||||
} else {
|
||||
self.isRecording = true
|
||||
self.status = .recording
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
@@ -329,7 +402,7 @@ fileprivate var renderer:RenderAR!
|
||||
|
||||
In order to stop the recording before the specified duration, simply call `stop()` or `stopAndExport()` methods.
|
||||
|
||||
- WARNING : You CAN NOT `pause()` video recording when a duration is specified.
|
||||
- WARNING: You CAN NOT `pause()` video recording when a duration is specified.
|
||||
- parameter duration: A `TimeInterval` object that can be set to the duration specified in seconds.
|
||||
- parameter finished: A block that will be called when the specified `duration` has ended.
|
||||
|
||||
@@ -338,16 +411,30 @@ fileprivate var renderer:RenderAR!
|
||||
`videoPath`
|
||||
A `URL` object that contains the local file path of the video to allow manual exporting or preview of the video.
|
||||
*/
|
||||
@objc public func record(forDuration duration:TimeInterval, _ finished: ((_ videoPath: URL) -> Swift.Void)? = nil) {
|
||||
@objc public func record(forDuration duration: TimeInterval, _ finished: ((_ videoPath: URL) -> Swift.Void)? = nil) {
|
||||
writerQueue.sync {
|
||||
self.recordingWithLimit = true
|
||||
self.isRecording = true
|
||||
self.status = .recording
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
|
||||
self.stop { path in
|
||||
finished?(path)
|
||||
if self.enableAudio && micStatus == .unknown {
|
||||
self.requestMicrophonePermission { _ in
|
||||
self.recordingWithLimit = true
|
||||
self.isRecording = true
|
||||
self.status = .recording
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
|
||||
self.stop { path in
|
||||
finished?(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.recordingWithLimit = true
|
||||
self.isRecording = true
|
||||
self.status = .recording
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + duration) {
|
||||
self.stop { path in
|
||||
finished?(path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
/**
|
||||
@@ -360,8 +447,8 @@ fileprivate var renderer:RenderAR!
|
||||
onlyRenderWhileRec = false
|
||||
isRecording = false
|
||||
adjustPausedTime = true
|
||||
}else{
|
||||
logAR.message("NOT PERMITTED: The [ pause() ] method CAN NOT be used while using [ record(forDuration duration:TimeInterval) ]")
|
||||
} else {
|
||||
logAR.message("NOT PERMITTED: The [ pause() ] method CAN NOT be used while using [ record(forDuration duration: TimeInterval) ]")
|
||||
}
|
||||
}
|
||||
/**
|
||||
@@ -380,7 +467,7 @@ fileprivate var renderer:RenderAR!
|
||||
`exported`
|
||||
A boolean that returns `true` when a video is successfully exported to the Photo Library. Otherwise, it returns `false`.
|
||||
*/
|
||||
@objc public func stopAndExport(_ finished: ((_ videoPath: URL, _ permissionStatus:PHAuthorizationStatus, _ exported:Bool) -> Swift.Void)? = nil) {
|
||||
@objc public func stopAndExport(_ finished: ((_ videoPath: URL, _ permissionStatus: PHAuthorizationStatus, _ exported: Bool) -> Swift.Void)? = nil) {
|
||||
writerQueue.sync {
|
||||
self.isRecording = false
|
||||
self.adjustPausedTime = false
|
||||
@@ -397,7 +484,7 @@ fileprivate var renderer:RenderAR!
|
||||
}
|
||||
self.delegate?.recorder(didEndRecording: path, with: true)
|
||||
self.status = .readyToRecord
|
||||
}else{
|
||||
} else {
|
||||
finished?(self.currentVideoPath!, .notDetermined, false)
|
||||
self.status = .readyToRecord
|
||||
self.delegate?.recorder(didFailRecording: errSecDecode as? Error, and: "An error occured while stopping your video.")
|
||||
@@ -432,7 +519,7 @@ fileprivate var renderer:RenderAR!
|
||||
finished?(path)
|
||||
self.delegate?.recorder(didEndRecording: path, with: true)
|
||||
self.status = .readyToRecord
|
||||
}else{
|
||||
} else {
|
||||
self.status = .readyToRecord
|
||||
self.delegate?.recorder(didFailRecording: errSecDecode as? Error, and: "An error occured while stopping your video.")
|
||||
}
|
||||
@@ -441,6 +528,39 @@ fileprivate var renderer:RenderAR!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
A method that cancels ⏹ recording a video 📹.
|
||||
|
||||
- parameter finished: A block that will be called when the specified `duration` has ended.
|
||||
|
||||
*/
|
||||
@objc public func cancel() {
|
||||
writerQueue.sync {
|
||||
isRecording = false
|
||||
adjustPausedTime = false
|
||||
backFromPause = false
|
||||
recordingWithLimit = false
|
||||
|
||||
pausedFrameTime = nil
|
||||
resumeFrameTime = nil
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.writer?.cancel()
|
||||
if let path = self.currentVideoPath {
|
||||
logAR.remove(from: path)
|
||||
self.delegate?.recorder?(didCancelRecording: "Recording was cancelled manually.")
|
||||
self.status = .readyToRecord
|
||||
} else {
|
||||
self.status = .readyToRecord
|
||||
self.delegate?.recorder(didFailRecording: errSecDecode as? Error, and: "An error occured while stopping your video.")
|
||||
}
|
||||
self.writer = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
A method that exports a video 📹 file path to the Photo Library 📲💾.
|
||||
|
||||
@@ -456,35 +576,25 @@ fileprivate var renderer:RenderAR!
|
||||
`permissionStatus`
|
||||
A `PHAuthorizationStatus` object that returns the current application's status for exporting media to the Photo Library.
|
||||
*/
|
||||
@objc public func export(video path:URL, _ finished: ((_ exported:Bool, _ permissionStatus:PHAuthorizationStatus) -> Swift.Void)? = nil) {
|
||||
@objc public func export(video path: URL, _ finished: ((_ exported: Bool, _ permissionStatus: PHAuthorizationStatus) -> Void)? = nil) {
|
||||
audioSessionQueue.async {
|
||||
let photos = PHPhotoLibrary.authorizationStatus()
|
||||
if photos == .notDetermined {
|
||||
PHPhotoLibrary.requestAuthorization({ status in
|
||||
if status == .authorized {
|
||||
PHPhotoLibrary.shared().performChanges({
|
||||
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: path)
|
||||
}) { saved, _ in
|
||||
if saved && self.deleteCacheWhenExported {
|
||||
logAR.remove(from: path)
|
||||
}
|
||||
finished?(saved, status)
|
||||
}
|
||||
}else{
|
||||
finished?(false, status)
|
||||
}
|
||||
})
|
||||
}else if photos == .authorized {
|
||||
let status = PHPhotoLibrary.authorizationStatus()
|
||||
if status == .notDetermined {
|
||||
PHPhotoLibrary.requestAuthorization() { status in
|
||||
// Recursive call after authorization request
|
||||
self.export(video: path, finished)
|
||||
}
|
||||
} else if status == .authorized {
|
||||
PHPhotoLibrary.shared().performChanges({
|
||||
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: path)
|
||||
}) { saved, error in
|
||||
if saved && self.deleteCacheWhenExported {
|
||||
logAR.remove(from: path)
|
||||
}
|
||||
finished?(saved, photos)
|
||||
finished?(saved, status)
|
||||
}
|
||||
}else if photos == .denied || photos == .restricted {
|
||||
finished?(false, photos)
|
||||
} else if status == .denied || status == .restricted {
|
||||
finished?(false, status)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -503,34 +613,18 @@ fileprivate var renderer:RenderAR!
|
||||
`permissionStatus`
|
||||
A `PHAuthorizationStatus` object that returns the current application's status for exporting media to the Photo Library.
|
||||
*/
|
||||
@objc public func export(image path:URL?=nil, UIImage:UIImage?=nil, _ finished: ((_ exported:Bool, _ permissionStatus:PHAuthorizationStatus) -> Swift.Void)? = nil) {
|
||||
let photos = PHPhotoLibrary.authorizationStatus()
|
||||
if photos == .notDetermined {
|
||||
PHPhotoLibrary.requestAuthorization({ status in
|
||||
if status == .authorized {
|
||||
PHPhotoLibrary.shared().performChanges({
|
||||
if let path = path {
|
||||
PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: path)
|
||||
}else if let image = UIImage {
|
||||
PHAssetChangeRequest.creationRequestForAsset(from: image)
|
||||
}
|
||||
}) { saved, error in
|
||||
if saved && self.deleteCacheWhenExported {
|
||||
if let path = path {
|
||||
logAR.remove(from: path)
|
||||
}
|
||||
}
|
||||
finished?(saved, status)
|
||||
}
|
||||
}else{
|
||||
finished?(false, status)
|
||||
}
|
||||
})
|
||||
}else if photos == .authorized {
|
||||
@objc public func export(image path: URL? = nil, UIImage: UIImage? = nil, _ finished: ((_ exported: Bool, _ permissionStatus: PHAuthorizationStatus) -> Void)? = nil) {
|
||||
let status = PHPhotoLibrary.authorizationStatus()
|
||||
if status == .notDetermined {
|
||||
PHPhotoLibrary.requestAuthorization() { status in
|
||||
// Recursive call after authorization request
|
||||
self.export(image: path, UIImage: UIImage, finished)
|
||||
}
|
||||
} else if status == .authorized {
|
||||
PHPhotoLibrary.shared().performChanges({
|
||||
if let path = path {
|
||||
PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: path)
|
||||
}else if let image = UIImage {
|
||||
} else if let image = UIImage {
|
||||
PHAssetChangeRequest.creationRequestForAsset(from: image)
|
||||
}
|
||||
}) { saved, error in
|
||||
@@ -539,16 +633,16 @@ fileprivate var renderer:RenderAR!
|
||||
logAR.remove(from: path)
|
||||
}
|
||||
}
|
||||
finished?(saved, photos)
|
||||
finished?(saved, status)
|
||||
}
|
||||
}else if photos == .denied || photos == .restricted {
|
||||
finished?(false, photos)
|
||||
} else if status == .denied || status == .restricted {
|
||||
finished?(false, status)
|
||||
}
|
||||
}
|
||||
/**
|
||||
A method that exports a `PHLivePhotoPlus` 🎇 object to the Photo Library 📲💾.
|
||||
|
||||
- parameter photo: A `PHLivePhotoPlus` object that can be set to the returned `PHLivePhotoPlus` object in the `livePhoto(export:Bool, _ finished:{})` method.
|
||||
- parameter photo: A `PHLivePhotoPlus` object that can be set to the returned `PHLivePhotoPlus` object in the `livePhoto(export: Bool, _ finished:{})` method.
|
||||
|
||||
- parameter finished: A block that will be called when the export process is complete.
|
||||
|
||||
@@ -560,55 +654,42 @@ fileprivate var renderer:RenderAR!
|
||||
`permissionStatus`
|
||||
A `PHAuthorizationStatus` object that returns the current application's status for exporting media to the Photo Library.
|
||||
*/
|
||||
@objc public func export(live photo:PHLivePhotoPlus, _ finished: ((_ exported:Bool, _ permissionStatus:PHAuthorizationStatus) -> Swift.Void)? = nil) {
|
||||
guard let keyPhotoPath = photo.keyPhotoPath else{logAR.message("An error occurred while exporting a live photo"); return}
|
||||
guard let videoPath = photo.pairedVideoPath else{logAR.message("An error occurred while exporting a live photo"); return}
|
||||
@objc public func export(live photo: PHLivePhotoPlus, _ finished: ((_ exported: Bool, _ permissionStatus: PHAuthorizationStatus) -> Void)? = nil) {
|
||||
guard let keyPhotoPath = photo.keyPhotoPath else {
|
||||
logAR.message("An error occurred while exporting a live photo")
|
||||
return
|
||||
}
|
||||
guard let videoPath = photo.pairedVideoPath else {
|
||||
logAR.message("An error occurred while exporting a live photo")
|
||||
return
|
||||
}
|
||||
|
||||
let photos = PHPhotoLibrary.authorizationStatus()
|
||||
if photos == .notDetermined {
|
||||
PHPhotoLibrary.requestAuthorization({ status in
|
||||
if status == .authorized {
|
||||
PHPhotoLibrary.shared().performChanges({
|
||||
let request = PHAssetCreationRequest.forAsset()
|
||||
let options = PHAssetResourceCreationOptions()
|
||||
request.addResource(with: .photo, fileURL: keyPhotoPath, options: options)
|
||||
request.addResource(with: .pairedVideo, fileURL: videoPath, options: options)
|
||||
}, completionHandler: { saved, error in
|
||||
if saved {
|
||||
if self.deleteCacheWhenExported {
|
||||
logAR.remove(from: keyPhotoPath)
|
||||
logAR.remove(from: videoPath)
|
||||
}
|
||||
self.fileCount = 0
|
||||
}else{
|
||||
logAR.message("An error occurred while exporting a live photo: \(error!)")
|
||||
}
|
||||
finished?(saved, status)
|
||||
})
|
||||
}else{
|
||||
finished?(false, status)
|
||||
}
|
||||
})
|
||||
}else if photos == .authorized {
|
||||
let status = PHPhotoLibrary.authorizationStatus()
|
||||
if status == .notDetermined {
|
||||
PHPhotoLibrary.requestAuthorization() { status in
|
||||
// Recursive call after authorization request
|
||||
self.export(live: photo, finished)
|
||||
}
|
||||
} else if status == .authorized {
|
||||
PHPhotoLibrary.shared().performChanges({
|
||||
let request = PHAssetCreationRequest.forAsset()
|
||||
let options = PHAssetResourceCreationOptions()
|
||||
request.addResource(with: .photo, fileURL: keyPhotoPath, options: options)
|
||||
request.addResource(with: .pairedVideo, fileURL: videoPath, options: options)
|
||||
}, completionHandler: { saved, error in
|
||||
}) { saved, error in
|
||||
if saved {
|
||||
if self.deleteCacheWhenExported {
|
||||
logAR.remove(from: keyPhotoPath)
|
||||
logAR.remove(from: videoPath)
|
||||
}
|
||||
self.fileCount = 0
|
||||
}else{
|
||||
} else {
|
||||
logAR.message("An error occurred while exporting a live photo: \(error!)")
|
||||
}
|
||||
finished?(saved, photos)
|
||||
})
|
||||
}else if photos == .denied || photos == .restricted {
|
||||
finished?(false, photos)
|
||||
finished?(saved, status)
|
||||
}
|
||||
} else if status == .denied || status == .restricted {
|
||||
finished?(false, status)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,7 +707,7 @@ fileprivate var renderer:RenderAR!
|
||||
finished?(permitted)
|
||||
if permitted {
|
||||
self.micStatus = .enabled
|
||||
}else{
|
||||
} else {
|
||||
self.micStatus = .disabled
|
||||
}
|
||||
})
|
||||
@@ -642,48 +723,58 @@ fileprivate var renderer:RenderAR!
|
||||
Recommended to use in the `UIViewController`'s method `func viewWillAppear(_ animated: Bool)`
|
||||
- parameter configuration: An object that defines motion and scene tracking behaviors for the session.
|
||||
*/
|
||||
@objc public func prepare(_ configuration:ARConfiguration) {
|
||||
ARcontentMode = contentMode
|
||||
@objc func prepare(_ configuration: ARConfiguration? = nil) {
|
||||
renderer.ARcontentMode = contentMode
|
||||
onlyRenderWhileRec = onlyRenderWhileRecording
|
||||
if let view = view as? ARSCNView {
|
||||
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
|
||||
ViewAR.orientation = .portrait
|
||||
|
||||
//try resetting anchors for the initial landscape orientation issue.
|
||||
|
||||
view.session.run(configuration)
|
||||
}else if let view = view as? ARSKView {
|
||||
//try resetting anchors for the initial landscape orientation issue.
|
||||
guard let config = configuration else { return }
|
||||
view.session.run(config)
|
||||
} else if let view = view as? ARSKView {
|
||||
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
|
||||
ViewAR.orientation = .portrait
|
||||
guard let config = configuration else { return }
|
||||
view.session.run(config)
|
||||
} else if let _ = view as? SCNView {
|
||||
UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
|
||||
ViewAR.orientation = .portrait
|
||||
|
||||
view.session.run(configuration)
|
||||
}
|
||||
|
||||
onlyRenderWhileRec = onlyRenderWhileRecording
|
||||
}
|
||||
/**
|
||||
A method that switches off the orientation lock used in a `UIViewController` with AR scenes 📐😴.
|
||||
|
||||
Recommended to use in the `UIViewController`'s method `func viewWillDisappear(_ animated: Bool)`.
|
||||
*/
|
||||
@objc public func rest() {
|
||||
@objc func rest() {
|
||||
ViewAR.orientation = UIInterfaceOrientationMask(ViewAR.orientations)
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Internal AR Video Frames Rendering
|
||||
//MARK: - AR Video Frames Rendering
|
||||
@available(iOS 11.0, *)
|
||||
internal extension RecordAR {
|
||||
@objc internal func renderFrame() {
|
||||
extension RecordAR {
|
||||
@objc func renderFrame() {
|
||||
//frame rendering
|
||||
if self.onlyRenderWhileRec && !isRecording && !isRecordingGIF {return}
|
||||
|
||||
guard let buffer = renderer.buffer else{return}
|
||||
guard let rawBuffer = renderer.rawBuffer else{logAR.message("ERROR:- An error occurred while rendering the camera's main buffers.");return}
|
||||
guard let size = renderer.bufferSize else{logAR.message("ERROR:- An error occurred while rendering the camera buffer.");return}
|
||||
if self.onlyRenderWhileRec && !isRecording && !isRecordingGIF { return }
|
||||
|
||||
renderer.ARcontentMode = contentMode
|
||||
|
||||
|
||||
guard let buffer = renderer.buffer else { return }
|
||||
guard let rawBuffer = renderer.rawBuffer else {
|
||||
logAR.message("ERROR:- An error occurred while rendering the camera's main buffers.")
|
||||
return
|
||||
}
|
||||
guard let size = renderer.bufferSize else {
|
||||
logAR.message("ERROR:- An error occurred while rendering the camera buffer.")
|
||||
return
|
||||
}
|
||||
|
||||
self.writerQueue.sync {
|
||||
var time:CMTime {return CMTimeMakeWithSeconds(renderer.time, 1000000);}
|
||||
|
||||
var time: CMTime { return CMTime(seconds: renderer.time, preferredTimescale: 1000000) }
|
||||
|
||||
self.renderAR?.frame(didRender: buffer, with: time, using: rawBuffer)
|
||||
|
||||
@@ -697,22 +788,22 @@ internal extension RecordAR {
|
||||
//frame writing
|
||||
if self.isRecording {
|
||||
if let frameWriter = self.writer {
|
||||
var finalFrameTime:CMTime?
|
||||
var finalFrameTime: CMTime?
|
||||
if self.backFromPause {
|
||||
if self.resumeFrameTime == nil {
|
||||
self.resumeFrameTime = time
|
||||
}
|
||||
//Formula: (currentTime - (timeWhenResume - timeWhenPaused))
|
||||
guard let resumeTime = self.resumeFrameTime else {return}
|
||||
guard let pausedTime = self.pausedFrameTime else {return}
|
||||
guard let resumeTime = self.resumeFrameTime,
|
||||
let pausedTime = self.pausedFrameTime else { return }
|
||||
finalFrameTime = self.adjustTime(current: time, resume: resumeTime, pause: pausedTime)
|
||||
}else{
|
||||
} else {
|
||||
finalFrameTime = time
|
||||
}
|
||||
|
||||
frameWriter.insert(pixel: buffer, with: finalFrameTime!)
|
||||
|
||||
guard let isWriting = frameWriter.isWritingWithoutError else {return}
|
||||
guard let isWriting = frameWriter.isWritingWithoutError else { return }
|
||||
if !isWriting {
|
||||
self.isRecording = false
|
||||
|
||||
@@ -720,21 +811,23 @@ internal extension RecordAR {
|
||||
self.delegate?.recorder(didFailRecording: errSecDecode as? Error, and: "An error occured while recording your video.")
|
||||
self.delegate?.recorder(didEndRecording: self.currentVideoPath!, with: false)
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
self.currentVideoPath = self.newVideoPath
|
||||
|
||||
self.writer = WritAR(output: self.currentVideoPath!, width: Int(size.width), height: Int(size.height), adjustForSharing: self.adjustVideoForSharing, audioEnabled: self.enableAudio, orientaions: self.inputViewOrientations, queue: self.writerQueue, allowMix: self.enableMixWithOthers)
|
||||
self.writer?.videoInputOrientation = self.videoOrientation
|
||||
self.writer?.delegate = self.delegate
|
||||
}
|
||||
}else if !self.isRecording && self.adjustPausedTime {
|
||||
} else if !self.isRecording && self.adjustPausedTime {
|
||||
writer?.pause()
|
||||
|
||||
self.adjustPausedTime = false
|
||||
|
||||
if self.pausedFrameTime != nil {
|
||||
self.pausedFrameTime = self.adjustTime(current: time, resume: self.resumeFrameTime!, pause: self.pausedFrameTime!)
|
||||
}else{
|
||||
if self.pausedFrameTime != nil && self.resumeFrameTime != nil {
|
||||
self.pausedFrameTime = self.adjustTime(current: time,
|
||||
resume: self.resumeFrameTime!,
|
||||
pause: self.pausedFrameTime!)
|
||||
} else {
|
||||
self.pausedFrameTime = time
|
||||
}
|
||||
|
||||
|
||||
@@ -23,9 +23,12 @@ import ARKit
|
||||
|
||||
Recommended to return in the application delegate method `func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask`.
|
||||
*/
|
||||
@objc internal(set) public static var orientation: UIInterfaceOrientationMask {get {return mask;}set {mask = newValue}}
|
||||
@objc internal(set) public static var orientation: UIInterfaceOrientationMask {
|
||||
get { return mask }
|
||||
set { mask = newValue }
|
||||
}
|
||||
|
||||
internal static var orientations:[UIInterfaceOrientationMask] {
|
||||
static var orientations: [UIInterfaceOrientationMask] {
|
||||
var all:[UIInterfaceOrientationMask] = []
|
||||
if let info = Bundle.main.infoDictionary {
|
||||
if let supportedOrientaions = info["UISupportedInterfaceOrientations"] as? NSArray {
|
||||
@@ -33,11 +36,11 @@ import ARKit
|
||||
if let o = orientation as? String {
|
||||
if o == "UIInterfaceOrientationPortrait" {
|
||||
all.append(.portrait)
|
||||
}else if o == "UIInterfaceOrientationPortraitUpsideDown" {
|
||||
} else if o == "UIInterfaceOrientationPortraitUpsideDown" {
|
||||
all.append(.portraitUpsideDown)
|
||||
}else if o == "UIInterfaceOrientationLandscapeLeft" {
|
||||
} else if o == "UIInterfaceOrientationLandscapeLeft" {
|
||||
all.append(.landscapeLeft)
|
||||
}else if o == "UIInterfaceOrientationLandscapeRight" {
|
||||
} else if o == "UIInterfaceOrientationLandscapeRight" {
|
||||
all.append(.landscapeRight)
|
||||
}
|
||||
}
|
||||
@@ -48,9 +51,21 @@ import ARKit
|
||||
return all
|
||||
}
|
||||
//returns the application's delegate to check if the current UIViewController contains an ARView
|
||||
fileprivate static var delegate = UIApplication.shared.delegate
|
||||
private static var delegate = UIApplication.shared.delegate
|
||||
//variable for the setter in `mask`
|
||||
fileprivate static var m: UIInterfaceOrientationMask = .portrait
|
||||
private static var m: UIInterfaceOrientationMask = .portrait
|
||||
//returns the most appropriate orientation based on the content of the UIViewController.
|
||||
fileprivate static var mask: UIInterfaceOrientationMask {get {if let vc = delegate?.window??.inputViewController {if vc.hasARView {return .portrait}else{return UIInterfaceOrientationMask(orientations)}};return m;}set {m = newValue;}}
|
||||
private static var mask: UIInterfaceOrientationMask {
|
||||
get {
|
||||
if let vc = delegate?.window??.inputViewController {
|
||||
if vc.hasARView {
|
||||
return .portrait
|
||||
} else {
|
||||
return UIInterfaceOrientationMask(orientations)
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
set { m = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// SSSS.swift
|
||||
// ARVideoKit
|
||||
//
|
||||
// Created by Saul Moreno Abril on 01/05/2019.
|
||||
// Copyright © 2019 Ahmed Fathit Bekhit. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class WeakProxy: NSObject {
|
||||
weak var target: NSObjectProtocol?
|
||||
|
||||
init(target: NSObjectProtocol) {
|
||||
self.target = target
|
||||
super.init()
|
||||
}
|
||||
|
||||
override func responds(to aSelector: Selector!) -> Bool {
|
||||
return (target?.responds(to: aSelector) ?? false) || super.responds(to: aSelector)
|
||||
}
|
||||
|
||||
override func forwardingTarget(for aSelector: Selector!) -> Any? {
|
||||
return target
|
||||
}
|
||||
}
|
||||
@@ -19,8 +19,8 @@
|
||||
FB36BD9420085C7B00002808 /* Scene.m in Sources */ = {isa = PBXBuildFile; fileRef = FB36BD9120085C7B00002808 /* Scene.m */; };
|
||||
FB36BD9520085C7B00002808 /* Scene.sks in Resources */ = {isa = PBXBuildFile; fileRef = FB36BD9320085C7B00002808 /* Scene.sks */; };
|
||||
FB36BD98200869D200002808 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = FB36BD97200869D200002808 /* LICENSE */; };
|
||||
FB36BD9F20086A1600002808 /* ARVideoKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB36BD9E20086A0B00002808 /* ARVideoKit.framework */; };
|
||||
FB36BDA020086A1600002808 /* ARVideoKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FB36BD9E20086A0B00002808 /* ARVideoKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
FBE12A5320093EF800B0BB61 /* ARVideoKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB36BD9E20086A0B00002808 /* ARVideoKit.framework */; };
|
||||
FBE12A5420093EF800B0BB61 /* ARVideoKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FB36BD9E20086A0B00002808 /* ARVideoKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -31,7 +31,7 @@
|
||||
remoteGlobalIDString = FBD604DA1FA969DD00EC9804;
|
||||
remoteInfo = ARVideoKit;
|
||||
};
|
||||
FB36BDA120086A1600002808 /* PBXContainerItemProxy */ = {
|
||||
FBE12A5520093EF800B0BB61 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = FB36BD9920086A0A00002808 /* ARVideoKit.xcodeproj */;
|
||||
proxyType = 1;
|
||||
@@ -41,13 +41,13 @@
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
FB36BD61200816E400002808 /* Embed Frameworks */ = {
|
||||
FBE12A5720093EF800B0BB61 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
FB36BDA020086A1600002808 /* ARVideoKit.framework in Embed Frameworks */,
|
||||
FBE12A5420093EF800B0BB61 /* ARVideoKit.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -82,7 +82,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FB36BD9F20086A1600002808 /* ARVideoKit.framework in Frameworks */,
|
||||
FBE12A5320093EF800B0BB61 /* ARVideoKit.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -205,12 +205,12 @@
|
||||
FB36BD382008166700002808 /* Sources */,
|
||||
FB36BD392008166700002808 /* Frameworks */,
|
||||
FB36BD3A2008166700002808 /* Resources */,
|
||||
FB36BD61200816E400002808 /* Embed Frameworks */,
|
||||
FBE12A5720093EF800B0BB61 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
FB36BDA220086A1600002808 /* PBXTargetDependency */,
|
||||
FBE12A5620093EF800B0BB61 /* PBXTargetDependency */,
|
||||
);
|
||||
name = "ARKit-Video";
|
||||
productName = "ARKit-Video";
|
||||
@@ -299,10 +299,10 @@
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
FB36BDA220086A1600002808 /* PBXTargetDependency */ = {
|
||||
FBE12A5620093EF800B0BB61 /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = ARVideoKit;
|
||||
targetProxy = FB36BDA120086A1600002808 /* PBXContainerItemProxy */;
|
||||
targetProxy = FBE12A5520093EF800B0BB61 /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
@@ -436,7 +436,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DEVELOPMENT_TEAM = P5DZ3XQ9FJ;
|
||||
INFOPLIST_FILE = "ARKit-Video/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
@@ -452,7 +452,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DEVELOPMENT_TEAM = P5DZ3XQ9FJ;
|
||||
INFOPLIST_FILE = "ARKit-Video/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -10,6 +10,7 @@
|
||||
#import <SceneKit/SceneKit.h>
|
||||
#import <ARKit/ARKit.h>
|
||||
#import <CoreMedia/CoreMedia.h>
|
||||
#import <Photos/Photos.h>
|
||||
@import ARVideoKit;
|
||||
|
||||
@interface SCNViewController : UIViewController
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#import <SpriteKit/SpriteKit.h>
|
||||
#import <ARKit/ARKit.h>
|
||||
#import <CoreMedia/CoreMedia.h>
|
||||
#import <Photos/Photos.h>
|
||||
@import ARVideoKit;
|
||||
|
||||
@interface SKViewController : UIViewController
|
||||
|
||||
@@ -50,6 +50,9 @@
|
||||
|
||||
// Configure RecordAR to store media files in local app directory
|
||||
recorder.deleteCacheWhenExported = NO;
|
||||
|
||||
// Configure the envronment light rendering.
|
||||
recorder.enableAdjustEnvironmentLighting = YES;
|
||||
}
|
||||
|
||||
-(void)viewWillAppear:(BOOL)animated {
|
||||
@@ -70,7 +73,11 @@
|
||||
[self.sceneView.session pause];
|
||||
|
||||
if(recorder.status == RecordARStatusRecording) {
|
||||
[recorder stopAndExport:NULL];
|
||||
[recorder stopAndExport:^(NSURL*_Nonnull filePath, PHAuthorizationStatus status, BOOL ready) {
|
||||
if (status == PHAuthorizationStatusAuthorized) {
|
||||
NSLog(@"Video Exported Successfully!");
|
||||
}
|
||||
}];
|
||||
}
|
||||
recorder.onlyRenderWhileRecording = YES;
|
||||
|
||||
@@ -116,7 +123,11 @@
|
||||
[sender setTitle:@"Record" forState: UIControlStateNormal];
|
||||
[self.pauseBtn setTitle:@"Pause" forState: UIControlStateNormal];
|
||||
self.pauseBtn.enabled = NO;
|
||||
[recorder stopAndExport:NULL];
|
||||
[recorder stopAndExport:^(NSURL*_Nonnull filePath, PHAuthorizationStatus status, BOOL ready) {
|
||||
if (status == PHAuthorizationStatusAuthorized) {
|
||||
NSLog(@"Video Exported Successfully!");
|
||||
}
|
||||
}];
|
||||
}
|
||||
}else if (sender.tag == 1) {
|
||||
//Record with duration
|
||||
@@ -131,7 +142,11 @@
|
||||
[self.pauseBtn setTitle:@"Pause" forState: UIControlStateNormal];
|
||||
self.pauseBtn.enabled = NO;
|
||||
self.recordBtn.enabled = YES;
|
||||
[recorder stopAndExport:NULL];
|
||||
[recorder stopAndExport:^(NSURL*_Nonnull filePath, PHAuthorizationStatus status, BOOL ready) {
|
||||
if (status == PHAuthorizationStatusAuthorized) {
|
||||
NSLog(@"Video Exported Successfully!");
|
||||
}
|
||||
}];
|
||||
}
|
||||
}else if (sender.tag == 2) {
|
||||
//Pause
|
||||
|
||||
@@ -120,7 +120,12 @@
|
||||
[sender setTitle:@"Record" forState: UIControlStateNormal];
|
||||
[self.pauseBtn setTitle:@"Pause" forState: UIControlStateNormal];
|
||||
self.pauseBtn.enabled = NO;
|
||||
[recorder stopAndExport:NULL];
|
||||
//URL, PHAuthorizationStatus, Bool
|
||||
[recorder stopAndExport:^(NSURL*_Nonnull filePath, PHAuthorizationStatus status, BOOL ready) {
|
||||
if (status == PHAuthorizationStatusAuthorized) {
|
||||
NSLog(@"Video Exported Successfully!");
|
||||
}
|
||||
}];
|
||||
}
|
||||
}else if (sender.tag == 1) {
|
||||
//Record with duration
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# Objective-C Example
|
||||
This folder provides you an example project written in Objective-C that demonstrates the use of [**ARVideoKit**](https://github.com/AFathi/ARVideoKit) framework.
|
||||
|
||||
## Implementation
|
||||
1. Import the `ARVideoKit` into the application delegate implementation file `AppDelegate.m` and a `UIViewController` class with an `ARKit` scene.
|
||||
```
|
||||
@import ARVideoKit;
|
||||
```
|
||||
|
||||
2. In the application delegate implementation file `AppDelegate.m`, add this 👇 in order to allow the framework access and identify the supported device orientations. **Recommended** if the application supports landscape orientations.
|
||||
```
|
||||
-(UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
|
||||
return ViewAR.orientation;
|
||||
}
|
||||
```
|
||||
|
||||
3. In the selected `UIViewController` class, create a [`RecordAR`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR) global variable by adding the following in the interface section of the implementation file (e.g `ViewController.m`).
|
||||
```
|
||||
@interface ViewController ()
|
||||
{
|
||||
RecordAR *recorder;
|
||||
}
|
||||
```
|
||||
|
||||
4. Initialize [`RecordAR`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR) with [`ARSCNView`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#init-arscenekitarscnview) or [`ARSKView`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#init-arspritekitarskview). **Recommended** to initialize in `(void)viewDidLoad`.
|
||||
|
||||
Initializing RecordAR with `ARSCNView`
|
||||
```
|
||||
recorder = [[RecordAR alloc] initWithARSceneKit:self.sceneView];
|
||||
```
|
||||
Initializing RecordAR with `ARSKView`
|
||||
```
|
||||
recorder = [[RecordAR alloc] initWithARSpriteKit:self.SKSceneView];
|
||||
```
|
||||
|
||||
5. Call the [`prepare()`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-prepare_-configurationarconfiguration) method in `(void)viewWillAppear:(BOOL)animated`
|
||||
```
|
||||
ARWorldTrackingConfiguration *configuration = [ARWorldTrackingConfiguration new];
|
||||
[recorder prepare:configuration];
|
||||
```
|
||||
|
||||
6. Call the [`rest()`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-rest) method in `(void)viewWillDisappear:(BOOL)animated`
|
||||
```
|
||||
[recorder rest];
|
||||
```
|
||||
|
||||
7. Call the [`record()`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-record) method in the proper method to start recording.
|
||||
```
|
||||
- (IBAction)startRecording:(UIButton *)sender {
|
||||
if (recorder.status == RecordARStatusReadyToRecord) {
|
||||
[recorder record];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
8. Call the [`stopAndExport()`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-stopandexport_-finished-_-videopath-url-_-permissionstatusphauthorizationstatus-_-exportedbool---swiftvoid--nil) method in the proper method to stop recording.
|
||||
```
|
||||
- (IBAction)stopRecording:(UIButton *)sender {
|
||||
if (recorder.status == RecordARStatusRecording) {
|
||||
[recorder stopAndExport:NULL];
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -144,20 +144,12 @@
|
||||
path = SK;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FBE12A4B20086C9400B0BB61 /* Framework */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
path = Framework;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FBE134AD1FAAD3DD00BEC469 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FB2E368B1FAE2A510035B8D6 /* LICENSE */,
|
||||
FB36BDAA20086BB000002808 /* ARVideoKit.xcodeproj */,
|
||||
FBE134B81FAAD3DD00BEC469 /* ARVideoKit-Example */,
|
||||
FBE12A4B20086C9400B0BB61 /* Framework */,
|
||||
FBE134B71FAAD3DD00BEC469 /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
@@ -426,7 +418,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DEVELOPMENT_TEAM = LXD85S9DW4;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
@@ -437,7 +429,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ahmedbekhit.ARVideoKit-Example";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -448,7 +440,7 @@
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
DEVELOPMENT_TEAM = LXD85S9DW4;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(PROJECT_DIR)",
|
||||
@@ -459,7 +451,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ahmedbekhit.ARVideoKit-Example";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 4.0;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -19,8 +19,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
return ViewAR.orientation
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13529" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="N8h-YD-r0X">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="N8h-YD-r0X">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13527"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<customFonts key="customFonts">
|
||||
<array key="Avenir.ttc">
|
||||
<string>Avenir-Black</string>
|
||||
<string>Avenir-Medium</string>
|
||||
</array>
|
||||
</customFonts>
|
||||
<scenes>
|
||||
<!--Main View Controller-->
|
||||
<scene sceneID="wdx-bS-4nS">
|
||||
@@ -242,7 +236,7 @@
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="54" y="866"/>
|
||||
<point key="canvasLocation" x="53.600000000000001" y="865.81709145427294"/>
|
||||
</scene>
|
||||
<!--SKViewController-->
|
||||
<scene sceneID="emy-Ex-RPU">
|
||||
@@ -409,7 +403,7 @@
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="9if-am-XF6" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="54" y="134"/>
|
||||
<point key="canvasLocation" x="53.600000000000001" y="133.5832083958021"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
</document>
|
||||
|
||||
@@ -36,8 +36,13 @@ class SCNViewController: UIViewController, ARSCNViewDelegate, RenderARDelegate,
|
||||
// Set the scene to the view
|
||||
sceneView.scene = scene
|
||||
sceneView.scene.rootNode.scale = SCNVector3(0.2, 0.2, 0.2)
|
||||
//
|
||||
sceneView.automaticallyUpdatesLighting = true
|
||||
sceneView.autoenablesDefaultLighting = true
|
||||
|
||||
// Initialize ARVideoKit recorder
|
||||
|
||||
|
||||
recorder = RecordAR(ARSceneKit: sceneView)
|
||||
|
||||
/*----👇---- ARVideoKit Configuration ----👇----*/
|
||||
@@ -54,6 +59,9 @@ class SCNViewController: UIViewController, ARSCNViewDelegate, RenderARDelegate,
|
||||
// Configure ARKit content mode. Default is .auto
|
||||
recorder?.contentMode = .aspectFill
|
||||
|
||||
//record or photo add environment light rendering, Default is false
|
||||
recorder?.enableAdjustEnvironmentLighting = true
|
||||
|
||||
// Set the UIViewController orientations
|
||||
recorder?.inputViewOrientations = [.landscapeLeft, .landscapeRight, .portrait]
|
||||
// Configure RecordAR to store media files in local app directory
|
||||
@@ -107,7 +115,7 @@ class SCNViewController: UIViewController, ARSCNViewDelegate, RenderARDelegate,
|
||||
}else if status == .denied || status == .restricted || status == .notDetermined {
|
||||
let errorView = UIAlertController(title: "😅", message: "Please allow access to the photo library in order to save this media file.", preferredStyle: .alert)
|
||||
let settingsBtn = UIAlertAction(title: "Open Settings", style: .cancel) { (_) -> Void in
|
||||
guard let settingsUrl = URL(string: UIApplicationOpenSettingsURLString) else {
|
||||
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
|
||||
return
|
||||
}
|
||||
if UIApplication.shared.canOpenURL(settingsUrl) {
|
||||
@@ -115,11 +123,11 @@ class SCNViewController: UIViewController, ARSCNViewDelegate, RenderARDelegate,
|
||||
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
|
||||
})
|
||||
} else {
|
||||
UIApplication.shared.openURL(URL(string:UIApplicationOpenSettingsURLString)!)
|
||||
UIApplication.shared.openURL(URL(string:UIApplication.openSettingsURLString)!)
|
||||
}
|
||||
}
|
||||
}
|
||||
errorView.addAction(UIAlertAction(title: "Later", style: UIAlertActionStyle.default, handler: {
|
||||
errorView.addAction(UIAlertAction(title: "Later", style: UIAlertAction.Style.default, handler: {
|
||||
(UIAlertAction)in
|
||||
}))
|
||||
errorView.addAction(settingsBtn)
|
||||
|
||||
@@ -106,7 +106,7 @@ class SKViewController: UIViewController, ARSKViewDelegate, RenderARDelegate, Re
|
||||
}else if status == .denied || status == .restricted || status == .notDetermined {
|
||||
let errorView = UIAlertController(title: "😅", message: "Please allow access to the photo library in order to save this media file.", preferredStyle: .alert)
|
||||
let settingsBtn = UIAlertAction(title: "Open Settings", style: .cancel) { (_) -> Void in
|
||||
guard let settingsUrl = URL(string: UIApplicationOpenSettingsURLString) else {
|
||||
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
|
||||
return
|
||||
}
|
||||
if UIApplication.shared.canOpenURL(settingsUrl) {
|
||||
@@ -114,11 +114,11 @@ class SKViewController: UIViewController, ARSKViewDelegate, RenderARDelegate, Re
|
||||
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
|
||||
})
|
||||
} else {
|
||||
UIApplication.shared.openURL(URL(string:UIApplicationOpenSettingsURLString)!)
|
||||
UIApplication.shared.openURL(URL(string:UIApplication.openSettingsURLString)!)
|
||||
}
|
||||
}
|
||||
}
|
||||
errorView.addAction(UIAlertAction(title: "Later", style: UIAlertActionStyle.default, handler: {
|
||||
errorView.addAction(UIAlertAction(title: "Later", style: UIAlertAction.Style.default, handler: {
|
||||
(UIAlertAction)in
|
||||
}))
|
||||
errorView.addAction(settingsBtn)
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Swift Example
|
||||
This folder provides you an example project written in Swift that demonstrates the use of [**ARVideoKit**](https://github.com/AFathi/ARVideoKit) framework.
|
||||
|
||||
## Implementation
|
||||
1. `import ARVideoKit` in the application delegate `AppDelegate.swift` and a `UIViewController` with an `ARKit` scene.
|
||||
|
||||
2. In the application delegate `AppDelegate.swift`, add this 👇 in order to allow the framework access and identify the supported device orientations. **Recommended** if the application supports landscape orientations.
|
||||
```
|
||||
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
|
||||
return ViewAR.orientation
|
||||
}
|
||||
```
|
||||
|
||||
3. In the selected `UIViewController` class, create an optional type [`RecordAR`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR) global variable.
|
||||
```
|
||||
var recorder:RecordAR?
|
||||
```
|
||||
|
||||
4. Initialize [`RecordAR`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR) with [`ARSCNView`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#init-arscenekitarscnview) or [`ARSKView`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#init-arspritekitarskview). **Recommended** to initialize in `viewDidLoad()`.
|
||||
|
||||
Initializing RecordAR with `ARSCNView`
|
||||
```
|
||||
recorder = RecordAR(ARSceneKit: sceneView)
|
||||
```
|
||||
Initializing RecordAR with `ARSKView`
|
||||
```
|
||||
recorder = RecordAR(ARSpriteKit: SKSceneView)
|
||||
```
|
||||
|
||||
5. Call the [`prepare()`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-prepare_-configurationarconfiguration) method in `viewWillAppear(_ animated: Bool)`
|
||||
```
|
||||
let configuration = ARWorldTrackingConfiguration()
|
||||
recorder?.prepare(configuration)
|
||||
```
|
||||
|
||||
6. Call the [`rest()`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-rest) method in `viewWillDisappear(_ animated: Bool)`
|
||||
```
|
||||
recorder?.rest()
|
||||
```
|
||||
|
||||
7. Call the [`record()`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-record) method in the proper method to start recording.
|
||||
```
|
||||
@IBAction func startRecording(_ sender: UIButton) {
|
||||
recorder?.record()
|
||||
}
|
||||
```
|
||||
|
||||
8. Call the [`stopAndExport()`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-stopandexport_-finished-_-videopath-url-_-permissionstatusphauthorizationstatus-_-exportedbool---swiftvoid--nil) method in the proper method to stop recording.
|
||||
```
|
||||
@IBAction func stopRecording(_ sender: UIButton) {
|
||||
recorder?.stopAndExport()
|
||||
}
|
||||
```
|
||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
// Generated by Apple Swift version 4.0.3 (swiftlang-900.0.74.1 clang-900.0.39.2)
|
||||
// Generated by Apple Swift version 4.1.2 (swiftlang-902.0.54 clang-902.0.39.2)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wgcc-compat"
|
||||
|
||||
@@ -15,15 +15,6 @@
|
||||
# define __has_warning(x) 0
|
||||
#endif
|
||||
|
||||
#if __has_attribute(external_source_symbol)
|
||||
# define SWIFT_STRINGIFY(str) #str
|
||||
# define SWIFT_MODULE_NAMESPACE_PUSH(module_name) _Pragma(SWIFT_STRINGIFY(clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in=module_name, generated_declaration))), apply_to=any(function, enum, objc_interface, objc_category, objc_protocol))))
|
||||
# define SWIFT_MODULE_NAMESPACE_POP _Pragma("clang attribute pop")
|
||||
#else
|
||||
# define SWIFT_MODULE_NAMESPACE_PUSH(module_name)
|
||||
# define SWIFT_MODULE_NAMESPACE_POP
|
||||
#endif
|
||||
|
||||
#if __has_include(<swift/objc-prologue.h>)
|
||||
# include <swift/objc-prologue.h>
|
||||
#endif
|
||||
@@ -38,7 +29,7 @@
|
||||
# define SWIFT_TYPEDEFS 1
|
||||
# if __has_include(<uchar.h>)
|
||||
# include <uchar.h>
|
||||
# elif !defined(__cplusplus) || __cplusplus < 201103L
|
||||
# elif !defined(__cplusplus)
|
||||
typedef uint_least16_t char16_t;
|
||||
typedef uint_least32_t char32_t;
|
||||
# endif
|
||||
@@ -188,7 +179,13 @@ typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4)));
|
||||
#pragma clang diagnostic ignored "-Wunknown-pragmas"
|
||||
#pragma clang diagnostic ignored "-Wnullability"
|
||||
|
||||
SWIFT_MODULE_NAMESPACE_PUSH("ARVideoKit")
|
||||
#if __has_attribute(external_source_symbol)
|
||||
# pragma push_macro("any")
|
||||
# undef any
|
||||
# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="ARVideoKit",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol))
|
||||
# pragma pop_macro("any")
|
||||
#endif
|
||||
|
||||
/// Allows specifying the final video orientation.
|
||||
typedef SWIFT_ENUM(NSInteger, ARFrameMode) {
|
||||
ARFrameModeAuto = 0,
|
||||
@@ -248,6 +245,7 @@ typedef SWIFT_ENUM(NSInteger, ARVideoOrientation) {
|
||||
SWIFT_CLASS("_TtC10ARVideoKit6ARView") SWIFT_AVAILABILITY(ios,introduced=11.0)
|
||||
@interface ARView : NSObject
|
||||
- (nonnull instancetype)init SWIFT_UNAVAILABLE;
|
||||
+ (nonnull instancetype)new SWIFT_DEPRECATED_MSG("-init is unavailable");
|
||||
@end
|
||||
|
||||
@class NSCoder;
|
||||
@@ -285,6 +283,7 @@ enum RecordARMicrophoneStatus : NSInteger;
|
||||
enum RecordARMicrophonePermission : NSInteger;
|
||||
@class ARSCNView;
|
||||
@class ARSKView;
|
||||
@class SCNView;
|
||||
@class UIImage;
|
||||
|
||||
/// This class renders the <code>ARSCNView</code> or <code>ARSKView</code> content with the device’s camera stream to generate a video 📹, photo 🌄, live photo 🎇 or GIF 🎆.
|
||||
@@ -334,10 +333,14 @@ SWIFT_CLASS("_TtC10ARVideoKit8RecordAR") SWIFT_AVAILABILITY(ios,introduced=11.0)
|
||||
@property (nonatomic) BOOL adjustGIFForSharing;
|
||||
/// A boolean that enables or disables clearing cached media after exporting to Camera Roll. Default is <code>true</code>.
|
||||
@property (nonatomic) BOOL deleteCacheWhenExported;
|
||||
/// A boolean that enables or disables using envronment light rendering. Default is <code>false</code>.
|
||||
@property (nonatomic) BOOL enableAdjsutEnvironmentLighting;
|
||||
/// Initialize 🌞🍳 <code>RecordAR</code> with an <code>ARSCNView</code> 🚀.
|
||||
- (nullable instancetype)initWithARSceneKit:(ARSCNView * _Nonnull)ARSceneKit OBJC_DESIGNATED_INITIALIZER;
|
||||
/// Initialize 🌞🍳 <code>RecordAR</code> with an <code>ARSKView</code> 👾.
|
||||
- (nullable instancetype)initWithARSpriteKit:(ARSKView * _Nonnull)ARSpriteKit OBJC_DESIGNATED_INITIALIZER;
|
||||
/// Initialize 🌞🍳 <code>RecordAR</code> with an <code>SCNView</code> 🚀.
|
||||
- (nullable instancetype)initWithSceneKit:(SCNView * _Nonnull)SceneKit OBJC_DESIGNATED_INITIALIZER;
|
||||
/// A method that renders a photo 🌄 and returns it as <code>UIImage</code>.
|
||||
- (UIImage * _Nonnull)photo SWIFT_WARN_UNUSED_RESULT;
|
||||
/// A method that renders a <code>PHLivePhoto</code> 🎇 and returns <code>PHLivePhotoPlus</code> in the completion handler.
|
||||
@@ -463,7 +466,7 @@ SWIFT_AVAILABILITY(ios,introduced=11.0)
|
||||
/// Recommended to use in the <code>UIViewController</code>’s method <code>func viewWillAppear(_ animated: Bool)</code>
|
||||
/// \param configuration An object that defines motion and scene tracking behaviors for the session.
|
||||
///
|
||||
- (void)prepare:(ARConfiguration * _Nonnull)configuration;
|
||||
- (void)prepare:(ARConfiguration * _Nullable)configuration;
|
||||
/// A method that switches off the orientation lock used in a <code>UIViewController</code> with AR scenes 📐😴.
|
||||
/// Recommended to use in the <code>UIViewController</code>’s method <code>func viewWillDisappear(_ animated: Bool)</code>.
|
||||
- (void)rest;
|
||||
@@ -603,5 +606,7 @@ SWIFT_CLASS_PROPERTY(@property (nonatomic, class, readonly) UIInterfaceOrientati
|
||||
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
|
||||
@end
|
||||
|
||||
SWIFT_MODULE_NAMESPACE_POP
|
||||
#if __has_attribute(external_source_symbol)
|
||||
# pragma clang attribute pop
|
||||
#endif
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
Binary file not shown.
@@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2017 Ahmed Fathi Bekhit - me@ahmedbekhit.com
|
||||
Copyright 2017 Ahmed Fathi Bekhit, www.ahmedbekhit.com, me@ahmedbekhit.com
|
||||
|
||||
ARVideoKit is licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,23 @@
|
||||
// swift-tools-version:5.1
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "ARVideoKit",
|
||||
platforms: [
|
||||
.iOS(.v11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "ARVideoKit",
|
||||
targets: ["ARVideoKit"]),
|
||||
],
|
||||
dependencies: [ ],
|
||||
targets: [
|
||||
.target(
|
||||
name: "ARVideoKit",
|
||||
dependencies: [],
|
||||
path: "ARVideoKit")
|
||||
]
|
||||
)
|
||||
@@ -1,4 +1,16 @@
|
||||
# ARVideoKit
|
||||
> Use [swift_4_2](https://github.com/AFathi/ARVideoKit/tree/swift_4_2) branch for projects written in Swift 4.2
|
||||
|
||||
> Use [master](https://github.com/AFathi/ARVideoKit/tree/master) branch for projects written in Swift 4.0
|
||||
|
||||
## Sponsors
|
||||
[**Chemical Wedding**](https://www.chemicalwedding.tv)
|
||||
|
||||

|
||||
|
||||
## ARVideoKit
|
||||
|
||||

|
||||
|
||||
An iOS Framework that enables developers to capture videos 📹, photos 🌄, Live Photos 🎇, and GIFs 🎆 with ARKit content.
|
||||
|
||||
In other words, you **NO LONGER** have to ~screen record~/~screenshot~ to capture videos 📹 and photos 🌄 of your awesome ARKit apps!
|
||||
@@ -10,25 +22,28 @@ In other words, you **NO LONGER** have to ~screen record~/~screenshot~ to captur
|
||||
| [Preview](#preview) | Displays 2 GIF images captured using the supported [`gif`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-gifforduration-durationtimeinterval-exportbool-_-finished-_-statusbool-_-gifpath-url-_-permissionstatusphauthorizationstatus-_-exportedbool---swiftvoid--nil) method in `ARVideoKit`|
|
||||
| [Key Features](#key-features) | Lists the key features `ARVideoKit` offers |
|
||||
| [Compatibility](#compatibility) | Describes the `ARVideoKit` device and iOS compatibality |
|
||||
| [Example Project](#example-project) | Explains how to run the example project provided in this repository |
|
||||
| [Installation](#installation) | Describes the [Manual](#manual) option to install `ARVideoKit` |
|
||||
| [Implementation](#implementation) | Lists the [steps needed](#implementation), [notes](#note), and [reference](#youre-all-set-) for more options |
|
||||
| [Example Projects](#example-projects) | Explains how to run the example project provided in this repository |
|
||||
| [Installation](#installation) | Describes the [Cocoapods](#cocoapods), [Carthage](#carthage) & [Manual](#manual) options to install `ARVideoKit` |
|
||||
| [Implementation](#implementation) | Lists the [steps needed](#implementation) for Objective-C & Swift, [notes](#note), and [reference](#youre-all-set-) for more options |
|
||||
| [Publishing to the App Store](#publishing-to-the-app-store) | Describes the steps **required** before submitting an application using `ARVideoKit` to the App Store. |
|
||||
|[](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=ahmedfbekhit@gmail.com&item_name=Support+ARVideoKit+Developer&item_number=ARVideoKit+Framework+Donations&amount=0%2e00¤cy_code=USD) | [Donations](#donate) will support me to keep maintaining `ARVideoKit` ❤️|
|
||||
|[Sponsors](#sponsors) | Featuring the sponsors of this project |
|
||||
| [Contributions](#contributions) | Describes how you can contribute to this project |
|
||||
| [Apps using ARVideoKit](#apps-using-arvideokit) | A list of published applications using ARVideoKit |
|
||||
| [License](#license) | Describes `ARVideoKit` license |
|
||||
| [AppCoda Tutorial](https://www.appcoda.com/record-arkit-video/) | Check out the detailed tutorial about implementing `ARVideoKit` with SpriteKit ☺️ |
|
||||
| [AppCoda Tutorial](https://www.appcoda.com/record-arkit-video/) | Check out a detailed tutorial about implementing `ARVideoKit` with SpriteKit ☺️ |
|
||||
|
||||
## Preview
|
||||
|👾 [Initialized with SpriteKit](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#init-arspritekitarskview)👇 |🚀 [Initialized with SceneKit](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#init-arscenekitarscnview) 👇 |
|
||||
|👾 [Initialized with SpriteKit](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#init-arspritekitarskview)👇|🚀 [Initialized with SceneKit](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#init-arscenekitarscnview) 👇|
|
||||
|--------------|--------------|
|
||||
|
||||
 
|
||||
|  | |
|
||||
|
||||
## Key Features
|
||||
✅ Capture [Photos](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-photo---uiimage) from [`ARSCNView`](https://developer.apple.com/documentation/arkit/arscnview) and [`ARSKView`](https://developer.apple.com/documentation/arkit/arskview)
|
||||
✅ Capture [Photos](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-photo---uiimage) from [`ARSCNView`](https://developer.apple.com/documentation/arkit/arscnview), [`ARSKView`](https://developer.apple.com/documentation/arkit/arskview), and [`SCNView`](https://developer.apple.com/documentation/scenekit/scnview)
|
||||
|
||||
✅ Capture [Live Photos](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-livephotoexportbool-_-finished-_-statusbool-_-livephotophlivephotoplus-_-permissionstatusphauthorizationstatus-_-exportedbool---swiftvoid--nil) & [GIFs](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-gifforduration-durationtimeinterval-exportbool-_-finished-_-statusbool-_-gifpath-url-_-permissionstatusphauthorizationstatus-_-exportedbool---swiftvoid--nil) from [`ARSCNView`](https://developer.apple.com/documentation/arkit/arscnview) and [`ARSKView`](https://developer.apple.com/documentation/arkit/arskview)
|
||||
✅ Capture [Live Photos](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-livephotoexportbool-_-finished-_-statusbool-_-livephotophlivephotoplus-_-permissionstatusphauthorizationstatus-_-exportedbool---swiftvoid--nil) & [GIFs](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-gifforduration-durationtimeinterval-exportbool-_-finished-_-statusbool-_-gifpath-url-_-permissionstatusphauthorizationstatus-_-exportedbool---swiftvoid--nil) from [`ARSCNView`](https://developer.apple.com/documentation/arkit/arscnview), [`ARSKView`](https://developer.apple.com/documentation/arkit/arskview), and [`SCNView`](https://developer.apple.com/documentation/scenekit/scnview)
|
||||
|
||||
✅ [Record](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-record) Videos from [`ARSCNView`](https://developer.apple.com/documentation/arkit/arscnview) and [`ARSKView`](https://developer.apple.com/documentation/arkit/arskview)
|
||||
✅ [Record](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-record) Videos from [`ARSCNView`](https://developer.apple.com/documentation/arkit/arscnview), [`ARSKView`](https://developer.apple.com/documentation/arkit/arskview), and [`SCNView`](https://developer.apple.com/documentation/scenekit/scnview)
|
||||
|
||||
✅ [Pause/Resume](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-pause) video
|
||||
|
||||
@@ -42,66 +57,44 @@ In other words, you **NO LONGER** have to ~screen record~/~screenshot~ to captur
|
||||
- iOS 11
|
||||
- Swift 3.2 or higher
|
||||
|
||||
## Example Project
|
||||
To try the example project, simply clone this repository and open `ARVideoKit-Example.xcodeproj` project file.
|
||||
## Example Projects
|
||||
To try the example project, simply clone this repository and open the `Examples` folder to choose between the Objective-C and Swift project files.
|
||||
|
||||
It's **recommended** to test `SKViewController` by assigning it as the initial view controller.
|
||||

|
||||
## Installation
|
||||
### Cocoapods
|
||||
1. Add this line to your project's `Podfile` (for Swift 5.0)
|
||||
```
|
||||
pod 'ARVideoKit', '~> 1.5.51'
|
||||
|
||||
```
|
||||
2. Install the pod
|
||||
```
|
||||
$ pod install
|
||||
```
|
||||
|
||||
### Carthage
|
||||
1. Add this line to your project's `Cartfile`
|
||||
```
|
||||
github "AFathi/ARVideoKit" ~> 1.31
|
||||
```
|
||||
2. Update your Carthage directory
|
||||
```
|
||||
$ carthage update
|
||||
```
|
||||
|
||||
### Swift Package Manager (available Xcode 11.2 and forward)
|
||||
|
||||
1. In Xcode, select File > Swift Packages > Add Package Dependency.
|
||||
2. Follow the prompts using the URL for this repository.
|
||||
|
||||
### Manual
|
||||
Drag the `ARVideoKit.framework` file as an embedded binary of your project targets. `ARVideoKit.framework` can be found in the `/Framework/` folder of this repository.
|
||||

|
||||
|
||||
Drag `ARVideoKit.xcodeproj` into your project and click the **+** button in the embedded binaries section of your project's target.
|
||||

|
||||
## Implementation
|
||||
1. `import ARVideoKit` in the application delegate `AppDelegate.swift` and a `UIViewController` with an `ARKit` scene.
|
||||
|
||||
2. In the application delegate `AppDelegate.swift`, add this 👇 in order to allow the framework access and identify the supported device orientations. **Recommended** if the application supports landscape orientations.
|
||||
```
|
||||
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
|
||||
return ViewAR.orientation
|
||||
}
|
||||
```
|
||||
|
||||
3. In the selected `UIViewController` class, create an optional type [`RecordAR`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR) global variable.
|
||||
```
|
||||
var recorder:RecordAR?
|
||||
```
|
||||
|
||||
4. Initialize [`RecordAR`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR) with [`ARSCNView`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#init-arscenekitarscnview) or [`ARSKView`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#init-arspritekitarskview). **Recommended** to initialize in `viewDidLoad()`.
|
||||
|
||||
Initializing RecordAR with `ARSCNView`
|
||||
```
|
||||
recorder = RecordAR(ARSceneKit: sceneView)
|
||||
```
|
||||
Initializing RecordAR with `ARSKView`
|
||||
```
|
||||
recorder = RecordAR(ARSpriteKit: SKSceneView)
|
||||
```
|
||||
|
||||
5. Call the [`prepare()`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-prepare_-configurationarconfiguration) method in `viewWillAppear(_ animated: Bool)`
|
||||
```
|
||||
let configuration = ARWorldTrackingConfiguration()
|
||||
recorder?.prepare(configuration)
|
||||
```
|
||||
|
||||
6. Call the [`rest()`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-rest) method in `viewWillDisappear(_ animated: Bool)`
|
||||
```
|
||||
recorder?.rest()
|
||||
```
|
||||
|
||||
7. Call the [`record()`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-record) method in the proper method to start recording.
|
||||
```
|
||||
@IBAction func startRecording(_ sender: UIButton) {
|
||||
recorder?.record()
|
||||
}
|
||||
```
|
||||
|
||||
8. Call the [`stopAndExport()`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR#func-stopandexport_-finished-_-videopath-url-_-permissionstatusphauthorizationstatus-_-exportedbool---swiftvoid--nil) method in the proper method to stop recording.
|
||||
```
|
||||
@IBAction func stopRecording(_ sender: UIButton) {
|
||||
recorder?.stopAndExport()
|
||||
}
|
||||
```
|
||||
### Swift
|
||||
[Click here to check the Swift implementation steps.](https://github.com/AFathi/ARVideoKit/tree/master/Examples/Swift)
|
||||
### Objective-C
|
||||
[Click here to check the Objective-C implementation steps.](https://github.com/AFathi/ARVideoKit/tree/master/Examples/Objective-C)
|
||||
|
||||
### NOTE
|
||||
Make sure you add the usage description of the `camera`, `microphone`, and `photo library` in the app's `Info.plist`.
|
||||
@@ -123,39 +116,69 @@ Check [`RecordAR`](https://github.com/AFathi/ARVideoKit/wiki/RecordAR) documenta
|
||||
## Publishing to the App Store
|
||||
Before publishing to the App Store make sure to add the [ARVideoKit License](#license) to your app licences list.
|
||||
|
||||
Additionally, you MUST **strip out the simulator architectures** from the framework before pushing an application to the App Store.
|
||||
1. Build ARVideoKit for release
|
||||
> Open ARVideoKit.xcodeproj
|
||||
|
||||
> Select ARVideoKitRelease scheme with Generic iOS Device
|
||||
|
||||
> Build the project (cmd + B)
|
||||
|
||||
> Right click on Products/ARVideoKit.framework -> Show in finder
|
||||
|
||||
> Copy and replace ARVideoKit.framework in your project
|
||||
|
||||
Additionally, if you are using the binary build from `Framework Build` or the latest release, you MUST **strip out the simulator architectures** from the framework before pushing an application to the App Store.
|
||||
|
||||
To do so, follow those steps:
|
||||
|
||||
1. Install Carthage
|
||||
2. Install Carthage
|
||||
> Download `Carthage.pkg` [from here](https://github.com/Carthage/Carthage/releases)
|
||||
|
||||
> Or install with Homebrew using this command `brew install carthage`
|
||||
2. Go to your project target's `Build Phase`
|
||||
3. Go to your project target's `Build Phase`
|
||||
<img width="684" alt="screen shot 2017-11-14 at 8 21 44 pm" src="https://user-images.githubusercontent.com/4106695/32813978-e70ae5a0-c97a-11e7-9d19-3ef434e4c4f1.png">
|
||||
|
||||
3. Add a new `Run Script Phase`
|
||||
4. Add a new `Run Script Phase`
|
||||
<img width="686" alt="screen shot 2017-11-14 at 8 22 14 pm" src="https://user-images.githubusercontent.com/4106695/32814003-0ab4cffc-c97b-11e7-97d0-cf3143afec6d.png">
|
||||
|
||||
4. Add the following command to the `Run Script Phase`
|
||||
5. Add the following command to the `Run Script Phase`
|
||||
```
|
||||
/usr/local/bin/carthage copy-frameworks
|
||||
```
|
||||
<img width="676" alt="screen shot 2017-11-14 at 8 30 12 pm" src="https://user-images.githubusercontent.com/4106695/32814033-3302bece-c97b-11e7-867c-e8707ac7dd6b.png">
|
||||
|
||||
5. Finally, add `ARVideoKit.framework` file path as an `Input File`. In my case, I have it in a folder named `Frameworks` inside my project folder
|
||||
6. Finally, add `ARVideoKit.framework` file path as an `Input File`. In my case, I have it in a folder named `Frameworks` inside my project folder
|
||||
<img width="672" alt="screen shot 2017-11-14 at 8 41 06 pm" src="https://user-images.githubusercontent.com/4106695/32814258-327bd048-c97c-11e7-8148-8d606d545214.png">
|
||||
|
||||
## Donate
|
||||
Donations will support me to keep maintining **ARVideoKit Framework** ❤️
|
||||
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=ahmedfbekhit@gmail.com&item_name=Support+ARVideoKit+Developer&item_number=ARVideoKit+Framework+Donations&amount=0%2e00¤cy_code=USD)
|
||||
|
||||
## Contributions
|
||||
If you have an idea for a new **ARVideoKit** feature/functionality and want to add it to this repository, feel free to fork the project and create a pull request!
|
||||
|
||||
## [License](LICENSE)
|
||||
Copyright 2017 Ahmed Fathi Bekhit, www.ahmedbekhit.com, me@ahmedbekhit.com
|
||||
Also, feel free to create an issue if you have any suggestions or need any help ☺️
|
||||
|
||||
## Apps using ARVideoKit
|
||||
| App | Description |
|
||||
| ------------------ |:------------------:|
|
||||
| [Pathica](https://apps.apple.com/us/app/pathica/id1564780182) | The first proof of the theory of "six degrees of separation" which is the idea that any two people on the planet can be connected through five or fewer individuals. |
|
||||
| [Our SolAR](https://itunes.apple.com/app/id1267675913) | An app that allows you to see our Solar System anywhere at all! |
|
||||
| [In The Walls](https://apps.apple.com/us/app/id1522257130) | Uses real time face tracking and AR to put your face in any real world wall! |
|
||||
| [ScribScrab](https://apps.apple.com/in/app/scribscrab/id1339432955) | ScribScrab is a tool for creativity. You’re only limited to your imagination and your battery life. |
|
||||
|
||||
_Feel free to add your application to this list!_
|
||||
## License
|
||||
### ARVideoKit Framework
|
||||
Copyright 2018 Ahmed Fathi Bekhit, www.ahmedbekhit.com, me@ahmedbekhit.com
|
||||
|
||||
`ARVideoKit` is licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
|
||||
|
||||
[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
|
||||
|
||||
### ARVideoKit Logo & Header
|
||||
Designed by [**Gabriel Garcia**](https://github.com/ggabogarcia) and licensed under the [Creative Commons Attribution 4.0 International License](https://creativecommons.org/licenses/by/4.0/)
|
||||

|
||||
|
||||
Reference in New Issue
Block a user