├── .gitignore ├── .swift-version ├── .travis.yml ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── SceneKitVideoRecorder.podspec ├── SceneKitVideoRecorder ├── Assets │ └── .gitkeep └── Classes │ ├── .gitkeep │ ├── Error.swift │ ├── FileController.swift │ ├── Options.swift │ ├── PixelBufferFactory.swift │ └── SceneKitVideoRecorder.swift ├── SceneKitVideoRecorderDemo ├── Podfile ├── Podfile.lock ├── SceneKitVideoRecorderDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── SceneKitVideoRecorderDemo.xcscheme ├── SceneKitVideoRecorderDemo.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── SceneKitVideoRecorderDemo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-57x57@1x.png │ │ │ ├── Icon-App-57x57@2x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-72x72@1x.png │ │ │ ├── Icon-App-72x72@2x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ ├── Icon-Small-50x50@1x.png │ │ │ ├── Icon-Small-50x50@2x.png │ │ │ └── ItunesArtwork@2x.png │ │ └── Contents.json │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ ├── Info.plist │ ├── ViewController.swift │ └── art.scnassets │ │ ├── ship.scn │ │ └── texture.png └── SceneKitVideoRecorderDemoUnitTests │ ├── Info.plist │ └── SceneKitVideoRecorderDemoUnitTests.swift └── _Pods.xcodeproj /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata/ 15 | *.xccheckout 16 | *.moved-aside 17 | DerivedData 18 | *.hmap 19 | *.ipa 20 | 21 | # Bundler 22 | .bundle 23 | 24 | Carthage 25 | Example/Pods/ 26 | /SceneKitVideoRecorderDemo/Pods 27 | -------------------------------------------------------------------------------- /.swift-version: -------------------------------------------------------------------------------- 1 | 4.2 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # references: 2 | # * http://www.objc.io/issue-6/travis-ci.html 3 | # * https://github.com/supermarin/xcpretty#usage 4 | 5 | os: osx 6 | language: swift 7 | osx_image: xcode10.1 8 | 9 | cache: cocoapods 10 | podfile: Podfile 11 | 12 | before_install: 13 | - gem install cocoapods # Since Travis is not always on latest version 14 | - pod install --project-directory=SceneKitVideoRecorderDemo 15 | 16 | script: 17 | - set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo.xcworkspace -scheme SceneKitVideoRecorderDemo -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 7' ONLY_ACTIVE_ARCH=NO | xcpretty 18 | - pod lib lint 19 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Before submitting an issue please make sure that: 2 | 3 | - [ ] Issue is reproducable in the Demo Project 4 | - [ ] Issue is reproducable with a clean install 5 | - [ ] You are running on a Device and not the Simulator 6 | - [ ] You have searched existing issues (Open and Closed) 7 | - [ ] You are using an appropriate version of XCode and iOS 8 | 9 | Issue environment: 10 | 11 | - Device Model: 12 | - Xcode Version: 13 | - iOS Version: 14 | - Pod Version or Repo Commit: 15 | 16 | Issue Details: 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 okaris 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![SceneKitVideoRecorder](https://i.imgur.com/1f7aXFY.png "SceneKitVideoRecorder") 2 | 3 | # SceneKitVideoRecorder 4 | 5 | [![Version](https://img.shields.io/cocoapods/v/SceneKitVideoRecorder.svg?style=flat)](http://cocoapods.org/pods/SceneKitVideoRecorder) 6 | [![Downloads](https://img.shields.io/cocoapods/dt/SceneKitVideoRecorder.svg?style=flat)](http://cocoapods.org/pods/SceneKitVideoRecorder) 7 | [![License](https://img.shields.io/cocoapods/l/SceneKitVideoRecorder.svg?style=flat)](http://cocoapods.org/pods/SceneKitVideoRecorder) 8 | [![Platform](https://img.shields.io/cocoapods/p/SceneKitVideoRecorder.svg?style=flat)](http://cocoapods.org/pods/SceneKitVideoRecorder) 9 | [![Build Status](https://travis-ci.org/svtek/SceneKitVideoRecorder.svg?branch=master)](https://travis-ci.org/svtek/SceneKitVideoRecorder) 10 | 11 | ## Example 12 | 13 | To run the example project, clone the repo, and run `pod install` from the Example directory first. 14 | 15 | ## Apps using SceneKitVideoRecorder 16 | 17 | [Surreal AR](https://itunes.apple.com/us/app/surreal-ar-augmented-reality/id1286981298?mt=8) 18 | [Arrow](https://itunes.apple.com/app/arrow-ar-texts-emojis/id1296755150?ref=producthunt) 19 | 20 | *Send a PR to add your app here* 21 | 22 | ## Installation 23 | 24 | SceneKitVideoRecorder is available through [CocoaPods](http://cocoapods.org). To install 25 | it, simply add the following line to your Podfile: 26 | 27 | ```ruby 28 | pod 'SceneKitVideoRecorder' 29 | ``` 30 | 31 | 32 | To install Swift 4 branch add the following line to your Podfile: 33 | ```ruby 34 | pod 'SceneKitVideoRecorder', :git => 'https://github.com/svtek/SceneKitVideoRecorder.git', :branch => 'swift4' 35 | 36 | ``` 37 | 38 | ## Usage 39 | 40 | Add `NSMicrophoneUsageDescription` to `info.plist` 41 | 42 | 43 | Add below code to your view controller 44 | 45 | ``` swift 46 | var recorder: SceneKitVideoRecorder? 47 | override func viewDidLayoutSubviews() { 48 | super.viewDidLayoutSubviews() 49 | 50 | if recorder == nil { 51 | var options = SceneKitVideoRecorder.Options.default 52 | 53 | let scale = UIScreen.main.nativeScale 54 | let sceneSize = sceneView.bounds.size 55 | options.videoSize = CGSize(width: sceneSize.width * scale, height: sceneSize.height * scale) 56 | recorder = try! SceneKitVideoRecorder(withARSCNView: sceneView, options: options) 57 | } 58 | } 59 | 60 | @IBAction func startRecording (sender: UIButton) { 61 | self.recorder?.startWriting().onSuccess { 62 | print("Recording Started") 63 | } 64 | } 65 | 66 | @IBAction func stopRecording (sender: UIButton) { 67 | self.recorder?.finishWriting().onSuccess { [weak self] url in 68 | print("Recording Finished", url) 69 | } 70 | } 71 | ``` 72 | 73 | ## Performance tips 74 | 75 | Here is a piece of Apple sample code 76 | 77 | ``` 78 | if let camera = sceneView.pointOfView?.camera { 79 | camera.wantsHDR = true 80 | camera.wantsExposureAdaptation = true 81 | camera.exposureOffset = -1 82 | camera.minimumExposure = -1 83 | } 84 | ``` 85 | The line ```camera.wantsHDR = true``` and ```camera.wantsExposureAdaptation = true``` causes a huge drop in video recording performance. You should remove or disable it for video recording. 86 | 87 | ## Author 88 | | [](http://okaris.com) | [Omer Karisman](http://okaris.com)

Product Manager @ [MojiLaLa](http://mojilala.com)
[![Twitter][1.1]][1] [![Dribble][2.1]][2] [![Github][3.1]][3]| [](https://twitter.com/sahin) | [Sahin Boydas](https://twitter.com/sahin)

Co-Founder @ [MojiLaLa](http://mojilala.com)
[![LinkedIn][4.1]][4]| 89 | | - | :- | - | :- | 90 | 91 | [1.1]: http://i.imgur.com/wWzX9uB.png (twitter icon without padding) 92 | [2.1]: http://i.imgur.com/Vvy3Kru.png (dribbble icon without padding) 93 | [3.1]: http://i.imgur.com/9I6NRUm.png (github icon without padding) 94 | [4.1]: https://www.kingsfund.org.uk/themes/custom/kingsfund/dist/img/svg/sprite-icon-linkedin.svg (linkedin icon) 95 | 96 | [1]: http://www.twitter.com/okarisman 97 | [2]: http://dribbble.com/okaris 98 | [3]: http://www.github.com/okaris 99 | [4]: https://www.linkedin.com/in/sahinboydas 100 | 101 | ## Inspired from 102 | noppefoxwolf, noppelabs@gmail.com 103 | 104 | ## License 105 | 106 | SceneKitVideoRecorder is available under the MIT license. See the LICENSE file for more info. 107 | -------------------------------------------------------------------------------- /SceneKitVideoRecorder.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # Be sure to run `pod spec lint SceneKitVideoRecorder.podspec' to ensure this is a 3 | # valid spec and to remove all comments including this before submitting the spec. 4 | # 5 | # To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html 6 | # To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ 7 | # 8 | 9 | Pod::Spec.new do |s| 10 | 11 | # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 12 | # 13 | # These will help people to find your library, and whilst it 14 | # can feel like a chore to fill in it's definitely to your advantage. The 15 | # summary should be tweet-length, and the description more in depth. 16 | # 17 | 18 | s.name = "SceneKitVideoRecorder" 19 | s.version = "1.6.0" 20 | s.summary = "Record videos of SceneKit and ARKit" 21 | 22 | # This description is used to generate tags and improve search results. 23 | # * Think: What does it do? Why did you write it? What is the focus? 24 | # * Try to keep it short, snappy and to the point. 25 | # * Write the description between the DESC delimiters below. 26 | # * Finally, don't worry about the indent, CocoaPods strips it! 27 | s.description = <<-DESC 28 | SceneKitVideoRecorder records videos of SceneKit and ARKit. 29 | It supports iOS10 or later on Metal supported devices. 30 | DESC 31 | 32 | s.homepage = "https://github.com/svtek/SceneKitVideoRecorder" 33 | # s.screenshots = "www.example.com/screenshots_1.gif", "www.example.com/screenshots_2.gif" 34 | 35 | 36 | # ――― Spec License ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 37 | # 38 | # Licensing your code is important. See http://choosealicense.com for more info. 39 | # CocoaPods will detect a license file if there is a named LICENSE* 40 | # Popular ones are 'MIT', 'BSD' and 'Apache License, Version 2.0'. 41 | # 42 | 43 | s.license = { :type => 'MIT', :file => 'LICENSE' } 44 | # s.license = { :type => "MIT", :file => "FILE_LICENSE" } 45 | 46 | 47 | # ――― Author Metadata ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 48 | # 49 | # Specify the authors of the library, with email addresses. Email addresses 50 | # of the authors are extracted from the SCM log. E.g. $ git log. CocoaPods also 51 | # accepts just a name if you'd rather not provide an email address. 52 | # 53 | # Specify a social_media_url where others can refer to, for example a twitter 54 | # profile URL. 55 | # 56 | 57 | s.author = { "okaris" => "ok@okaris.com" } 58 | # Or just: s.author = "Omer Karisman" 59 | # s.authors = { "Omer Karisman" => "ok@okaris.com" } 60 | s.social_media_url = "http://twitter.com/okarisman" 61 | 62 | # ――― Platform Specifics ――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 63 | # 64 | # If this Pod runs only on iOS or OS X, then specify the platform and 65 | # the deployment target. You can optionally include the target after the platform. 66 | # 67 | 68 | # s.platform = :ios 69 | # s.platform = :ios, "5.0" 70 | 71 | # When using multiple platforms 72 | # s.ios.deployment_target = "5.0" 73 | # s.osx.deployment_target = "10.7" 74 | # s.watchos.deployment_target = "2.0" 75 | # s.tvos.deployment_target = "9.0" 76 | 77 | 78 | # ――― Source Location ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 79 | # 80 | # Specify the location from where the source should be retrieved. 81 | # Supports git, hg, bzr, svn and HTTP. 82 | # 83 | 84 | s.source = { :git => "https://github.com/svtek/SceneKitVideoRecorder.git", :tag => "#{s.version}" } 85 | 86 | 87 | # ――― Source Code ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 88 | # 89 | # CocoaPods is smart about how it includes source code. For source files 90 | # giving a folder will include any swift, h, m, mm, c & cpp files. 91 | # For header files it will include any header in the folder. 92 | # Not including the public_header_files will make all headers public. 93 | # 94 | 95 | s.ios.deployment_target = '10.0' 96 | 97 | s.source_files = "SceneKitVideoRecorder/Classes/**/*" 98 | s.exclude_files = "Classes/Exclude" 99 | 100 | # s.public_header_files = "Classes/**/*.h" 101 | 102 | 103 | # ――― Resources ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 104 | # 105 | # A list of resources included with the Pod. These are copied into the 106 | # target bundle with a build phase script. Anything else will be cleaned. 107 | # You can preserve files from being cleaned, please don't preserve 108 | # non-essential files like tests, examples and documentation. 109 | # 110 | 111 | # s.resource = "icon.png" 112 | # s.resources = "Resources/*.png" 113 | 114 | # s.preserve_paths = "FilesToSave", "MoreFilesToSave" 115 | 116 | 117 | # ――― Project Linking ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 118 | # 119 | # Link your library with frameworks, or libraries. Libraries do not include 120 | # the lib prefix of their name. 121 | # 122 | 123 | s.framework = 'Metal', 'CoreGraphics', 'QuartzCore' 124 | # s.framework = "SomeFramework" 125 | # s.frameworks = "SomeFramework", "AnotherFramework" 126 | 127 | # s.library = "iconv" 128 | # s.libraries = "iconv", "xml2" 129 | 130 | 131 | # ――― Project Settings ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # 132 | # 133 | # If your library depends on compiler flags you can set them in the xcconfig hash 134 | # where they will only apply to your library. If you depend on other Podspecs 135 | # you can include multiple dependencies to ensure it works. 136 | 137 | # s.requires_arc = true 138 | 139 | # s.xcconfig = { "HEADER_SEARCH_PATHS" => "$(SDKROOT)/usr/include/libxml2" } 140 | # s.dependency "JSONKit", "~> 1.4" 141 | s.dependency "BrightFutures" 142 | end 143 | -------------------------------------------------------------------------------- /SceneKitVideoRecorder/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorder/Assets/.gitkeep -------------------------------------------------------------------------------- /SceneKitVideoRecorder/Classes/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorder/Classes/.gitkeep -------------------------------------------------------------------------------- /SceneKitVideoRecorder/Classes/Error.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Error.swift 3 | // 4 | // Created by Omer Karisman on 2017/08/29. 5 | // 6 | 7 | import UIKit 8 | 9 | extension SceneKitVideoRecorder { 10 | public enum ErrorCode: Int { 11 | case notReady = 0 12 | case zeroFrames = 1 13 | case assetExport = 2 14 | case recorderBusy = 3 15 | case unknown = 4 16 | } 17 | } 18 | 19 | -------------------------------------------------------------------------------- /SceneKitVideoRecorder/Classes/FileController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileController.swift 3 | // 4 | // Created by Omer Karisman on 2017/08/29. 5 | // 6 | 7 | import UIKit 8 | 9 | struct FileController { 10 | static func delete(file url: URL) { 11 | let fm = FileManager.default 12 | if fm.fileExists(atPath: url.path) { 13 | try! fm.removeItem(at: url) 14 | } 15 | } 16 | 17 | static func move(from urlFrom: URL, to urlTo: URL) { 18 | let fm = FileManager.default 19 | guard fm.fileExists(atPath: urlFrom.path) else { return } 20 | if fm.fileExists(atPath: urlTo.path) { 21 | try! fm.removeItem(at: urlTo) 22 | } 23 | 24 | try! fm.moveItem(at: urlFrom, to: urlTo) 25 | 26 | if fm.fileExists(atPath: urlFrom.path) { 27 | try! fm.removeItem(at: urlFrom) 28 | } 29 | } 30 | 31 | static func clearTemporaryDirectory(){ 32 | var removed: Int = 0 33 | do { 34 | let tmpDirURL = URL(string: NSTemporaryDirectory())! 35 | var tmpFiles = try FileManager.default.contentsOfDirectory(at: tmpDirURL, includingPropertiesForKeys: nil, options: .skipsHiddenFiles) 36 | tmpFiles = tmpFiles.filter() { $0.absoluteString.contains(".mp4") } 37 | for url in tmpFiles { 38 | removed += 1 39 | try FileManager.default.removeItem(at: url) 40 | } 41 | } catch { 42 | print(error) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /SceneKitVideoRecorder/Classes/Options.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Options.swift 3 | // 4 | // Created by Omer Karisman on 2017/08/29. 5 | // 6 | 7 | import UIKit 8 | import SceneKit 9 | import AVFoundation 10 | 11 | extension SceneKitVideoRecorder { 12 | public struct Options { 13 | public var timeScale: Int32 14 | public var videoSize: CGSize 15 | public var fps: Int 16 | public var outputUrl: URL 17 | public var audioOnlyUrl: URL 18 | public var videoOnlyUrl: URL 19 | public var fileType: AVFileType 20 | public var codec: String 21 | public var deleteFileIfExists: Bool 22 | public var useMicrophone: Bool 23 | public var antialiasingMode: SCNAntialiasingMode 24 | 25 | public static var `default`: Options { 26 | return Options(timeScale: 1000, 27 | videoSize: CGSize(width: 720, height: 1280), 28 | fps: 60, 29 | outputUrl: URL(fileURLWithPath: NSTemporaryDirectory() + "output.mp4"), 30 | audioOnlyUrl: URL(fileURLWithPath: NSTemporaryDirectory() + "audio.m4a"), 31 | videoOnlyUrl: URL(fileURLWithPath: NSTemporaryDirectory() + "video.mp4"), 32 | fileType: AVFileType.m4v, 33 | codec: AVVideoCodecH264, 34 | deleteFileIfExists: true, 35 | useMicrophone: true, 36 | antialiasingMode: .multisampling4X) 37 | } 38 | 39 | var assetWriterVideoInputSettings: [String : Any] { 40 | return [ 41 | AVVideoCodecKey: codec, 42 | AVVideoWidthKey: videoSize.width, 43 | AVVideoHeightKey: videoSize.height 44 | ] 45 | } 46 | 47 | var assetWriterAudioInputSettings: [String : Any] { 48 | return [ 49 | AVFormatIDKey: Int(kAudioFormatMPEG4AAC), 50 | AVSampleRateKey: 12000, 51 | AVNumberOfChannelsKey: 1, 52 | AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue 53 | ] 54 | } 55 | 56 | var sourcePixelBufferAttributes: [String : Any] { 57 | return [ 58 | kCVPixelBufferPixelFormatTypeKey as String: NSNumber(value: kCVPixelFormatType_32ARGB), 59 | kCVPixelBufferWidthKey as String: videoSize.width, 60 | kCVPixelBufferHeightKey as String: videoSize.height, 61 | ] 62 | } 63 | } 64 | } 65 | 66 | -------------------------------------------------------------------------------- /SceneKitVideoRecorder/Classes/PixelBufferFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PixelBufferFactory.swift 3 | // 4 | // Created by Omer Karisman on 2017/08/29. 5 | // 6 | 7 | import UIKit 8 | 9 | struct PixelBufferFactory { 10 | 11 | static let context = CIContext(mtlDevice: MTLCreateSystemDefaultDevice()!) 12 | 13 | static func make(with image: UIImage, usingBuffer pool: CVPixelBufferPool) -> CVPixelBuffer? { 14 | 15 | var pixelBuffer: CVPixelBuffer? 16 | CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pool, &pixelBuffer) 17 | 18 | if let pixelBuffer = pixelBuffer { 19 | CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.init(rawValue: 0)) 20 | let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer) 21 | 22 | let rgbColorSpace = CGColorSpaceCreateDeviceRGB() 23 | let context = CGContext(data: pixelData, width: Int(image.size.width), height: Int(image.size.height), bitsPerComponent: 8, bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer), space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue) 24 | 25 | context?.translateBy(x: 0, y: image.size.height) 26 | context?.scaleBy(x: 1.0, y: -1.0) 27 | 28 | UIGraphicsPushContext(context!) 29 | image.draw(in: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)) 30 | UIGraphicsPopContext() 31 | 32 | CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.init(rawValue: 0)) 33 | return pixelBuffer 34 | } 35 | return nil 36 | } 37 | 38 | static func imageFromCVPixelBuffer(buffer: CVPixelBuffer) -> UIImage { 39 | 40 | let ciimage = CIImage(cvPixelBuffer: buffer) 41 | let cgimgage = context.createCGImage(ciimage, from: CGRect(x: 0, y: 0, width: CVPixelBufferGetWidth(buffer), height: CVPixelBufferGetHeight(buffer))) 42 | 43 | let uiimage = UIImage(cgImage: cgimgage!) 44 | 45 | return uiimage 46 | } 47 | 48 | } 49 | 50 | -------------------------------------------------------------------------------- /SceneKitVideoRecorder/Classes/SceneKitVideoRecorder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneKitVideoRecorder.swift 3 | // 4 | // Created by Omer Karisman on 2017/08/29. 5 | // 6 | 7 | import UIKit 8 | import SceneKit 9 | import ARKit 10 | import AVFoundation 11 | import CoreImage 12 | import BrightFutures 13 | 14 | public class SceneKitVideoRecorder: NSObject, AVAudioRecorderDelegate { 15 | private var writer: AVAssetWriter! 16 | private var videoInput: AVAssetWriterInput! 17 | 18 | var recordingSession: AVAudioSession! 19 | var audioRecorder: AVAudioRecorder! 20 | 21 | private var pixelBufferAdaptor: AVAssetWriterInputPixelBufferAdaptor! 22 | private var options: Options 23 | 24 | private let frameQueue = DispatchQueue(label: "com.svtek.SceneKitVideoRecorder.frameQueue") 25 | private let bufferQueue = DispatchQueue(label: "com.svtek.SceneKitVideoRecorder.bufferQueue", attributes: .concurrent) 26 | private let audioQueue = DispatchQueue(label: "com.svtek.SceneKitVideoRecorder.audioQueue") 27 | 28 | private let errorDomain = "com.svtek.SceneKitVideoRecorder" 29 | 30 | private var displayLink: CADisplayLink? = nil 31 | 32 | private var initialTime: CMTime = CMTime.invalid 33 | private var currentTime: CMTime = CMTime.invalid 34 | 35 | private var sceneView: SCNView 36 | 37 | private var audioSettings: [String : Any]? 38 | 39 | public var isAudioSetup: Bool = false 40 | 41 | private var isPrepared: Bool = false 42 | private var isRecording: Bool = false 43 | 44 | private var useAudio: Bool { 45 | return options.useMicrophone && AVAudioSession.sharedInstance().recordPermission == .granted && isAudioSetup 46 | } 47 | private var videoFramesWritten: Bool = false 48 | private var waitingForPermissions: Bool = false 49 | 50 | private var renderer: SCNRenderer! 51 | 52 | public var updateFrameHandler: ((_ image: UIImage) -> Void)? = nil 53 | private var finishedCompletionHandler: ((_ url: URL) -> Void)? = nil 54 | 55 | @available(iOS 11.0, *) 56 | public convenience init(withARSCNView view: ARSCNView, options: Options = .`default`) throws { 57 | try self.init(scene: view, options: options) 58 | } 59 | 60 | public init(scene: SCNView, options: Options = .`default`, setupAudio: Bool = true) throws { 61 | 62 | self.sceneView = scene 63 | 64 | self.options = options 65 | 66 | self.isRecording = false 67 | self.videoFramesWritten = false 68 | 69 | super.init() 70 | 71 | FileController.clearTemporaryDirectory() 72 | 73 | self.prepare() 74 | 75 | if setupAudio { 76 | self.setupAudio() 77 | } 78 | } 79 | 80 | private func prepare() { 81 | 82 | self.prepare(with: self.options) 83 | isPrepared = true 84 | 85 | } 86 | 87 | private func prepare(with options: Options) { 88 | guard let device = MTLCreateSystemDefaultDevice() else { return } 89 | self.renderer = SCNRenderer(device: device, options: nil) 90 | renderer.scene = self.sceneView.scene 91 | 92 | initialTime = CMTime.invalid 93 | 94 | self.options.videoSize = options.videoSize 95 | 96 | writer = try! AVAssetWriter(outputURL: self.options.videoOnlyUrl, fileType: self.options.fileType) 97 | 98 | self.setupVideo() 99 | } 100 | 101 | @discardableResult public func cleanUp() -> URL { 102 | 103 | var output = options.outputUrl 104 | 105 | if options.deleteFileIfExists { 106 | let nameOnly = (options.outputUrl.lastPathComponent as NSString).deletingPathExtension 107 | let fileExt = (options.outputUrl.lastPathComponent as NSString).pathExtension 108 | let tempFileName = NSTemporaryDirectory() + nameOnly + "TMP." + fileExt 109 | output = URL(fileURLWithPath: tempFileName) 110 | 111 | FileController.move(from: options.outputUrl, to: output) 112 | 113 | FileController.delete(file: self.options.audioOnlyUrl) 114 | FileController.delete(file: self.options.videoOnlyUrl) 115 | } 116 | 117 | return output 118 | } 119 | 120 | public func setupAudio() { 121 | guard self.options.useMicrophone, !self.isAudioSetup else { return } 122 | 123 | recordingSession = AVAudioSession.sharedInstance() 124 | 125 | do { 126 | try recordingSession.setCategory(AVAudioSession.Category.playAndRecord, mode: .default) 127 | try recordingSession.setActive(true) 128 | 129 | recordingSession.requestRecordPermission() { allowed in 130 | DispatchQueue.main.async { 131 | if allowed { 132 | self.isAudioSetup = true 133 | } else { 134 | self.isAudioSetup = false 135 | } 136 | } 137 | } 138 | } catch { 139 | self.isAudioSetup = false 140 | } 141 | } 142 | 143 | private func startRecordingAudio() { 144 | let audioUrl = self.options.audioOnlyUrl 145 | 146 | let settings = self.options.assetWriterAudioInputSettings 147 | 148 | do { 149 | audioRecorder = try AVAudioRecorder(url: audioUrl, settings: settings) 150 | audioRecorder.delegate = self 151 | audioRecorder.record() 152 | 153 | } catch { 154 | finishRecordingAudio(success: false) 155 | } 156 | } 157 | 158 | private func finishRecordingAudio(success: Bool) { 159 | audioRecorder.stop() 160 | audioRecorder = nil 161 | } 162 | 163 | private func setupVideo() { 164 | 165 | self.videoInput = AVAssetWriterInput(mediaType: AVMediaType.video, 166 | outputSettings: self.options.assetWriterVideoInputSettings) 167 | 168 | self.videoInput.mediaTimeScale = self.options.timeScale 169 | 170 | self.pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoInput,sourcePixelBufferAttributes: self.options.sourcePixelBufferAttributes) 171 | 172 | writer.add(videoInput) 173 | } 174 | 175 | public func startWriting() -> Future { 176 | let promise = Promise() 177 | guard !isRecording else { 178 | promise.failure(NSError(domain: errorDomain, code: ErrorCode.recorderBusy.rawValue, userInfo: nil)) 179 | return promise.future 180 | } 181 | isRecording = true 182 | 183 | startDisplayLink() 184 | 185 | guard startInputPipeline() else { 186 | stopDisplayLink() 187 | cleanUp() 188 | promise.failure(NSError(domain: errorDomain, code: ErrorCode.unknown.rawValue, userInfo: nil)) 189 | return promise.future 190 | } 191 | 192 | promise.success(()) 193 | return promise.future 194 | } 195 | 196 | public func finishWriting() -> Future { 197 | 198 | let promise = Promise() 199 | guard isRecording, writer.status == .writing else { 200 | let error = NSError(domain: errorDomain, code: ErrorCode.notReady.rawValue, userInfo: nil) 201 | promise.failure(error) 202 | return promise.future 203 | } 204 | 205 | videoInput.markAsFinished() 206 | 207 | if audioRecorder != nil { 208 | finishRecordingAudio(success: true) 209 | } 210 | 211 | isRecording = false 212 | isPrepared = false 213 | videoFramesWritten = false 214 | 215 | currentTime = CMTime.invalid 216 | 217 | writer.finishWriting { [weak self] in 218 | 219 | guard let this = self else { return } 220 | 221 | this.stopDisplayLink() 222 | 223 | if this.useAudio { 224 | this.mergeVideoAndAudio(videoUrl: this.options.videoOnlyUrl, audioUrl: this.options.audioOnlyUrl).onSuccess { 225 | let outputUrl = this.cleanUp() 226 | promise.success(outputUrl) 227 | } 228 | .onFailure { error in 229 | this.cleanUp() 230 | promise.failure(error) 231 | } 232 | } else { 233 | FileController.move(from: this.options.videoOnlyUrl, to: this.options.outputUrl) 234 | let outputUrl = this.cleanUp() 235 | promise.success(outputUrl) 236 | } 237 | 238 | this.prepare() 239 | } 240 | return promise.future 241 | } 242 | 243 | private func getCurrentCMTime() -> CMTime { 244 | return CMTimeMakeWithSeconds(CACurrentMediaTime(), preferredTimescale: 1000); 245 | } 246 | 247 | private func getAppendTime() -> CMTime { 248 | currentTime = getCurrentCMTime() - initialTime 249 | return currentTime 250 | } 251 | 252 | private func startDisplayLink() { 253 | displayLink = CADisplayLink(target: self, selector: #selector(updateDisplayLink)) 254 | displayLink?.preferredFramesPerSecond = options.fps 255 | displayLink?.add(to: .main, forMode: RunLoop.Mode.common) 256 | } 257 | 258 | @objc private func updateDisplayLink() { 259 | 260 | frameQueue.async { [weak self] in 261 | 262 | if self?.writer.status == .unknown { return } 263 | if self?.writer.status == .failed { return } 264 | guard let input = self?.videoInput, input.isReadyForMoreMediaData else { return } 265 | 266 | self?.renderSnapshot() 267 | } 268 | } 269 | 270 | private func startInputPipeline() -> Bool { 271 | guard writer.status == .unknown else { return false } 272 | guard writer.startWriting() else { return false } 273 | 274 | writer.startSession(atSourceTime: CMTime.zero) 275 | 276 | videoInput.requestMediaDataWhenReady(on: frameQueue, using: {}) 277 | 278 | return true 279 | } 280 | 281 | private func renderSnapshot() { 282 | 283 | autoreleasepool { 284 | 285 | let time = CACurrentMediaTime() 286 | let image = renderer.snapshot(atTime: time, with: self.options.videoSize, antialiasingMode: self.options.antialiasingMode) 287 | 288 | updateFrameHandler?(image) 289 | 290 | guard let pool = self.pixelBufferAdaptor.pixelBufferPool else { print("No pool"); return } 291 | 292 | let pixelBufferTemp = PixelBufferFactory.make(with: image, usingBuffer: pool) 293 | 294 | guard let pixelBuffer = pixelBufferTemp else { print("No buffer"); return } 295 | 296 | guard videoInput.isReadyForMoreMediaData else { print("No ready for media data"); return } 297 | 298 | if videoFramesWritten == false { 299 | videoFramesWritten = true 300 | startRecordingAudio() 301 | initialTime = getCurrentCMTime() 302 | } 303 | 304 | let currentTime = getCurrentCMTime() 305 | 306 | guard CMTIME_IS_VALID(currentTime) else { print("No current time"); return } 307 | 308 | let appendTime = getAppendTime() 309 | 310 | guard CMTIME_IS_VALID(appendTime) else { print("No append time"); return } 311 | 312 | bufferQueue.async { [weak self] in 313 | self?.pixelBufferAdaptor.append(pixelBuffer, withPresentationTime: appendTime) 314 | } 315 | } 316 | } 317 | 318 | private func stopDisplayLink() { 319 | 320 | displayLink?.invalidate() 321 | displayLink = nil 322 | 323 | } 324 | 325 | private func mergeVideoAndAudio(videoUrl:URL, audioUrl:URL) -> Future 326 | { 327 | let promise = Promise() 328 | 329 | let mixComposition : AVMutableComposition = AVMutableComposition() 330 | var mutableCompositionVideoTrack : [AVMutableCompositionTrack] = [] 331 | var mutableCompositionAudioTrack : [AVMutableCompositionTrack] = [] 332 | let totalVideoCompositionInstruction : AVMutableVideoCompositionInstruction = AVMutableVideoCompositionInstruction() 333 | 334 | let aVideoAsset : AVAsset = AVAsset(url: videoUrl) 335 | let aAudioAsset : AVAsset = AVAsset(url: audioUrl) 336 | mutableCompositionVideoTrack.append(mixComposition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)!) 337 | mutableCompositionAudioTrack.append(mixComposition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)!) 338 | 339 | guard !aVideoAsset.tracks.isEmpty, !aAudioAsset.tracks.isEmpty else { 340 | let error = NSError(domain: errorDomain, code: ErrorCode.zeroFrames.rawValue, userInfo: nil) 341 | promise.failure(error) 342 | return promise.future 343 | } 344 | 345 | let aVideoAssetTrack : AVAssetTrack = aVideoAsset.tracks(withMediaType: AVMediaType.video)[0] 346 | let aAudioAssetTrack : AVAssetTrack = aAudioAsset.tracks(withMediaType: AVMediaType.audio)[0] 347 | 348 | do { 349 | try mutableCompositionVideoTrack[0].insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: aVideoAssetTrack.timeRange.duration), of: aVideoAssetTrack, at: CMTime.zero) 350 | try mutableCompositionAudioTrack[0].insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: aVideoAssetTrack.timeRange.duration), of: aAudioAssetTrack, at: CMTime.zero) 351 | } catch { 352 | 353 | } 354 | 355 | totalVideoCompositionInstruction.timeRange = CMTimeRangeMake(start: CMTime.zero,duration: aVideoAssetTrack.timeRange.duration ) 356 | 357 | let mutableVideoComposition : AVMutableVideoComposition = AVMutableVideoComposition() 358 | mutableVideoComposition.frameDuration = CMTimeMake(value: 1, timescale: Int32(self.options.fps)) 359 | 360 | mutableVideoComposition.renderSize = self.options.videoSize 361 | 362 | let savePathUrl : URL = self.options.outputUrl 363 | 364 | let assetExport: AVAssetExportSession = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality)! 365 | assetExport.outputFileType = AVFileType.mp4 366 | assetExport.outputURL = savePathUrl 367 | assetExport.shouldOptimizeForNetworkUse = true 368 | 369 | assetExport.exportAsynchronously { () -> Void in 370 | switch assetExport.status { 371 | 372 | case AVAssetExportSession.Status.completed: 373 | promise.success(()) 374 | case AVAssetExportSession.Status.failed: 375 | let assetExportErrorMessage = "failed \(String(describing: assetExport.error))" 376 | let error = NSError(domain: self.errorDomain, code: ErrorCode.assetExport.rawValue, userInfo: ["Reason": assetExportErrorMessage]) 377 | promise.failure(error) 378 | case AVAssetExportSession.Status.cancelled: 379 | let assetExportErrorMessage = "cancelled \(String(describing: assetExport.error))" 380 | let error = NSError(domain: self.errorDomain, code: ErrorCode.assetExport.rawValue, userInfo: ["Reason": assetExportErrorMessage]) 381 | promise.failure(error) 382 | default: 383 | promise.success(()) 384 | } 385 | } 386 | 387 | return promise.future 388 | } 389 | 390 | } 391 | -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '11.0' 2 | 3 | target 'SceneKitVideoRecorderDemo' do 4 | use_frameworks! 5 | 6 | # Pods for SceneKitVideoRecorderDemo 7 | pod 'SceneKitVideoRecorder', :path => '../' 8 | end 9 | -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - BrightFutures (6.0.0): 3 | - Result (~> 3.2.4) 4 | - Result (3.2.4) 5 | - SceneKitVideoRecorder (1.5.1): 6 | - BrightFutures 7 | 8 | DEPENDENCIES: 9 | - SceneKitVideoRecorder (from `../`) 10 | 11 | SPEC REPOS: 12 | https://github.com/cocoapods/specs.git: 13 | - BrightFutures 14 | - Result 15 | 16 | EXTERNAL SOURCES: 17 | SceneKitVideoRecorder: 18 | :path: "../" 19 | 20 | SPEC CHECKSUMS: 21 | BrightFutures: 9e7604f511aed9f0d6a49b04ac9b3fca6b117758 22 | Result: d2d07204ce72856f1fd9130bbe42c35a7b0fea10 23 | SceneKitVideoRecorder: a730dd5775d87e526080e41ea2d86ac9b799d4d8 24 | 25 | PODFILE CHECKSUM: 021703fdf9e627ae9130f7f3490711d953c90f66 26 | 27 | COCOAPODS: 1.5.3 28 | -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 2C1EA37D1F6281C200E97D5E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1EA37C1F6281C200E97D5E /* AppDelegate.swift */; }; 11 | 2C1EA37F1F6281C200E97D5E /* art.scnassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C1EA37E1F6281C200E97D5E /* art.scnassets */; }; 12 | 2C1EA3811F6281C200E97D5E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C1EA3801F6281C200E97D5E /* ViewController.swift */; }; 13 | 2C1EA3841F6281C200E97D5E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2C1EA3821F6281C200E97D5E /* Main.storyboard */; }; 14 | 2C1EA3861F6281C200E97D5E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2C1EA3851F6281C200E97D5E /* Assets.xcassets */; }; 15 | 2C1EA3891F6281C200E97D5E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2C1EA3871F6281C200E97D5E /* LaunchScreen.storyboard */; }; 16 | 2C3EB95B1F9A580000AA77B4 /* SceneKitVideoRecorderDemoUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C3EB95A1F9A580000AA77B4 /* SceneKitVideoRecorderDemoUnitTests.swift */; }; 17 | EEB00B02F5C6843413C3F432 /* Pods_SceneKitVideoRecorderDemo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9BD48745ADAAAC0D1AE4D17 /* Pods_SceneKitVideoRecorderDemo.framework */; }; 18 | /* End PBXBuildFile section */ 19 | 20 | /* Begin PBXContainerItemProxy section */ 21 | 2C3EB95D1F9A580000AA77B4 /* PBXContainerItemProxy */ = { 22 | isa = PBXContainerItemProxy; 23 | containerPortal = 2C1EA3711F6281C200E97D5E /* Project object */; 24 | proxyType = 1; 25 | remoteGlobalIDString = 2C1EA3781F6281C200E97D5E; 26 | remoteInfo = SceneKitVideoRecorderDemo; 27 | }; 28 | /* End PBXContainerItemProxy section */ 29 | 30 | /* Begin PBXFileReference section */ 31 | 2C1EA3791F6281C200E97D5E /* SceneKitVideoRecorderDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SceneKitVideoRecorderDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 32 | 2C1EA37C1F6281C200E97D5E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33 | 2C1EA37E1F6281C200E97D5E /* art.scnassets */ = {isa = PBXFileReference; lastKnownFileType = wrapper.scnassets; path = art.scnassets; sourceTree = ""; }; 34 | 2C1EA3801F6281C200E97D5E /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 35 | 2C1EA3831F6281C200E97D5E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 36 | 2C1EA3851F6281C200E97D5E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 37 | 2C1EA3881F6281C200E97D5E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 38 | 2C1EA38A1F6281C200E97D5E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | 2C3EB9581F9A580000AA77B4 /* SceneKitVideoRecorderDemoUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SceneKitVideoRecorderDemoUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | 2C3EB95A1F9A580000AA77B4 /* SceneKitVideoRecorderDemoUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneKitVideoRecorderDemoUnitTests.swift; sourceTree = ""; }; 41 | 2C3EB95C1F9A580000AA77B4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | 5637905D9A0E8ABEDCB7C091 /* Pods-SceneKitVideoRecorderDemo.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SceneKitVideoRecorderDemo.release.xcconfig"; path = "Pods/Target Support Files/Pods-SceneKitVideoRecorderDemo/Pods-SceneKitVideoRecorderDemo.release.xcconfig"; sourceTree = ""; }; 43 | EAA3B70BD9F1A1E70DEC8926 /* Pods-SceneKitVideoRecorderDemo.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SceneKitVideoRecorderDemo.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SceneKitVideoRecorderDemo/Pods-SceneKitVideoRecorderDemo.debug.xcconfig"; sourceTree = ""; }; 44 | F9BD48745ADAAAC0D1AE4D17 /* Pods_SceneKitVideoRecorderDemo.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SceneKitVideoRecorderDemo.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 2C1EA3761F6281C200E97D5E /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | EEB00B02F5C6843413C3F432 /* Pods_SceneKitVideoRecorderDemo.framework in Frameworks */, 53 | ); 54 | runOnlyForDeploymentPostprocessing = 0; 55 | }; 56 | 2C3EB9551F9A580000AA77B4 /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | ); 61 | runOnlyForDeploymentPostprocessing = 0; 62 | }; 63 | /* End PBXFrameworksBuildPhase section */ 64 | 65 | /* Begin PBXGroup section */ 66 | 2C1EA3701F6281C200E97D5E = { 67 | isa = PBXGroup; 68 | children = ( 69 | 2C1EA37B1F6281C200E97D5E /* SceneKitVideoRecorderDemo */, 70 | 2C3EB9591F9A580000AA77B4 /* SceneKitVideoRecorderDemoUnitTests */, 71 | 2C1EA37A1F6281C200E97D5E /* Products */, 72 | 7D48497869DC66C1C9741FDD /* Pods */, 73 | C071E4A160A212D4358F643D /* Frameworks */, 74 | ); 75 | sourceTree = ""; 76 | }; 77 | 2C1EA37A1F6281C200E97D5E /* Products */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 2C1EA3791F6281C200E97D5E /* SceneKitVideoRecorderDemo.app */, 81 | 2C3EB9581F9A580000AA77B4 /* SceneKitVideoRecorderDemoUnitTests.xctest */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 2C1EA37B1F6281C200E97D5E /* SceneKitVideoRecorderDemo */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 2C1EA37C1F6281C200E97D5E /* AppDelegate.swift */, 90 | 2C1EA37E1F6281C200E97D5E /* art.scnassets */, 91 | 2C1EA3801F6281C200E97D5E /* ViewController.swift */, 92 | 2C1EA3821F6281C200E97D5E /* Main.storyboard */, 93 | 2C1EA3851F6281C200E97D5E /* Assets.xcassets */, 94 | 2C1EA3871F6281C200E97D5E /* LaunchScreen.storyboard */, 95 | 2C1EA38A1F6281C200E97D5E /* Info.plist */, 96 | ); 97 | path = SceneKitVideoRecorderDemo; 98 | sourceTree = ""; 99 | }; 100 | 2C3EB9591F9A580000AA77B4 /* SceneKitVideoRecorderDemoUnitTests */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 2C3EB95A1F9A580000AA77B4 /* SceneKitVideoRecorderDemoUnitTests.swift */, 104 | 2C3EB95C1F9A580000AA77B4 /* Info.plist */, 105 | ); 106 | path = SceneKitVideoRecorderDemoUnitTests; 107 | sourceTree = ""; 108 | }; 109 | 7D48497869DC66C1C9741FDD /* Pods */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | EAA3B70BD9F1A1E70DEC8926 /* Pods-SceneKitVideoRecorderDemo.debug.xcconfig */, 113 | 5637905D9A0E8ABEDCB7C091 /* Pods-SceneKitVideoRecorderDemo.release.xcconfig */, 114 | ); 115 | name = Pods; 116 | sourceTree = ""; 117 | }; 118 | C071E4A160A212D4358F643D /* Frameworks */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | F9BD48745ADAAAC0D1AE4D17 /* Pods_SceneKitVideoRecorderDemo.framework */, 122 | ); 123 | name = Frameworks; 124 | sourceTree = ""; 125 | }; 126 | /* End PBXGroup section */ 127 | 128 | /* Begin PBXNativeTarget section */ 129 | 2C1EA3781F6281C200E97D5E /* SceneKitVideoRecorderDemo */ = { 130 | isa = PBXNativeTarget; 131 | buildConfigurationList = 2C1EA38D1F6281C200E97D5E /* Build configuration list for PBXNativeTarget "SceneKitVideoRecorderDemo" */; 132 | buildPhases = ( 133 | FC79FC53330B284B1A4714B2 /* [CP] Check Pods Manifest.lock */, 134 | 2C1EA3751F6281C200E97D5E /* Sources */, 135 | 2C1EA3761F6281C200E97D5E /* Frameworks */, 136 | 2C1EA3771F6281C200E97D5E /* Resources */, 137 | 20AD365B3D83A9616714578E /* [CP] Embed Pods Frameworks */, 138 | ); 139 | buildRules = ( 140 | ); 141 | dependencies = ( 142 | ); 143 | name = SceneKitVideoRecorderDemo; 144 | productName = SceneKitVideoRecorderDemo; 145 | productReference = 2C1EA3791F6281C200E97D5E /* SceneKitVideoRecorderDemo.app */; 146 | productType = "com.apple.product-type.application"; 147 | }; 148 | 2C3EB9571F9A580000AA77B4 /* SceneKitVideoRecorderDemoUnitTests */ = { 149 | isa = PBXNativeTarget; 150 | buildConfigurationList = 2C3EB9611F9A580000AA77B4 /* Build configuration list for PBXNativeTarget "SceneKitVideoRecorderDemoUnitTests" */; 151 | buildPhases = ( 152 | 2C3EB9541F9A580000AA77B4 /* Sources */, 153 | 2C3EB9551F9A580000AA77B4 /* Frameworks */, 154 | 2C3EB9561F9A580000AA77B4 /* Resources */, 155 | ); 156 | buildRules = ( 157 | ); 158 | dependencies = ( 159 | 2C3EB95E1F9A580000AA77B4 /* PBXTargetDependency */, 160 | ); 161 | name = SceneKitVideoRecorderDemoUnitTests; 162 | productName = SceneKitVideoRecorderDemoUnitTests; 163 | productReference = 2C3EB9581F9A580000AA77B4 /* SceneKitVideoRecorderDemoUnitTests.xctest */; 164 | productType = "com.apple.product-type.bundle.unit-test"; 165 | }; 166 | /* End PBXNativeTarget section */ 167 | 168 | /* Begin PBXProject section */ 169 | 2C1EA3711F6281C200E97D5E /* Project object */ = { 170 | isa = PBXProject; 171 | attributes = { 172 | LastSwiftUpdateCheck = 0910; 173 | LastUpgradeCheck = 1010; 174 | ORGANIZATIONNAME = okaris; 175 | TargetAttributes = { 176 | 2C1EA3781F6281C200E97D5E = { 177 | CreatedOnToolsVersion = 9.0; 178 | LastSwiftMigration = 0910; 179 | ProvisioningStyle = Automatic; 180 | }; 181 | 2C3EB9571F9A580000AA77B4 = { 182 | CreatedOnToolsVersion = 9.1; 183 | ProvisioningStyle = Automatic; 184 | TestTargetID = 2C1EA3781F6281C200E97D5E; 185 | }; 186 | }; 187 | }; 188 | buildConfigurationList = 2C1EA3741F6281C200E97D5E /* Build configuration list for PBXProject "SceneKitVideoRecorderDemo" */; 189 | compatibilityVersion = "Xcode 8.0"; 190 | developmentRegion = en; 191 | hasScannedForEncodings = 0; 192 | knownRegions = ( 193 | en, 194 | Base, 195 | ); 196 | mainGroup = 2C1EA3701F6281C200E97D5E; 197 | productRefGroup = 2C1EA37A1F6281C200E97D5E /* Products */; 198 | projectDirPath = ""; 199 | projectRoot = ""; 200 | targets = ( 201 | 2C1EA3781F6281C200E97D5E /* SceneKitVideoRecorderDemo */, 202 | 2C3EB9571F9A580000AA77B4 /* SceneKitVideoRecorderDemoUnitTests */, 203 | ); 204 | }; 205 | /* End PBXProject section */ 206 | 207 | /* Begin PBXResourcesBuildPhase section */ 208 | 2C1EA3771F6281C200E97D5E /* Resources */ = { 209 | isa = PBXResourcesBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | 2C1EA37F1F6281C200E97D5E /* art.scnassets in Resources */, 213 | 2C1EA3891F6281C200E97D5E /* LaunchScreen.storyboard in Resources */, 214 | 2C1EA3861F6281C200E97D5E /* Assets.xcassets in Resources */, 215 | 2C1EA3841F6281C200E97D5E /* Main.storyboard in Resources */, 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | 2C3EB9561F9A580000AA77B4 /* Resources */ = { 220 | isa = PBXResourcesBuildPhase; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | ); 224 | runOnlyForDeploymentPostprocessing = 0; 225 | }; 226 | /* End PBXResourcesBuildPhase section */ 227 | 228 | /* Begin PBXShellScriptBuildPhase section */ 229 | 20AD365B3D83A9616714578E /* [CP] Embed Pods Frameworks */ = { 230 | isa = PBXShellScriptBuildPhase; 231 | buildActionMask = 2147483647; 232 | files = ( 233 | ); 234 | inputPaths = ( 235 | "${SRCROOT}/Pods/Target Support Files/Pods-SceneKitVideoRecorderDemo/Pods-SceneKitVideoRecorderDemo-frameworks.sh", 236 | "${BUILT_PRODUCTS_DIR}/BrightFutures/BrightFutures.framework", 237 | "${BUILT_PRODUCTS_DIR}/Result/Result.framework", 238 | "${BUILT_PRODUCTS_DIR}/SceneKitVideoRecorder/SceneKitVideoRecorder.framework", 239 | ); 240 | name = "[CP] Embed Pods Frameworks"; 241 | outputPaths = ( 242 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BrightFutures.framework", 243 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Result.framework", 244 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SceneKitVideoRecorder.framework", 245 | ); 246 | runOnlyForDeploymentPostprocessing = 0; 247 | shellPath = /bin/sh; 248 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SceneKitVideoRecorderDemo/Pods-SceneKitVideoRecorderDemo-frameworks.sh\"\n"; 249 | showEnvVarsInLog = 0; 250 | }; 251 | FC79FC53330B284B1A4714B2 /* [CP] Check Pods Manifest.lock */ = { 252 | isa = PBXShellScriptBuildPhase; 253 | buildActionMask = 2147483647; 254 | files = ( 255 | ); 256 | inputPaths = ( 257 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 258 | "${PODS_ROOT}/Manifest.lock", 259 | ); 260 | name = "[CP] Check Pods Manifest.lock"; 261 | outputPaths = ( 262 | "$(DERIVED_FILE_DIR)/Pods-SceneKitVideoRecorderDemo-checkManifestLockResult.txt", 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | shellPath = /bin/sh; 266 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 267 | showEnvVarsInLog = 0; 268 | }; 269 | /* End PBXShellScriptBuildPhase section */ 270 | 271 | /* Begin PBXSourcesBuildPhase section */ 272 | 2C1EA3751F6281C200E97D5E /* Sources */ = { 273 | isa = PBXSourcesBuildPhase; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | 2C1EA3811F6281C200E97D5E /* ViewController.swift in Sources */, 277 | 2C1EA37D1F6281C200E97D5E /* AppDelegate.swift in Sources */, 278 | ); 279 | runOnlyForDeploymentPostprocessing = 0; 280 | }; 281 | 2C3EB9541F9A580000AA77B4 /* Sources */ = { 282 | isa = PBXSourcesBuildPhase; 283 | buildActionMask = 2147483647; 284 | files = ( 285 | 2C3EB95B1F9A580000AA77B4 /* SceneKitVideoRecorderDemoUnitTests.swift in Sources */, 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | }; 289 | /* End PBXSourcesBuildPhase section */ 290 | 291 | /* Begin PBXTargetDependency section */ 292 | 2C3EB95E1F9A580000AA77B4 /* PBXTargetDependency */ = { 293 | isa = PBXTargetDependency; 294 | target = 2C1EA3781F6281C200E97D5E /* SceneKitVideoRecorderDemo */; 295 | targetProxy = 2C3EB95D1F9A580000AA77B4 /* PBXContainerItemProxy */; 296 | }; 297 | /* End PBXTargetDependency section */ 298 | 299 | /* Begin PBXVariantGroup section */ 300 | 2C1EA3821F6281C200E97D5E /* Main.storyboard */ = { 301 | isa = PBXVariantGroup; 302 | children = ( 303 | 2C1EA3831F6281C200E97D5E /* Base */, 304 | ); 305 | name = Main.storyboard; 306 | sourceTree = ""; 307 | }; 308 | 2C1EA3871F6281C200E97D5E /* LaunchScreen.storyboard */ = { 309 | isa = PBXVariantGroup; 310 | children = ( 311 | 2C1EA3881F6281C200E97D5E /* Base */, 312 | ); 313 | name = LaunchScreen.storyboard; 314 | sourceTree = ""; 315 | }; 316 | /* End PBXVariantGroup section */ 317 | 318 | /* Begin XCBuildConfiguration section */ 319 | 2C1EA38B1F6281C200E97D5E /* Debug */ = { 320 | isa = XCBuildConfiguration; 321 | buildSettings = { 322 | ALWAYS_SEARCH_USER_PATHS = NO; 323 | CLANG_ANALYZER_NONNULL = YES; 324 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 325 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 326 | CLANG_CXX_LIBRARY = "libc++"; 327 | CLANG_ENABLE_MODULES = YES; 328 | CLANG_ENABLE_OBJC_ARC = YES; 329 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 330 | CLANG_WARN_BOOL_CONVERSION = YES; 331 | CLANG_WARN_COMMA = YES; 332 | CLANG_WARN_CONSTANT_CONVERSION = YES; 333 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 334 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 335 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 336 | CLANG_WARN_EMPTY_BODY = YES; 337 | CLANG_WARN_ENUM_CONVERSION = YES; 338 | CLANG_WARN_INFINITE_RECURSION = YES; 339 | CLANG_WARN_INT_CONVERSION = YES; 340 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 341 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 342 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 343 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 344 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 345 | CLANG_WARN_STRICT_PROTOTYPES = YES; 346 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 347 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 348 | CLANG_WARN_UNREACHABLE_CODE = YES; 349 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 350 | CODE_SIGN_IDENTITY = "iPhone Developer"; 351 | COPY_PHASE_STRIP = NO; 352 | DEBUG_INFORMATION_FORMAT = dwarf; 353 | ENABLE_STRICT_OBJC_MSGSEND = YES; 354 | ENABLE_TESTABILITY = YES; 355 | GCC_C_LANGUAGE_STANDARD = gnu11; 356 | GCC_DYNAMIC_NO_PIC = NO; 357 | GCC_NO_COMMON_BLOCKS = YES; 358 | GCC_OPTIMIZATION_LEVEL = 0; 359 | GCC_PREPROCESSOR_DEFINITIONS = ( 360 | "DEBUG=1", 361 | "$(inherited)", 362 | ); 363 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 364 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 365 | GCC_WARN_UNDECLARED_SELECTOR = YES; 366 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 367 | GCC_WARN_UNUSED_FUNCTION = YES; 368 | GCC_WARN_UNUSED_VARIABLE = YES; 369 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 370 | MTL_ENABLE_DEBUG_INFO = YES; 371 | ONLY_ACTIVE_ARCH = YES; 372 | SDKROOT = iphoneos; 373 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 374 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 375 | SWIFT_VERSION = 3.0; 376 | }; 377 | name = Debug; 378 | }; 379 | 2C1EA38C1F6281C200E97D5E /* Release */ = { 380 | isa = XCBuildConfiguration; 381 | buildSettings = { 382 | ALWAYS_SEARCH_USER_PATHS = NO; 383 | CLANG_ANALYZER_NONNULL = YES; 384 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 385 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 386 | CLANG_CXX_LIBRARY = "libc++"; 387 | CLANG_ENABLE_MODULES = YES; 388 | CLANG_ENABLE_OBJC_ARC = YES; 389 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 390 | CLANG_WARN_BOOL_CONVERSION = YES; 391 | CLANG_WARN_COMMA = YES; 392 | CLANG_WARN_CONSTANT_CONVERSION = YES; 393 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 394 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 395 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 396 | CLANG_WARN_EMPTY_BODY = YES; 397 | CLANG_WARN_ENUM_CONVERSION = YES; 398 | CLANG_WARN_INFINITE_RECURSION = YES; 399 | CLANG_WARN_INT_CONVERSION = YES; 400 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 401 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 402 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 403 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 404 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 405 | CLANG_WARN_STRICT_PROTOTYPES = YES; 406 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 407 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 408 | CLANG_WARN_UNREACHABLE_CODE = YES; 409 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 410 | CODE_SIGN_IDENTITY = "iPhone Developer"; 411 | COPY_PHASE_STRIP = NO; 412 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 413 | ENABLE_NS_ASSERTIONS = NO; 414 | ENABLE_STRICT_OBJC_MSGSEND = YES; 415 | GCC_C_LANGUAGE_STANDARD = gnu11; 416 | GCC_NO_COMMON_BLOCKS = YES; 417 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 418 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 419 | GCC_WARN_UNDECLARED_SELECTOR = YES; 420 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 421 | GCC_WARN_UNUSED_FUNCTION = YES; 422 | GCC_WARN_UNUSED_VARIABLE = YES; 423 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 424 | MTL_ENABLE_DEBUG_INFO = NO; 425 | SDKROOT = iphoneos; 426 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 427 | SWIFT_VERSION = 3.0; 428 | VALIDATE_PRODUCT = YES; 429 | }; 430 | name = Release; 431 | }; 432 | 2C1EA38E1F6281C200E97D5E /* Debug */ = { 433 | isa = XCBuildConfiguration; 434 | baseConfigurationReference = EAA3B70BD9F1A1E70DEC8926 /* Pods-SceneKitVideoRecorderDemo.debug.xcconfig */; 435 | buildSettings = { 436 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 437 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 438 | CODE_SIGN_STYLE = Automatic; 439 | DEVELOPMENT_TEAM = B48346ETV3; 440 | INFOPLIST_FILE = SceneKitVideoRecorderDemo/Info.plist; 441 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 442 | PRODUCT_BUNDLE_IDENTIFIER = com.okaris.SceneKitVideoRecorderDemo; 443 | PRODUCT_NAME = "$(TARGET_NAME)"; 444 | PROVISIONING_PROFILE_SPECIFIER = ""; 445 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 446 | SWIFT_VERSION = 4.2; 447 | TARGETED_DEVICE_FAMILY = "1,2"; 448 | }; 449 | name = Debug; 450 | }; 451 | 2C1EA38F1F6281C200E97D5E /* Release */ = { 452 | isa = XCBuildConfiguration; 453 | baseConfigurationReference = 5637905D9A0E8ABEDCB7C091 /* Pods-SceneKitVideoRecorderDemo.release.xcconfig */; 454 | buildSettings = { 455 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 456 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 457 | CODE_SIGN_STYLE = Automatic; 458 | DEVELOPMENT_TEAM = B48346ETV3; 459 | INFOPLIST_FILE = SceneKitVideoRecorderDemo/Info.plist; 460 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 461 | PRODUCT_BUNDLE_IDENTIFIER = com.okaris.SceneKitVideoRecorderDemo; 462 | PRODUCT_NAME = "$(TARGET_NAME)"; 463 | PROVISIONING_PROFILE_SPECIFIER = ""; 464 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 465 | SWIFT_VERSION = 4.2; 466 | TARGETED_DEVICE_FAMILY = "1,2"; 467 | }; 468 | name = Release; 469 | }; 470 | 2C3EB95F1F9A580000AA77B4 /* Debug */ = { 471 | isa = XCBuildConfiguration; 472 | buildSettings = { 473 | BUNDLE_LOADER = "$(TEST_HOST)"; 474 | CODE_SIGN_STYLE = Automatic; 475 | DEVELOPMENT_TEAM = B48346ETV3; 476 | INFOPLIST_FILE = SceneKitVideoRecorderDemoUnitTests/Info.plist; 477 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 478 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 479 | PRODUCT_BUNDLE_IDENTIFIER = com.okaris.SceneKitVideoRecorderDemoUnitTests; 480 | PRODUCT_NAME = "$(TARGET_NAME)"; 481 | SWIFT_VERSION = 4.2; 482 | TARGETED_DEVICE_FAMILY = "1,2"; 483 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SceneKitVideoRecorderDemo.app/SceneKitVideoRecorderDemo"; 484 | }; 485 | name = Debug; 486 | }; 487 | 2C3EB9601F9A580000AA77B4 /* Release */ = { 488 | isa = XCBuildConfiguration; 489 | buildSettings = { 490 | BUNDLE_LOADER = "$(TEST_HOST)"; 491 | CODE_SIGN_STYLE = Automatic; 492 | DEVELOPMENT_TEAM = B48346ETV3; 493 | INFOPLIST_FILE = SceneKitVideoRecorderDemoUnitTests/Info.plist; 494 | IPHONEOS_DEPLOYMENT_TARGET = 11.1; 495 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 496 | PRODUCT_BUNDLE_IDENTIFIER = com.okaris.SceneKitVideoRecorderDemoUnitTests; 497 | PRODUCT_NAME = "$(TARGET_NAME)"; 498 | SWIFT_VERSION = 4.2; 499 | TARGETED_DEVICE_FAMILY = "1,2"; 500 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SceneKitVideoRecorderDemo.app/SceneKitVideoRecorderDemo"; 501 | }; 502 | name = Release; 503 | }; 504 | /* End XCBuildConfiguration section */ 505 | 506 | /* Begin XCConfigurationList section */ 507 | 2C1EA3741F6281C200E97D5E /* Build configuration list for PBXProject "SceneKitVideoRecorderDemo" */ = { 508 | isa = XCConfigurationList; 509 | buildConfigurations = ( 510 | 2C1EA38B1F6281C200E97D5E /* Debug */, 511 | 2C1EA38C1F6281C200E97D5E /* Release */, 512 | ); 513 | defaultConfigurationIsVisible = 0; 514 | defaultConfigurationName = Release; 515 | }; 516 | 2C1EA38D1F6281C200E97D5E /* Build configuration list for PBXNativeTarget "SceneKitVideoRecorderDemo" */ = { 517 | isa = XCConfigurationList; 518 | buildConfigurations = ( 519 | 2C1EA38E1F6281C200E97D5E /* Debug */, 520 | 2C1EA38F1F6281C200E97D5E /* Release */, 521 | ); 522 | defaultConfigurationIsVisible = 0; 523 | defaultConfigurationName = Release; 524 | }; 525 | 2C3EB9611F9A580000AA77B4 /* Build configuration list for PBXNativeTarget "SceneKitVideoRecorderDemoUnitTests" */ = { 526 | isa = XCConfigurationList; 527 | buildConfigurations = ( 528 | 2C3EB95F1F9A580000AA77B4 /* Debug */, 529 | 2C3EB9601F9A580000AA77B4 /* Release */, 530 | ); 531 | defaultConfigurationIsVisible = 0; 532 | defaultConfigurationName = Release; 533 | }; 534 | /* End XCConfigurationList section */ 535 | }; 536 | rootObject = 2C1EA3711F6281C200E97D5E /* Project object */; 537 | } 538 | -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo.xcodeproj/xcshareddata/xcschemes/SceneKitVideoRecorderDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SceneKitVideoRecorderDemo 4 | // 5 | // Created by Okaris 2017 on 08/09/2017. 6 | // Copyright © 2017 okaris. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "57x57", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-57x57@1x.png", 49 | "scale" : "1x" 50 | }, 51 | { 52 | "size" : "57x57", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-57x57@2x.png", 55 | "scale" : "2x" 56 | }, 57 | { 58 | "size" : "60x60", 59 | "idiom" : "iphone", 60 | "filename" : "Icon-App-60x60@2x.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "60x60", 65 | "idiom" : "iphone", 66 | "filename" : "Icon-App-60x60@3x.png", 67 | "scale" : "3x" 68 | }, 69 | { 70 | "size" : "20x20", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-20x20@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "20x20", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-20x20@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "29x29", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-29x29@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "29x29", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-29x29@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "40x40", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-40x40@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "40x40", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-40x40@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "50x50", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-Small-50x50@1x.png", 109 | "scale" : "1x" 110 | }, 111 | { 112 | "size" : "50x50", 113 | "idiom" : "ipad", 114 | "filename" : "Icon-Small-50x50@2x.png", 115 | "scale" : "2x" 116 | }, 117 | { 118 | "size" : "72x72", 119 | "idiom" : "ipad", 120 | "filename" : "Icon-App-72x72@1x.png", 121 | "scale" : "1x" 122 | }, 123 | { 124 | "size" : "72x72", 125 | "idiom" : "ipad", 126 | "filename" : "Icon-App-72x72@2x.png", 127 | "scale" : "2x" 128 | }, 129 | { 130 | "size" : "76x76", 131 | "idiom" : "ipad", 132 | "filename" : "Icon-App-76x76@1x.png", 133 | "scale" : "1x" 134 | }, 135 | { 136 | "size" : "76x76", 137 | "idiom" : "ipad", 138 | "filename" : "Icon-App-76x76@2x.png", 139 | "scale" : "2x" 140 | }, 141 | { 142 | "size" : "83.5x83.5", 143 | "idiom" : "ipad", 144 | "filename" : "Icon-App-83.5x83.5@2x.png", 145 | "scale" : "2x" 146 | }, 147 | { 148 | "size" : "1024x1024", 149 | "idiom" : "ios-marketing", 150 | "filename" : "ItunesArtwork@2x.png", 151 | "scale" : "1x" 152 | } 153 | ], 154 | "info" : { 155 | "version" : 1, 156 | "author" : "xcode" 157 | } 158 | } -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@1x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/Icon-Small-50x50@2x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSCameraUsageDescription 24 | This application will use the camera for Augmented Reality. 25 | NSMicrophoneUsageDescription 26 | Record audio with video 27 | NSPhotoLibraryUsageDescription 28 | Save recorded video. 29 | UILaunchStoryboardName 30 | LaunchScreen 31 | UIMainStoryboardFile 32 | Main 33 | UIRequiredDeviceCapabilities 34 | 35 | armv7 36 | arkit 37 | 38 | UIStatusBarHidden 39 | 40 | UISupportedInterfaceOrientations 41 | 42 | UIInterfaceOrientationPortrait 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UISupportedInterfaceOrientations~ipad 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationPortraitUpsideDown 50 | UIInterfaceOrientationLandscapeLeft 51 | UIInterfaceOrientationLandscapeRight 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // SceneKitVideoRecorderDemo 4 | // 5 | // Created by Okaris 2017 on 08/09/2017. 6 | // Copyright © 2017 okaris. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Photos 11 | import SceneKit 12 | import ARKit 13 | import SceneKitVideoRecorder 14 | 15 | class ViewController: UIViewController, ARSCNViewDelegate { 16 | 17 | @IBOutlet var sceneView: ARSCNView! 18 | 19 | var recorder: SceneKitVideoRecorder? 20 | 21 | override func viewDidLoad() { 22 | super.viewDidLoad() 23 | 24 | // Set the view's delegate 25 | sceneView.delegate = self 26 | 27 | // Show statistics such as fps and timing information 28 | sceneView.showsStatistics = true 29 | 30 | // Create a new scene 31 | let scene = SCNScene(named: "art.scnassets/ship.scn")! 32 | 33 | // Set the scene to the view 34 | sceneView.scene = scene 35 | 36 | recorder = try! SceneKitVideoRecorder(withARSCNView: sceneView) 37 | } 38 | 39 | override func viewWillAppear(_ animated: Bool) { 40 | super.viewWillAppear(animated) 41 | 42 | // Create a session configuration 43 | let configuration = ARWorldTrackingConfiguration() 44 | 45 | // Run the view's session 46 | sceneView.session.run(configuration) 47 | } 48 | 49 | @IBAction func startRecording (sender: UIButton) { 50 | sender.backgroundColor = .red 51 | _ = self.recorder?.startWriting() 52 | } 53 | 54 | @IBAction func stopRecording (sender: UIButton) { 55 | sender.backgroundColor = .white 56 | self.recorder?.finishWriting().onSuccess { [weak self] url in 57 | print("Recording Finished", url) 58 | self?.checkAuthorizationAndPresentActivityController(toShare: url, using: self!) 59 | } 60 | } 61 | 62 | private func checkAuthorizationAndPresentActivityController(toShare data: Any, using presenter: UIViewController) { 63 | switch PHPhotoLibrary.authorizationStatus() { 64 | case .authorized: 65 | let activityViewController = UIActivityViewController(activityItems: [data], applicationActivities: nil) 66 | activityViewController.excludedActivityTypes = [UIActivity.ActivityType.addToReadingList, UIActivity.ActivityType.openInIBooks, UIActivity.ActivityType.print] 67 | presenter.present(activityViewController, animated: true, completion: nil) 68 | case .restricted, .denied: 69 | let libraryRestrictedAlert = UIAlertController(title: "Photos access denied", 70 | message: "Please enable Photos access for this application in Settings > Privacy to allow saving screenshots.", 71 | preferredStyle: UIAlertController.Style.alert) 72 | libraryRestrictedAlert.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil)) 73 | presenter.present(libraryRestrictedAlert, animated: true, completion: nil) 74 | case .notDetermined: 75 | PHPhotoLibrary.requestAuthorization({ (authorizationStatus) in 76 | if authorizationStatus == .authorized { 77 | let activityViewController = UIActivityViewController(activityItems: [data], applicationActivities: nil) 78 | activityViewController.excludedActivityTypes = [UIActivity.ActivityType.addToReadingList, UIActivity.ActivityType.openInIBooks, UIActivity.ActivityType.print] 79 | presenter.present(activityViewController, animated: true, completion: nil) 80 | } 81 | }) 82 | } 83 | } 84 | 85 | override func viewWillDisappear(_ animated: Bool) { 86 | super.viewWillDisappear(animated) 87 | 88 | // Pause the view's session 89 | sceneView.session.pause() 90 | } 91 | 92 | override func didReceiveMemoryWarning() { 93 | super.didReceiveMemoryWarning() 94 | // Release any cached data, images, etc that aren't in use. 95 | } 96 | 97 | // MARK: - ARSCNViewDelegate 98 | 99 | /* 100 | // Override to create and configure nodes for anchors added to the view's session. 101 | func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? { 102 | let node = SCNNode() 103 | 104 | return node 105 | } 106 | */ 107 | 108 | func session(_ session: ARSession, didFailWithError error: Error) { 109 | // Present an error message to the user 110 | 111 | } 112 | 113 | func sessionWasInterrupted(_ session: ARSession) { 114 | // Inform the user that the session has been interrupted, for example, by presenting an overlay 115 | 116 | } 117 | 118 | func sessionInterruptionEnded(_ session: ARSession) { 119 | // Reset tracking and/or remove existing anchors if consistent tracking is required 120 | 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/art.scnassets/ship.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/art.scnassets/ship.scn -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/art.scnassets/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svhawks/SceneKitVideoRecorder/1bbd5dda822e1170b45fa789ac0f2f68eace56d0/SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemo/art.scnassets/texture.png -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemoUnitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SceneKitVideoRecorderDemo/SceneKitVideoRecorderDemoUnitTests/SceneKitVideoRecorderDemoUnitTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneKitVideoRecorderDemoUnitTests.swift 3 | // SceneKitVideoRecorderDemoUnitTests 4 | // 5 | // Created by Okaris 2017 on 20/10/2017. 6 | // Copyright © 2017 okaris. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class SceneKitVideoRecorderDemoUnitTests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | super.tearDown() 21 | } 22 | 23 | func testExample() { 24 | // This is an example of a functional test case. 25 | // Use XCTAssert and related functions to verify your tests produce the correct results. 26 | } 27 | 28 | func testPerformanceExample() { 29 | // This is an example of a performance test case. 30 | self.measure { 31 | // Put the code you want to measure the time of here. 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /_Pods.xcodeproj: -------------------------------------------------------------------------------- 1 | Example/Pods/Pods.xcodeproj --------------------------------------------------------------------------------