122 Commits

Author SHA1 Message Date
Ahmed Bekhit b923fac2f9 Merge pull request #130 from fattalgazi/swift_5
Update README.md
2021-07-13 16:54:09 -04:00
isa 9e103afc7b Update README.md 2021-07-13 22:33:57 +03:00
Ahmed Bekhit b7e1facded Merge pull request #122 from seriyvolk83/audio_crash_fix
Fixing potential crash #101
2020-12-09 17:47:19 -05:00
Ahmed Bekhit 63c9ebb500 Merge pull request #117 from Jlack6/patch-1
Update README.md
2020-12-09 17:46:13 -05:00
Ahmed Bekhit 3162fa72fb Update README.md 2020-11-19 16:10:43 +03:00
Alexander Volkov 0c9a76a77a Handling flag properly 2020-11-10 12:16:49 +03:00
Alexander Volkov e2ba1ac1c3 Adding check 2020-11-10 12:08:45 +03:00
Alexander Volkov 092413fb42 Fixing potential crash 2020-11-10 11:54:57 +03:00
James Lack 7596d066a6 Update README.md
Added ScribScrab to the Apps using ARVideoKit
2020-09-11 09:23:50 -04:00
Ahmed Bekhit 275f850486 Merge pull request #115 from mattbierner/patch-1
Add In The Walls app
2020-08-30 16:07:01 -04:00
Ahmed Bekhit e150e00203 Update README.md 2020-08-21 17:24:48 -04:00
Matt Bierner e2d5e537e3 Add In The Walls app
Adds [In The Walls](https://apps.apple.com/us/app/id1522257130) to the readme. We use this library for recording video
2020-08-05 19:43:35 -07:00
Ahmed Bekhit 1ef5e99fe7 Merge pull request #113 from shaj04/ARRenderer_content_mode_bugfix
adjust when to set renderer.ARcontentMode
2020-08-01 20:31:52 -04:00
Ahmed Bekhit 62a6c6a89a Update README.md 2020-07-30 21:50:13 -04:00
shajie e8dae8c989 adjust when to set renderer.ARcontentMode 2020-06-12 19:00:46 +08:00
Ahmed Bekhit 2d87e3e7e7 Merge pull request #111 from AFathi/CocoaPodsUpdate-patch-1
Update to latest version
2020-04-25 15:58:46 -04:00
Giacomo 74a3159c70 Update to latest version 2020-04-23 15:27:12 +02:00
Ahmed Bekhit 589e6e77d4 Merge pull request #108 from 0k1019/feature/MaxDevice
Fix iPhone X Max, XS Max, 11Pro Max size recording size problem.
2020-04-15 17:20:06 -04:00
Youngho Kwon d1c840e383 Update isNotch function consider when landscape mode. 2020-04-09 16:12:18 +09:00
Youngho Kwon ae9c620621 Fix iPhone X Max, XS Max, 11Pro Max size recording size problem. Renewing "isiPhone10" extension to "isNotch". 2020-03-24 15:41:44 +09:00
Ahmed Bekhit 8eabd31931 Merge pull request #105 from GiacomoLeopizzi/swift_package_manager
Swift package manager
2020-02-27 11:26:27 -05:00
Giacomo 6c0465b521 Merge pull request #2 from GiacomoLeopizzi/swift_5
Update
2020-02-26 16:58:54 +01:00
Giacomo d77170b2ba Merge pull request #1 from AFathi/swift_5
Update
2020-02-26 16:56:11 +01:00
Giacomo 419c434503 Readme updated 2020-02-26 16:42:59 +01:00
Giacomo d952f66094 Remove not needed files 2020-02-26 16:34:41 +01:00
Giacomo c6b94a485a SwiftPM 2020-02-26 16:26:20 +01:00
Ahmed Bekhit 24d9220fad Merge pull request #104 from GiacomoLeopizzi/weak_delegate
Added weak
2020-02-20 15:39:03 -08:00
Giacomo 62ed6bc6b7 Added weak 2020-02-20 23:40:53 +01:00
Giacomo 187c505359 Merge branch 'view_aspect_ratio' into swift_package_manager 2020-02-13 12:11:48 +02:00
Giacomo 2c1a98a12a Dynamic library 2020-02-13 12:09:05 +02:00
Giacomo 2eef6c5b44 Swift package manager 2020-02-13 11:50:54 +02:00
Ahmed Bekhit cabc5535f0 Merge pull request #100 from GiacomoLeopizzi/view_aspect_ratio
Support for custom aspect ratio
2020-02-09 10:44:13 -08:00
Giacomo e9a88d4a2b Support for custom aspect ratio 2020-02-09 14:21:27 +01:00
Ahmed Bekhit 7cc44e3eae Update README.md 2020-01-10 13:39:31 -05:00
Ahmed Bekhit dcddd6b7c3 Update + validate podspec file 2020-01-10 13:01:26 -05:00
Ahmed Bekhit 8e6f8bd448 Update podspec file 2020-01-10 12:40:55 -05:00
Ahmed Bekhit a56f372be8 Update podspec file 2020-01-10 12:36:22 -05:00
Ahmed Bekhit e01e55fa65 Merge pull request #98 from bartosy-ymija/swift_5
Add SCNTechnique retaining support
2019-11-21 13:28:24 -05:00
Bartosy 3b74f31e22 Ensure nil safety 2019-11-20 20:04:30 +01:00
Bartosy 5d19bc6959 Add technique retaining support 2019-11-20 13:51:01 +01:00
Ahmed Bekhit b87b020996 Merge pull request #95 from maxxfrazer/swift_5
Rebasing with Xcode 11 fixes
2019-09-08 14:33:28 -04:00
Ahmed Bekhit 7acb7a2dfd Update example project Swift version 2019-09-08 15:58:04 +01:00
Saul Moreno Abril 84c501810f Update to Swift 5.0 2019-09-08 15:58:04 +01:00
Ahmed Bekhit f2f64a6580 Merge pull request #94 from wolfcon/fix/xcode-11-error-for-PHLivePhotoPlus
Fix PHLivePhoto in Xcode 11 (iOS 13 SDK) error : `'init()' has been xplicitly marked unavailable here (Photos.PHLivePhoto)`
2019-08-21 08:26:16 -04:00
Frank e388eaa21e Fix PHLivePhoto in Xcode 11 (iOS 13 SDK) error : 'init()' has been explicitly marked unavailable here (Photos.PHLivePhoto) 2019-08-21 17:48:36 +08:00
Ahmed Bekhit 95994f707f Update example project Swift version 2019-06-25 22:24:27 -04:00
Ahmed Bekhit 507ec687c3 Change didCancel protocol to optional 2019-06-25 22:21:49 -04:00
Ahmed Bekhit d79e2d4829 Merge pull request #72 from asam139/feature/cancel_recording
Cancel recording
2019-06-25 22:14:54 -04:00
Ahmed Bekhit 387c864e71 Merge branch 'swift_4_2' into feature/cancel_recording 2019-06-25 22:14:29 -04:00
Ahmed Bekhit c44194230b Update README.md 2019-06-25 22:08:09 -04:00
Ahmed Bekhit 7870134192 Update README.md 2019-06-25 22:07:56 -04:00
Ahmed Bekhit 3cb80cba01 Update README.md 2019-05-13 09:13:59 -04:00
Ahmed Bekhit 658fb4ca07 Update README.md 2019-05-13 09:13:41 -04:00
Ahmed Bekhit 94e3dddfea Merge @asam139 contribution
Fix memory issues and enhance performance
2019-05-04 11:50:00 -04:00
Saul Moreno Abril d3f52c26f4 Fix crash: it checks resumeFrameTime is not nil 2019-05-03 16:10:49 +01:00
Saul Moreno Abril 4dd890a885 Fix memory leak using leak for delegate's WritAR 2019-05-01 15:49:53 +01:00
Saul Moreno Abril a320a51e90 Use weak proxy for CADisplayLink to broke the leak memory 2019-05-01 15:31:52 +01:00
Saul Moreno Abril 1ab12173f4 Move var as private and not global 2019-05-01 15:31:46 +01:00
Saul Moreno Abril a513e4157f Fix leak memory parent view must be weak reference 2019-05-01 15:07:05 +01:00
Saul Moreno Abril 54ad883307 Update to Swift 5.0 2019-03-30 12:27:11 +01:00
Ahmed Bekhit 11b1d4738b Update README.md 2019-03-06 10:38:01 -05:00
AFathi 7a63dbe34a minor bug fixes 2019-03-01 17:22:19 -05:00
Ahmed Bekhit 05ee09b1ce Merge pull request #73 from asam139/feature/current_duration
Add delegate to get progress of duration of current recording
2019-03-01 16:52:44 -05:00
Ahmed Bekhit 30afffd79f Merge pull request #77 from Svrf/fix-bug-when-crash
Fix bug when app crashes after stop video recording
2019-03-01 16:50:21 -05:00
Andrey Evstratenko 3b6e4f60c8 fix bug when app crashes 2019-02-27 15:58:59 +03:00
Saul Moreno Abril 7b5326adcb Add delegate to get progress of duration of current recording 2019-01-15 19:09:26 +01:00
Saul Moreno Abril e6d24ec1b4 Fix grammar 2019-01-15 19:02:59 +01:00
Saul Moreno Abril 5c419c3031 Add method to cancel recording (deleting current url) 2019-01-10 20:51:51 +01:00
Ahmed Bekhit 0653f11cfd Merge pull request #71 from asam139/fix/deprecated_swift_version_file
Deprecated swift_version file
2019-01-06 21:28:38 -05:00
Saul Moreno Abril bc70751aff Remove the ".swift-version" file which is now deprecated and only use the "swift_version" attribute within your podspec. 2019-01-05 19:52:56 +01:00
Ahmed Bekhit ebb2d0a485 Merge pull request #70 from youllbejustfine/swift_4_2
Bypassing pixel buffer attributes - Resolving recording issues that occurred on iPhone X and higher devices
2018-12-17 11:54:41 -05:00
Lukas Steinman 483bf4563b Bypassing pixel buffer attributes to resolve recordings not working on iPhone X/XS/XMax devices. 2018-12-17 12:21:48 -04:00
Ahmed Bekhit 2015b4db0e Update README.md 2018-09-26 23:08:47 -04:00
AFathi 85e846ef25 swift4.2 fixes xcode10 GM 2018-09-25 16:51:07 -04:00
AFathi 8420a3808f fixes for final xcode10 version 2018-09-25 16:26:11 -04:00
AFathi 1fb3a03593 Swift project update to 4.2 2018-09-20 12:08:58 -04:00
AFathi cd0d7f34cf Swift 4.2 Support 2018-09-20 12:02:07 -04:00
AFathi 6a4290e17b swift 4.2 branch 2018-09-20 11:45:09 -04:00
Ahmed Bekhit 3379dbaba4 Update README.md 2018-09-20 06:49:21 -04:00
Ahmed Bekhit 8e7eafb797 Merge pull request #59 from pingchen114/master
Update coding style
2018-08-04 15:36:19 -04:00
spc 62f90d09ef rollback change. 2018-07-29 13:46:42 +09:00
spc f0c0c393a0 Fix typo. 2018-07-29 13:46:31 +09:00
spc c798581992 Update coding style. 2018-07-29 11:53:54 +09:00
AFathi aa625e8a56 fixed issue #57 2018-07-17 12:32:21 -04:00
AFathi 0b09ad4483 fixed an issue that occured when 'onlyRenderWhileRecording' is set to false 2018-07-16 22:39:11 -04:00
AFathi 15124d26cc fixed an issue that occured when 'onlyRenderWhileRecording' is set to false 2018-07-16 22:38:37 -04:00
Ahmed Bekhit 525c802ca4 updated header 2018-07-14 12:34:30 -04:00
AFathi 1b786d4c3d organized directories 2018-07-12 11:13:41 -04:00
AFathi 5d20554db4 ARVideoKit Release Scheme re-added 2018-07-10 23:43:26 -04:00
AFathi f79760470e fixed issue #41 2018-07-10 15:36:57 -04:00
AFathi 469d1fcc70 fixed issue #41 2018-07-10 15:36:30 -04:00
Ahmed Bekhit a7bf5a0894 Update README.md 2018-07-09 13:39:21 -04:00
Ahmed Bekhit d27f999341 Update README.md 2018-07-09 13:38:33 -04:00
AFathi de69cfaaf6 version update 2018-07-09 13:32:29 -04:00
AFathi e6eddcce30 edited project schemes 2018-07-09 13:20:18 -04:00
Ahmed Bekhit eae0dcacd5 Update README.md 2018-07-08 19:56:51 -04:00
Ahmed Bekhit 81f5045f7c Added Cocoapods to the installation section 2018-07-08 17:27:38 -04:00
Ahmed Bekhit 3c3326129e Merge pull request #52 from AFathi/pkg-manager
Cocoapods Support
2018-07-08 17:00:52 -04:00
AFathi 8299d7c0f8 added cocoapods 2018-07-08 16:57:59 -04:00
Ahmed Bekhit d624343a30 Merge pull request #51 from AFathi/master
merge master
2018-07-08 16:28:40 -04:00
AFathi edcadab554 updated examples 2018-07-08 16:26:03 -04:00
AFathi 42613ed528 update 2018-07-08 16:19:23 -04:00
AFathi 6a9e2393c6 pkg-manager 2018-07-08 16:18:34 -04:00
Ahmed Bekhit 5786fffaf7 Merge pull request #50 from xpablocx/master
Add recursive calls to export methods in RecordAR
2018-07-07 13:51:57 -04:00
Pablo Cornejo 87101908c8 Add recursive calls to export methods in RecordAR to remove some duplicate code.
Rename the local authorization status constant in these same methods from 'photos' to 'status' for clarity.
2018-07-07 07:51:18 -04:00
Ahmed Bekhit 31ef66b903 Merge pull request #49 from xpablocx/master
variable typo fix RecordAR instance property: enableAdjustEnvironmentLighting
2018-07-07 02:39:31 -04:00
Pablo Cornejo 67fc993dc4 Fix typo on RecordAR instance property: enableAdjustEnvironmentLighting 2018-07-06 21:37:29 -04:00
AFathi 1e9968c1a3 testing swift pm 2018-06-23 16:21:57 -04:00
AFathi 2cac116d16 update 2018-06-23 15:36:36 -04:00
AFathi cc7019f687 update 2018-06-23 15:35:48 -04:00
Ahmed Bekhit 359e3dcbda Merge pull request #45 from Kelmatou/feature_bitcode_release
Feature bitcode release
2018-06-20 17:35:22 -04:00
Antoine Clop 7ff20805b0 Adding line jump between build instructions 2018-06-19 11:24:59 +02:00
Antoine Clop d94ae2125a Updating release instructions in README.md 2018-06-19 11:22:35 +02:00
Antoine Clop 49ea34ac8b Updating build (only armv architectures are supported by this new debug build) 2018-06-19 11:10:19 +02:00
Antoine Clop dba2398267 Implicitly Unwrapped Optionals is deprecated in Swift 4.2, changing cast to conform to new Swift version 2018-06-19 10:37:57 +02:00
Antoine Clop 71094b27e2 Updating settings for XCode 9.4 2018-06-19 10:34:44 +02:00
Antoine Clop 6505e7346d Updating build settings to provide full bitcode framework on release mode 2018-06-19 10:30:27 +02:00
Ahmed Fathi Bekhit 9894d2a9f8 Update README.md 2018-05-24 13:57:28 -04:00
Ahmed Fathi Bekhit ed3468f487 Update README.md 2018-05-24 13:55:22 -04:00
AFathi f7f98881c3 changed RecordAR.prepare(_:) parameter to optional 2018-05-12 22:58:00 -04:00
AFathi 232dab4f57 changed RecordAR.prepare(_:) parameter to optional 2018-05-12 22:57:21 -04:00
Ahmed Fathi Bekhit 6bb55a923b Update README.md 2018-04-21 00:06:05 -04:00
52 changed files with 971 additions and 619 deletions
+1
View File
@@ -65,3 +65,4 @@ fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
.DS_Store
+22
View File
@@ -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
+39 -22
View File
@@ -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.
+6 -9
View File
@@ -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
+7 -16
View File
@@ -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) || self.nativeBounds.size == CGSize(width: 2436, height: 1125)
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
+1 -1
View File
@@ -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>
+17 -3
View File
@@ -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)
}
+1 -1
View File
@@ -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)
}
+15 -12
View File
@@ -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}
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)
}
}
@@ -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)))
}
}
@@ -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
}
+72 -61
View File
@@ -8,112 +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 let _ = view as? SCNView {
} 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 let _ = renderedFrame {
}else{
renderedFrame = renderEngine.snapshot(atTime: 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};
guard let buffer = renderedFrame!.buffer else { return nil }
return buffer;
}else if let _ = view as? SCNView {
} else if view is SCNView {
let size = 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;
guard let buffer = renderedFrame!.buffer else { return nil }
return buffer
}
return nil;
return nil
}
}
+86 -75
View File
@@ -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) {
+21 -21
View 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,9 +80,9 @@ fileprivate var recentAngle = 0
parentVC = vc
}
@objc init?(SceneKit:SCNView) {
@objc init?(SceneKit: SCNView) {
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")
@@ -96,7 +96,7 @@ fileprivate var recentAngle = 0
parentVC = vc
}
@objc fileprivate func deviceDidRotate() {
@objc private func deviceDidRotate() {
guard var views = parentVC?.view.subviews else {
return
}
@@ -105,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 = []
}
+285 -231
View File
@@ -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,67 +27,100 @@ 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 enableAdjsutEnvironmentLighting:Bool = false {
@objc public var enableAdjustEnvironmentLighting: Bool = false {
didSet{
if (renderEngine != nil) {
renderEngine.autoenablesDefaultLighting = enableAdjsutEnvironmentLighting
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
}
}
}
@@ -117,16 +143,9 @@ 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()
}
/**
@@ -137,48 +156,59 @@ fileprivate var renderer:RenderAR!
view = SceneKit
setup()
}
//MARK: - Deinit
deinit {
gpuLoop.invalidate()
}
//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: - 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: - Internal Objects
fileprivate var scnView:SCNView!
fileprivate var fileCount = 0
//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
internal var parent:UIViewController? {
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 {
} 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]
@@ -193,20 +223,27 @@ fileprivate var renderer:RenderAR!
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
@@ -219,42 +256,34 @@ 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}
} 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
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
}
switch requestMicPermission {
case .auto:
AVAudioSession.sharedInstance().requestRecordPermission({ permitted in
if permitted {
self.micStatus = .enabled
}else{
self.micStatus = .disabled
}
})
default:
break
}
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)
}
@@ -272,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.
@@ -290,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 {
@@ -301,7 +330,7 @@ fileprivate var renderer:RenderAR!
finished?(true, photo!, status, done)
}
}
}else{
} else {
finished?(success, photo!, PHAuthorizationStatus.notDetermined, false)
}
}
@@ -310,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.
@@ -329,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)
}
@@ -356,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
}
}
}
/**
@@ -365,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.
@@ -374,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)
}
}
}
}
}
/**
@@ -396,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) ]")
}
}
/**
@@ -416,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
@@ -433,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.")
@@ -468,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.")
}
@@ -477,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 📲💾.
@@ -492,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)
}
}
}
@@ -539,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
@@ -575,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.
@@ -596,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)
}
}
@@ -662,7 +707,7 @@ fileprivate var renderer:RenderAR!
finished?(permitted)
if permitted {
self.micStatus = .enabled
}else{
} else {
self.micStatus = .disabled
}
})
@@ -678,51 +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 {
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
view.session.run(configuration)
}else if let _ = view as? SCNView {
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
}
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)
@@ -736,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
@@ -759,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 -8
View File
@@ -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 }
}
}
+26
View File
@@ -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
}
}
@@ -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>
@@ -52,7 +52,7 @@
recorder.deleteCacheWhenExported = NO;
// Configure the envronment light rendering.
recorder.enableAdjsutEnvironmentLighting = YES;
recorder.enableAdjustEnvironmentLighting = YES;
}
-(void)viewWillAppear:(BOOL)animated {
@@ -10,7 +10,6 @@
FB2E368C1FAE2A510035B8D6 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = FB2E368B1FAE2A510035B8D6 /* LICENSE */; };
FB36BDB020086BBB00002808 /* ARVideoKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FB36BDAF20086BB000002808 /* ARVideoKit.framework */; };
FB36BDB120086BBB00002808 /* ARVideoKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = FB36BDAF20086BB000002808 /* ARVideoKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
FB3B51A620489CBA000377AE /* overlay_2d_img.png in Resources */ = {isa = PBXBuildFile; fileRef = FB3B51A520489CB9000377AE /* overlay_2d_img.png */; };
FB9B5D2E1FC49E3E005DDD60 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB9B5D2D1FC49E3E005DDD60 /* MainViewController.swift */; };
FBDCC5E71FABDFC600E3184D /* Scene.sks in Resources */ = {isa = PBXBuildFile; fileRef = FBDCC5E51FABDFC500E3184D /* Scene.sks */; };
FBDCC5E81FABDFC600E3184D /* Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBDCC5E61FABDFC500E3184D /* Scene.swift */; };
@@ -57,7 +56,6 @@
/* Begin PBXFileReference section */
FB2E368B1FAE2A510035B8D6 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
FB36BDAA20086BB000002808 /* ARVideoKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ARVideoKit.xcodeproj; path = ../../ARVideoKit.xcodeproj; sourceTree = "<group>"; };
FB3B51A520489CB9000377AE /* overlay_2d_img.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = overlay_2d_img.png; sourceTree = "<group>"; };
FB9B5D2D1FC49E3E005DDD60 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
FBDCC5E51FABDFC500E3184D /* Scene.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = Scene.sks; sourceTree = "<group>"; };
FBDCC5E61FABDFC500E3184D /* Scene.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scene.swift; sourceTree = "<group>"; };
@@ -167,7 +165,6 @@
FBE134B81FAAD3DD00BEC469 /* ARVideoKit-Example */ = {
isa = PBXGroup;
children = (
FB3B51A520489CB9000377AE /* overlay_2d_img.png */,
FBE134B91FAAD3DD00BEC469 /* AppDelegate.swift */,
FBA0AA0D1FAD9E4B006C481B /* View Controllers */,
FBA0AA0C1FAD9E2C006C481B /* Storyboards */,
@@ -259,7 +256,6 @@
FBE134C11FAAD3DD00BEC469 /* Assets.xcassets in Resources */,
FBDCC6401FAC2CD900E3184D /* art.scnassets in Resources */,
FBE134BF1FAAD3DD00BEC469 /* Main.storyboard in Resources */,
FB3B51A620489CBA000377AE /* overlay_2d_img.png in Resources */,
FBDCC5E71FABDFC600E3184D /* Scene.sks in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -422,7 +418,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = P5DZ3XQ9FJ;
DEVELOPMENT_TEAM = LXD85S9DW4;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
@@ -433,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;
@@ -444,7 +440,7 @@
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = P5DZ3XQ9FJ;
DEVELOPMENT_TEAM = LXD85S9DW4;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)",
@@ -455,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;
@@ -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="13771" 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="13772"/>
<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">
@@ -88,10 +82,6 @@
<arscnView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="IMN-9B-haO">
<rect key="frame" x="0.0" y="10" width="375" height="667"/>
</arscnView>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="overlay_2d_img.png" translatesAutoresizingMaskIntoConstraints="NO" id="fyP-vk-9kb">
<rect key="frame" x="0.0" y="10" width="375" height="657"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="AR w/ SceneKit" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="fCR-qg-FqO">
<rect key="frame" x="97" y="20" width="181" height="65"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.65000000000000002" colorSpace="custom" customColorSpace="sRGB"/>
@@ -239,7 +229,6 @@
</view>
<nil key="simulatedTopBarMetrics"/>
<connections>
<outlet property="overlayImg" destination="fyP-vk-9kb" id="VJg-xp-ImM"/>
<outlet property="pauseBtn" destination="pCi-Bx-pwj" id="Vbx-Qf-GiU"/>
<outlet property="recordBtn" destination="1R6-Xa-VsB" id="xFW-bY-b3A"/>
<outlet property="sceneView" destination="IMN-9B-haO" id="6l9-yC-XES"/>
@@ -417,7 +406,4 @@
<point key="canvasLocation" x="53.600000000000001" y="133.5832083958021"/>
</scene>
</scenes>
<resources>
<image name="overlay_2d_img.png" width="1080" height="1920"/>
</resources>
</document>
@@ -13,7 +13,6 @@ import Photos
class SCNViewController: UIViewController, ARSCNViewDelegate, RenderARDelegate, RecordARDelegate {
@IBOutlet weak var overlayImg: UIImageView!
@IBOutlet var sceneView: ARSCNView!
@IBOutlet var recordBtn: UIButton!
@IBOutlet var pauseBtn: UIButton!
@@ -42,6 +41,8 @@ class SCNViewController: UIViewController, ARSCNViewDelegate, RenderARDelegate,
sceneView.autoenablesDefaultLighting = true
// Initialize ARVideoKit recorder
recorder = RecordAR(ARSceneKit: sceneView)
/*----👇---- ARVideoKit Configuration ----👇----*/
@@ -59,7 +60,7 @@ class SCNViewController: UIViewController, ARSCNViewDelegate, RenderARDelegate,
recorder?.contentMode = .aspectFill
//record or photo add environment light rendering, Default is false
recorder?.enableAdjsutEnvironmentLighting = true
recorder?.enableAdjustEnvironmentLighting = true
// Set the UIViewController orientations
recorder?.inputViewOrientations = [.landscapeLeft, .landscapeRight, .portrait]
@@ -114,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) {
@@ -122,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)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 KiB

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;
@@ -468,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;
@@ -608,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.
+23
View File
@@ -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")
]
)
+78 -17
View File
@@ -1,4 +1,15 @@
![intro image](http://www.ahmedbekhit.com/tutorials/ARVideoKit_prev_1.png)
> 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)
![Chemical Wedding](https://www.chemicalwedding.tv/imgs/general/logo-main.png)
## ARVideoKit
![intro image](http://www.ahmedbekhit.com/projects/repo-header-arvideokit.png)
An iOS Framework that enables developers to capture videos 📹, photos 🌄, Live Photos 🎇, and GIFs 🎆 with ARKit content.
@@ -12,11 +23,13 @@ In other words, you **NO LONGER** have to ~screen record~/~screenshot~ to captur
| [Key Features](#key-features) | Lists the key features `ARVideoKit` offers |
| [Compatibility](#compatibility) | Describes the `ARVideoKit` device and iOS compatibality |
| [Example Projects](#example-projects) | Explains how to run the example project provided in this repository |
| [Installation](#installation) | Describes the [Manual](#manual) option to install `ARVideoKit` |
| [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. |
|[![Donate](https://www.paypalobjects.com/webstatic/en_US/i/btn/png/btn_donate_92x26.png)](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&currency_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 a detailed tutorial about implementing `ARVideoKit` with SpriteKit ☺️ |
@@ -26,11 +39,11 @@ In other words, you **NO LONGER** have to ~screen record~/~screenshot~ to captur
| ![SpriteKit Preview](http://www.ahmedbekhit.com/SK_PREV.gif) | ![SceneKit Preview](http://www.ahmedbekhit.com/SCN_PREVIEW.gif)|
## 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
@@ -48,11 +61,34 @@ In other words, you **NO LONGER** have to ~screen record~/~screenshot~ to captur
To try the example project, simply clone this repository and open the `Examples` folder to choose between the Objective-C and Swift project files.
## Installation
### Manual
Drag the `ARVideoKit.framework` file as an embedded binary of your project targets. `ARVideoKit.framework` can be found in the `/Framework Build/` folder of this repository.
![Tutorial](http://www.ahmedbekhit.com/arvideokit_install_new.gif)
### Cocoapods
1. Add this line to your project's `Podfile` (for Swift 5.0)
```
pod 'ARVideoKit', '~> 1.5.51'
Or you may drag `ARVideoKit.xcodeproj` into your project and click the **+** button in the embedded binaries section of your project's target.
```
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 `ARVideoKit.xcodeproj` into your project and click the **+** button in the embedded binaries section of your project's target.
![example embed framework](http://www.ahmedbekhit.com/embeddedBinary.png)
## Implementation
### Swift
@@ -80,44 +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.
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** ❤️
[![Donate](https://www.paypalobjects.com/webstatic/en_US/i/btn/png/btn_donate_92x26.png)](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&currency_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!
Also, feel free to create an issue if you have any suggestions or need any help ☺️
## [License](LICENSE)
Copyright 2017 Ahmed Fathi Bekhit, www.ahmedbekhit.com, me@ahmedbekhit.com
## 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. Youre 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/)
![DERECHOS.jpg](https://cdn.steemitimages.com/DQmVSH7hxt3nuyDxNRdWUBUBtfR11TyvLxM1F1C4vYW5UzG/DERECHOS.jpg)