Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eddae7758c | |||
| 08ad254271 | |||
| 720c1af699 | |||
| e134ab9792 | |||
| e9c7cfaaca | |||
| 2f40c3d9f0 | |||
| de62d34b9e | |||
| fe093e65d6 | |||
| b05e744dc6 | |||
| c51fc0db85 | |||
| 61415aa9aa | |||
| b89bd27d59 | |||
| 6037aa9619 |
@@ -65,4 +65,3 @@ fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
.DS_Store
|
||||
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
echo "4.0" > .swift-version
|
||||
+3
-4
@@ -1,11 +1,11 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "ARVideoKit"
|
||||
s.version = "1.5.51"
|
||||
s.version = "1.3"
|
||||
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" }
|
||||
@@ -16,8 +16,7 @@ Pod::Spec.new do |s|
|
||||
|
||||
s.platform = :ios, "11.0"
|
||||
|
||||
# ARVideoKit for Swift 5.0
|
||||
s.source = { :git => "https://github.com/AFathi/ARVideoKit.git", :tag => "1.5.51" }
|
||||
s.source = { :git => "https://github.com/AFathi/ARVideoKit.git", :tag => "1.3" }
|
||||
s.source_files = "ARVideoKit", "ARVideoKit/**/*.{h,m,swift}"
|
||||
s.resources = "ARVideoKit/Assets/*.scnassets"
|
||||
end
|
||||
|
||||
@@ -7,10 +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 */; };
|
||||
@@ -33,12 +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>"; };
|
||||
@@ -71,14 +71,6 @@
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
B466A8B62279E33800BD7070 /* Utils */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B466A8B72279E34C00BD7070 /* WeakProxy.swift */,
|
||||
);
|
||||
path = Utils;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FBA0AA0A1FAD9D9D006C481B /* Writer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -107,7 +99,7 @@
|
||||
FBD604DC1FA969DD00EC9804 /* ARVideoKit */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B466A8B62279E33800BD7070 /* Utils */,
|
||||
FBD604E51FA96ACD00EC9804 /* Assets */,
|
||||
FBD604E61FA96AD400EC9804 /* Enumerations */,
|
||||
FBD604E71FA96AE500EC9804 /* Extensions */,
|
||||
FBD604E81FA96AEC00EC9804 /* Protocols */,
|
||||
@@ -119,6 +111,14 @@
|
||||
path = ARVideoKit;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FBD604E51FA96ACD00EC9804 /* Assets */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
FBD604EA1FA96B1C00EC9804 /* video.scnassets */,
|
||||
);
|
||||
path = Assets;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
FBD604E61FA96AD400EC9804 /* Enumerations */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -241,7 +241,7 @@
|
||||
TargetAttributes = {
|
||||
FBD604D91FA969DD00EC9804 = {
|
||||
CreatedOnToolsVersion = 9.1;
|
||||
LastSwiftMigration = 1020;
|
||||
LastSwiftMigration = 0910;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
@@ -252,7 +252,6 @@
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
Base,
|
||||
);
|
||||
mainGroup = FBD604D01FA969DD00EC9804;
|
||||
productRefGroup = FBD604DB1FA969DD00EC9804 /* Products */;
|
||||
@@ -270,6 +269,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
FB2E36891FAE29C00035B8D6 /* LICENSE in Resources */,
|
||||
FBD604EB1FA96B1C00EC9804 /* video.scnassets in Resources */,
|
||||
FBD605071FA96B6B00EC9804 /* LoveLiver_LICENSE in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -290,7 +290,6 @@
|
||||
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 */,
|
||||
@@ -451,7 +450,7 @@
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -476,7 +475,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.ahmedbekhit.ARVideoKit;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
Executable
BIN
Binary file not shown.
@@ -5,7 +5,6 @@
|
||||
// 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 {
|
||||
@@ -13,7 +12,6 @@ import Foundation
|
||||
case aspectFit
|
||||
/// Recommended for iPhone X
|
||||
case aspectFill
|
||||
case viewAspectRatio
|
||||
}
|
||||
|
||||
/// Allows specifying the video rendering frame per second `FPS` rate.
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import AVFoundation
|
||||
import Photos
|
||||
import UIKit
|
||||
|
||||
@available(iOS 11.0, *)
|
||||
extension RecordAR {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.32</string>
|
||||
<string>1.31</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
|
||||
@@ -28,25 +28,12 @@ import ARKit
|
||||
- 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)
|
||||
|
||||
/**
|
||||
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)
|
||||
|
||||
/**
|
||||
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.
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import AVFoundation
|
||||
import Photos
|
||||
import UIKit
|
||||
|
||||
@available(iOS 11.0, *)
|
||||
class LivePhotoGenerator {
|
||||
@@ -27,11 +26,11 @@ class LivePhotoGenerator {
|
||||
generator.appliesPreferredTrackTransform = true
|
||||
|
||||
//retrieves the key photo frame from the middle of the video asset
|
||||
let time = NSValue(time: CMTimeMultiplyByFloat64(asset.duration, multiplier: 0.5))
|
||||
|
||||
let time = NSValue(time: CMTimeMultiplyByFloat64(asset.duration, 0.5))
|
||||
|
||||
//generates the key photo CGImage asynchronously
|
||||
generator.generateCGImagesAsynchronously(forTimes: [time], completionHandler: { _, image, _, _, _ in
|
||||
if let cgImg = image, let imgData = UIImage(cgImage: cgImg).pngData() {
|
||||
if let cgImg = image, let imgData = UIImagePNGRepresentation(UIImage(cgImage: cgImg)) {
|
||||
do {
|
||||
self.keyPhotoPath = self.newPath(for: true, and: false)
|
||||
try imgData.write(to: self.keyPhotoPath!, options: [.atomic])
|
||||
|
||||
@@ -16,7 +16,7 @@ class QuickTimeMov {
|
||||
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 let dummyTimeRange = CMTimeRangeMake(CMTimeMake(0, 1000), CMTimeMake(200, 3000))
|
||||
|
||||
private lazy var asset: AVURLAsset = {
|
||||
let url = URL(fileURLWithPath: self.path)
|
||||
@@ -128,7 +128,7 @@ class QuickTimeMov {
|
||||
// --------------------------------------------------
|
||||
writer.startWriting()
|
||||
reader.startReading()
|
||||
writer.startSession(atSourceTime: CMTime.zero)
|
||||
writer.startSession(atSourceTime: kCMTimeZero)
|
||||
|
||||
// write metadata track
|
||||
adapter.append(AVTimedMetadataGroup(items: [metadataForStillImageTime()],
|
||||
@@ -148,7 +148,7 @@ class QuickTimeMov {
|
||||
input.markAsFinished()
|
||||
if reader.status == .completed && aAudioAsset.tracks.count > 1 {
|
||||
audioReader?.startReading()
|
||||
writer.startSession(atSourceTime: CMTime.zero)
|
||||
writer.startSession(atSourceTime: kCMTimeZero)
|
||||
let media_queue = DispatchQueue(label: "assetAudioWriterQueue", attributes: [])
|
||||
audioWriterInput?.requestMediaDataWhenReady(on: media_queue) {
|
||||
while (audioWriterInput?.isReadyForMoreMediaData)! {
|
||||
@@ -218,9 +218,7 @@ class QuickTimeMov {
|
||||
"com.apple.metadata.datatype.int8" ]
|
||||
|
||||
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)
|
||||
CMMetadataFormatDescriptionCreateWithMetadataSpecifications(kCFAllocatorDefault, kCMMetadataFormatType_Boxed, [spec] as CFArray, &desc)
|
||||
let input = AVAssetWriterInput(mediaType: AVMediaType.metadata,
|
||||
outputSettings: nil, sourceFormatHint: desc)
|
||||
return AVAssetWriterInputMetadataAdaptor(assetWriterInput: input)
|
||||
|
||||
@@ -18,7 +18,7 @@ import Photos
|
||||
* [Email](mailto:me@ahmedbekhit.com)
|
||||
*/
|
||||
@available(iOS 9.1, *)
|
||||
@objc public class PHLivePhotoPlus: NSObject {
|
||||
@objc public class PHLivePhotoPlus: PHLivePhoto {
|
||||
var pairedVideoPath: URL?
|
||||
var keyPhotoPath: URL?
|
||||
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
import Foundation
|
||||
import ARKit
|
||||
|
||||
private var view: Any?
|
||||
private var renderEngine: SCNRenderer!
|
||||
|
||||
@available(iOS 11.0, *)
|
||||
struct RenderAR {
|
||||
private var view: Any?
|
||||
private var renderEngine: SCNRenderer!
|
||||
var ARcontentMode: ARFrameMode!
|
||||
|
||||
init(_ ARview: Any?, renderer: SCNRenderer, contentMode: ARFrameMode) {
|
||||
@@ -54,13 +55,6 @@ struct RenderAR {
|
||||
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.isiPhone10 {
|
||||
width = Int(UIScreen.main.nativeBounds.width)
|
||||
@@ -99,7 +93,9 @@ struct RenderAR {
|
||||
} else {
|
||||
renderedFrame = renderEngine.snapshot(atTime: time, with: size, antialiasingMode: .none)
|
||||
}
|
||||
|
||||
guard let buffer = renderedFrame!.buffer else { return nil }
|
||||
|
||||
return buffer
|
||||
} else if view is ARSKView {
|
||||
guard let size = bufferSize else { return nil }
|
||||
|
||||
@@ -25,7 +25,7 @@ class WritAR: NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
|
||||
|
||||
private var isRecording: Bool = false
|
||||
|
||||
weak var delegate: RecordARDelegate?
|
||||
var delegate: RecordARDelegate?
|
||||
var videoInputOrientation: ARVideoOrientation = .auto
|
||||
|
||||
init(output: URL, width: Int, height: Int, adjustForSharing: Bool, audioEnabled: Bool, orientaions:[ARInputViewOrientation], queue: DispatchQueue, allowMix: Bool) {
|
||||
@@ -33,13 +33,13 @@ class WritAR: NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
|
||||
do {
|
||||
assetWriter = try AVAssetWriter(outputURL: output, fileType: AVFileType.mp4)
|
||||
} catch {
|
||||
// FIXME: handle when failed to allocate AVAssetWriter.
|
||||
return
|
||||
fatalError("An error occurred while intializing an AVAssetWriter")
|
||||
}
|
||||
|
||||
if audioEnabled {
|
||||
if allowMix {
|
||||
let audioOptions: AVAudioSession.CategoryOptions = [.mixWithOthers , .allowBluetooth, .defaultToSpeaker, .interruptSpokenAudioAndMixWithOthers]
|
||||
try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, mode: AVAudioSession.Mode.spokenAudio, options: audioOptions)
|
||||
let audioOptions: AVAudioSessionCategoryOptions = [.mixWithOthers , .allowBluetooth, .defaultToSpeaker, .interruptSpokenAudioAndMixWithOthers]
|
||||
try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: audioOptions)
|
||||
try? AVAudioSession.sharedInstance().setActive(true)
|
||||
}
|
||||
AVAudioSession.sharedInstance().requestRecordPermission({ permitted in
|
||||
@@ -57,15 +57,15 @@ class WritAR: NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
|
||||
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: nil)
|
||||
|
||||
|
||||
var angleEnabled: Bool {
|
||||
for v in orientaions {
|
||||
if UIDevice.current.orientation.rawValue == v.rawValue {
|
||||
@@ -141,6 +141,7 @@ class WritAR: NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
|
||||
if session.canAddInput(audioDeviceInput!) {
|
||||
session.addInput(audioDeviceInput!)
|
||||
}
|
||||
|
||||
if session.canAddOutput(audioDataOutput) {
|
||||
session.addOutput(audioDataOutput)
|
||||
}
|
||||
@@ -162,11 +163,34 @@ class WritAR: NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
|
||||
|
||||
var startingVideoTime: CMTime?
|
||||
var isWritingWithoutError: Bool?
|
||||
var currentDuration: TimeInterval = 0 // Seconds
|
||||
|
||||
func insert(pixel buffer: CVPixelBuffer, with intervals: CFTimeInterval) {
|
||||
let time: CMTime = CMTime(seconds: intervals, preferredTimescale: 1000000)
|
||||
insert(pixel: buffer, with: time)
|
||||
let time: CMTime = CMTimeMakeWithSeconds(intervals, 1000000)
|
||||
if assetWriter.status == .unknown {
|
||||
guard startingVideoTime == nil else {
|
||||
isWritingWithoutError = false
|
||||
return
|
||||
}
|
||||
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 time: CMTime) {
|
||||
@@ -178,19 +202,16 @@ class WritAR: NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
|
||||
startingVideoTime = time
|
||||
if assetWriter.startWriting() {
|
||||
assetWriter.startSession(atSourceTime: startingVideoTime!)
|
||||
currentDuration = 0
|
||||
isRecording = true
|
||||
isWritingWithoutError = true
|
||||
} 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 {
|
||||
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
|
||||
@@ -198,10 +219,8 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -219,25 +238,13 @@ class WritAR: NSObject, AVCaptureAudioDataOutputSampleBufferDelegate {
|
||||
isRecording = false
|
||||
}
|
||||
|
||||
func end(writing finished: @escaping () -> Void) {
|
||||
func end(writing finished: @escaping () -> Void){
|
||||
if let session = session {
|
||||
if session.isRunning {
|
||||
session.stopRunning()
|
||||
}
|
||||
}
|
||||
|
||||
if assetWriter.status == .writing {
|
||||
assetWriter.finishWriting(completionHandler: finished)
|
||||
}
|
||||
}
|
||||
|
||||
func cancel() {
|
||||
if let session = session {
|
||||
if session.isRunning {
|
||||
session.stopRunning()
|
||||
}
|
||||
}
|
||||
assetWriter.cancelWriting()
|
||||
assetWriter.finishWriting(completionHandler: finished)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import ARKit
|
||||
*/
|
||||
@available(iOS 11.0, *)
|
||||
@objc public class ARView: NSObject {
|
||||
private weak var parentVC: UIViewController?
|
||||
private var parentVC: UIViewController?
|
||||
private var recentAngle = 0
|
||||
private var inputViewOrientation:[ARInputViewOrientation] = []
|
||||
|
||||
@@ -52,7 +52,7 @@ import ARKit
|
||||
|
||||
@objc init?(ARSceneKit: ARSCNView) {
|
||||
super.init()
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(deviceDidRotate), name: UIDevice.orientationDidChangeNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(deviceDidRotate), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
|
||||
|
||||
let value = UIInterfaceOrientation.portrait.rawValue
|
||||
UIDevice.current.setValue(value, forKey: "orientation")
|
||||
@@ -68,7 +68,7 @@ import ARKit
|
||||
|
||||
@objc init?(ARSpriteKit: ARSKView) {
|
||||
super.init()
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(deviceDidRotate), name: UIDevice.orientationDidChangeNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(deviceDidRotate), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
|
||||
|
||||
let value = UIInterfaceOrientation.portrait.rawValue
|
||||
UIDevice.current.setValue(value, forKey: "orientation")
|
||||
@@ -82,7 +82,7 @@ import ARKit
|
||||
|
||||
@objc init?(SceneKit: SCNView) {
|
||||
super.init()
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(deviceDidRotate), name: UIDevice.orientationDidChangeNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(deviceDidRotate), name: NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
|
||||
|
||||
let value = UIInterfaceOrientation.portrait.rawValue
|
||||
UIDevice.current.setValue(value, forKey: "orientation")
|
||||
|
||||
@@ -12,6 +12,13 @@ import ARKit
|
||||
import Photos
|
||||
import PhotosUI
|
||||
|
||||
private var view: Any?
|
||||
private var renderEngine: SCNRenderer!
|
||||
private var gpuLoop: CADisplayLink!
|
||||
private var isResting = false
|
||||
private var ARcontentMode: ARFrameMode!
|
||||
@available(iOS 11.0, *)
|
||||
private 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 🎆.
|
||||
|
||||
@@ -27,11 +34,11 @@ import PhotosUI
|
||||
/**
|
||||
An object that passes the AR recorder errors and status in the protocol methods.
|
||||
*/
|
||||
@objc weak public var delegate: RecordARDelegate?
|
||||
@objc public weak var delegate: RecordARDelegate?
|
||||
/**
|
||||
An object that passes the AR rendered content in the protocol method.
|
||||
*/
|
||||
@objc weak public var renderAR: RenderARDelegate?
|
||||
@objc public weak var renderAR: RenderARDelegate?
|
||||
/**
|
||||
An object that returns the AR recorder current status.
|
||||
*/
|
||||
@@ -109,21 +116,6 @@ import PhotosUI
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
A boolean that indicates whether render engine should retain SCNTechnique used in the view. Default is `false`.
|
||||
*/
|
||||
@objc public var retainTechnique: Bool = false {
|
||||
didSet {
|
||||
if retainTechnique {
|
||||
guard let techniqueSupportingView = view as? SCNTechniqueSupport else {
|
||||
return
|
||||
}
|
||||
renderEngine?.technique = techniqueSupportingView.technique
|
||||
} else {
|
||||
renderEngine?.technique = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: - Public initialization methods
|
||||
/**
|
||||
@@ -143,9 +135,16 @@ import PhotosUI
|
||||
view = ARSpriteKit
|
||||
scnView = SCNView(frame: UIScreen.main.bounds)
|
||||
|
||||
let scene = SCNScene()
|
||||
scnView.scene = scene
|
||||
setup()
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,11 +155,6 @@ import PhotosUI
|
||||
view = SceneKit
|
||||
setup()
|
||||
}
|
||||
|
||||
//MARK: - Deinit
|
||||
deinit {
|
||||
gpuLoop.invalidate()
|
||||
}
|
||||
|
||||
//MARK: - threads
|
||||
let writerQueue = DispatchQueue(label:"com.ahmedbekhit.WriterQueue")
|
||||
@@ -168,13 +162,6 @@ import PhotosUI
|
||||
let audioSessionQueue = DispatchQueue(label: "com.ahmedbekhit.AudioSessionQueue", attributes: .concurrent)
|
||||
|
||||
//MARK: - Objects
|
||||
private var view: Any?
|
||||
private var renderEngine: SCNRenderer!
|
||||
private var gpuLoop: CADisplayLink!
|
||||
private var isResting = false
|
||||
private var ARcontentMode: ARFrameMode!
|
||||
private var renderer: RenderAR!
|
||||
|
||||
private var scnView: SCNView!
|
||||
private var fileCount = 0
|
||||
|
||||
@@ -233,11 +220,10 @@ import PhotosUI
|
||||
}
|
||||
renderEngine = SCNRenderer(device: mtlDevice, options: nil)
|
||||
renderEngine.scene = view.scene
|
||||
|
||||
gpuLoop = CADisplayLink(target: WeakProxy(target: self),
|
||||
selector: #selector(renderFrame))
|
||||
|
||||
gpuLoop = CADisplayLink(target: self, selector: #selector(renderFrame))
|
||||
gpuLoop.preferredFramesPerSecond = fps.rawValue
|
||||
gpuLoop.add(to: .main, forMode: .common)
|
||||
gpuLoop.add(to: .main, forMode: .commonModes)
|
||||
|
||||
status = .readyToRecord
|
||||
} else if let view = view as? ARSKView {
|
||||
@@ -257,11 +243,10 @@ import PhotosUI
|
||||
|
||||
renderEngine = SCNRenderer(device: mtlDevice, options: nil)
|
||||
renderEngine.scene = scnView.scene
|
||||
|
||||
gpuLoop = CADisplayLink(target: WeakProxy(target: self),
|
||||
selector: #selector(renderFrame))
|
||||
|
||||
gpuLoop = CADisplayLink(target: self, selector: #selector(renderFrame))
|
||||
gpuLoop.preferredFramesPerSecond = fps.rawValue
|
||||
gpuLoop.add(to: .main, forMode: .common)
|
||||
gpuLoop.add(to: .main, forMode: .commonModes)
|
||||
|
||||
status = .readyToRecord
|
||||
} else if let view = view as? SCNView {
|
||||
@@ -271,11 +256,10 @@ import PhotosUI
|
||||
}
|
||||
renderEngine = SCNRenderer(device: mtlDevice, options: nil)
|
||||
renderEngine.scene = view.scene
|
||||
|
||||
gpuLoop = CADisplayLink(target: WeakProxy(target: self),
|
||||
selector: #selector(renderFrame))
|
||||
|
||||
gpuLoop = CADisplayLink(target: self, selector: #selector(renderFrame))
|
||||
gpuLoop.preferredFramesPerSecond = fps.rawValue
|
||||
gpuLoop.add(to: .main, forMode: .common)
|
||||
gpuLoop.add(to: .main, forMode: .commonModes)
|
||||
|
||||
status = .readyToRecord
|
||||
}
|
||||
@@ -284,7 +268,7 @@ import PhotosUI
|
||||
|
||||
renderer = RenderAR(view, renderer: renderEngine, contentMode: contentMode)
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterBackground), name: UIApplication.willResignActiveNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(appWillEnterBackground), name: Notification.Name.UIApplicationWillResignActive, object: nil)
|
||||
}
|
||||
|
||||
|
||||
@@ -529,39 +513,6 @@ import PhotosUI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
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 📲💾.
|
||||
|
||||
@@ -724,7 +675,7 @@ import PhotosUI
|
||||
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 func prepare(_ configuration: ARConfiguration? = nil) {
|
||||
@objc public func prepare(_ configuration: ARConfiguration? = nil) {
|
||||
ARcontentMode = contentMode
|
||||
onlyRenderWhileRec = onlyRenderWhileRecording
|
||||
if let view = view as? ARSCNView {
|
||||
@@ -749,7 +700,7 @@ import PhotosUI
|
||||
|
||||
Recommended to use in the `UIViewController`'s method `func viewWillDisappear(_ animated: Bool)`.
|
||||
*/
|
||||
@objc func rest() {
|
||||
@objc public func rest() {
|
||||
ViewAR.orientation = UIInterfaceOrientationMask(ViewAR.orientations)
|
||||
}
|
||||
}
|
||||
@@ -773,8 +724,7 @@ extension RecordAR {
|
||||
renderer.ARcontentMode = contentMode
|
||||
|
||||
self.writerQueue.sync {
|
||||
|
||||
var time: CMTime { return CMTime(seconds: renderer.time, preferredTimescale: 1000000) }
|
||||
var time: CMTime { return CMTimeMakeWithSeconds(renderer.time, 1000000) }
|
||||
|
||||
self.renderAR?.frame(didRender: buffer, with: time, using: rawBuffer)
|
||||
|
||||
@@ -823,10 +773,8 @@ extension RecordAR {
|
||||
|
||||
self.adjustPausedTime = false
|
||||
|
||||
if self.pausedFrameTime != nil && self.resumeFrameTime != nil {
|
||||
self.pausedFrameTime = self.adjustTime(current: time,
|
||||
resume: self.resumeFrameTime!,
|
||||
pause: self.pausedFrameTime!)
|
||||
if self.pausedFrameTime != nil {
|
||||
self.pausedFrameTime = self.adjustTime(current: time, resume: self.resumeFrameTime!, pause: self.pausedFrameTime!)
|
||||
} else {
|
||||
self.pausedFrameTime = time
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
@@ -429,7 +429,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ahmedbekhit.ARVideoKit-Example";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -451,7 +451,7 @@
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.ahmedbekhit.ARVideoKit-Example";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_VERSION = 5.0;
|
||||
SWIFT_VERSION = 4.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -19,7 +19,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
return ViewAR.orientation
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -115,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: UIApplication.openSettingsURLString) else {
|
||||
guard let settingsUrl = URL(string: UIApplicationOpenSettingsURLString) else {
|
||||
return
|
||||
}
|
||||
if UIApplication.shared.canOpenURL(settingsUrl) {
|
||||
@@ -123,11 +123,11 @@ class SCNViewController: UIViewController, ARSCNViewDelegate, RenderARDelegate,
|
||||
UIApplication.shared.open(settingsUrl, completionHandler: { (success) in
|
||||
})
|
||||
} else {
|
||||
UIApplication.shared.openURL(URL(string:UIApplication.openSettingsURLString)!)
|
||||
UIApplication.shared.openURL(URL(string:UIApplicationOpenSettingsURLString)!)
|
||||
}
|
||||
}
|
||||
}
|
||||
errorView.addAction(UIAlertAction(title: "Later", style: UIAlertAction.Style.default, handler: {
|
||||
errorView.addAction(UIAlertAction(title: "Later", style: UIAlertActionStyle.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: UIApplication.openSettingsURLString) else {
|
||||
guard let settingsUrl = URL(string: UIApplicationOpenSettingsURLString) 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:UIApplication.openSettingsURLString)!)
|
||||
UIApplication.shared.openURL(URL(string:UIApplicationOpenSettingsURLString)!)
|
||||
}
|
||||
}
|
||||
}
|
||||
errorView.addAction(UIAlertAction(title: "Later", style: UIAlertAction.Style.default, handler: {
|
||||
errorView.addAction(UIAlertAction(title: "Later", style: UIAlertActionStyle.default, handler: {
|
||||
(UIAlertAction)in
|
||||
}))
|
||||
errorView.addAction(settingsBtn)
|
||||
|
||||
+2
-17
@@ -1,23 +1,8 @@
|
||||
// swift-tools-version:5.1
|
||||
// swift-tools-version:4.0
|
||||
// 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")
|
||||
]
|
||||
name: "ARVideoKit"
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Swift 4.0 Branch
|
||||
> 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
|
||||
> [swift_4_2](https://github.com/AFathi/ARVideoKit/tree/swift_4_2) branch has been set as the default branch for `ARVideoKit` as of March 6th, 2019.
|
||||
|
||||

|
||||
|
||||
@@ -54,10 +55,9 @@ To try the example project, simply clone this repository and open the `Examples`
|
||||
|
||||
## Installation
|
||||
### Cocoapods
|
||||
1. Add this line to your project's `Podfile` (for Swift 5.0)
|
||||
1. Add this line to your project's `Podfile`
|
||||
```
|
||||
pod 'ARVideoKit', '~> 1.5.51'
|
||||
|
||||
pod 'ARVideoKit'
|
||||
```
|
||||
2. Install the pod
|
||||
```
|
||||
@@ -74,12 +74,14 @@ github "AFathi/ARVideoKit" ~> 1.31
|
||||
$ carthage update
|
||||
```
|
||||
|
||||
### Swift Package Manager (available Xcode 11.2 and forward)
|
||||
### ~Manual~ _Deprecated_
|
||||
**If you're currently using the `.framework` file, I recommend to re-install the framework using one of the other installation options.**
|
||||
|
||||
1. In Xcode, select File > Swift Packages > Add Package Dependency.
|
||||
2. Follow the prompts using the URL for this repository.
|
||||
|
||||
### Manual
|
||||
~Drag the `ARVideoKit.framework` file as an embedded binary of your project targets. `ARVideoKit.framework` can be found in the `/Framework Build/` folder of this repository.~
|
||||

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

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