Showing
15 changed files
with
713 additions
and
1 deletions
No preview for this file type
HapticCam /.DS_Store
0 → 100644
No preview for this file type
HapticCam /AppDelegate.swift
0 → 100644
1 | +// | ||
2 | +// AppDelegate.swift | ||
3 | +// HapticCam | ||
4 | +// | ||
5 | +// Created by 박진형 on 2020/12/15. | ||
6 | +// | ||
7 | + | ||
8 | +import UIKit | ||
9 | + | ||
10 | +@main | ||
11 | +class AppDelegate: UIResponder, UIApplicationDelegate { | ||
12 | + | ||
13 | + | ||
14 | + | ||
15 | + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { | ||
16 | + // Override point for customization after application launch. | ||
17 | + return true | ||
18 | + } | ||
19 | + | ||
20 | + // MARK: UISceneSession Lifecycle | ||
21 | + | ||
22 | + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { | ||
23 | + // Called when a new scene session is being created. | ||
24 | + // Use this method to select a configuration to create the new scene with. | ||
25 | + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) | ||
26 | + } | ||
27 | + | ||
28 | + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) { | ||
29 | + // Called when the user discards a scene session. | ||
30 | + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. | ||
31 | + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. | ||
32 | + } | ||
33 | + | ||
34 | + | ||
35 | +} | ||
36 | + |
HapticCam /Assets.xcassets/.DS_Store
0 → 100644
No preview for this file type
1 | +{ | ||
2 | + "images" : [ | ||
3 | + { | ||
4 | + "idiom" : "iphone", | ||
5 | + "scale" : "2x", | ||
6 | + "size" : "20x20" | ||
7 | + }, | ||
8 | + { | ||
9 | + "idiom" : "iphone", | ||
10 | + "scale" : "3x", | ||
11 | + "size" : "20x20" | ||
12 | + }, | ||
13 | + { | ||
14 | + "idiom" : "iphone", | ||
15 | + "scale" : "2x", | ||
16 | + "size" : "29x29" | ||
17 | + }, | ||
18 | + { | ||
19 | + "idiom" : "iphone", | ||
20 | + "scale" : "3x", | ||
21 | + "size" : "29x29" | ||
22 | + }, | ||
23 | + { | ||
24 | + "idiom" : "iphone", | ||
25 | + "scale" : "2x", | ||
26 | + "size" : "40x40" | ||
27 | + }, | ||
28 | + { | ||
29 | + "idiom" : "iphone", | ||
30 | + "scale" : "3x", | ||
31 | + "size" : "40x40" | ||
32 | + }, | ||
33 | + { | ||
34 | + "idiom" : "iphone", | ||
35 | + "scale" : "2x", | ||
36 | + "size" : "60x60" | ||
37 | + }, | ||
38 | + { | ||
39 | + "idiom" : "iphone", | ||
40 | + "scale" : "3x", | ||
41 | + "size" : "60x60" | ||
42 | + }, | ||
43 | + { | ||
44 | + "idiom" : "ipad", | ||
45 | + "scale" : "1x", | ||
46 | + "size" : "20x20" | ||
47 | + }, | ||
48 | + { | ||
49 | + "idiom" : "ipad", | ||
50 | + "scale" : "2x", | ||
51 | + "size" : "20x20" | ||
52 | + }, | ||
53 | + { | ||
54 | + "idiom" : "ipad", | ||
55 | + "scale" : "1x", | ||
56 | + "size" : "29x29" | ||
57 | + }, | ||
58 | + { | ||
59 | + "idiom" : "ipad", | ||
60 | + "scale" : "2x", | ||
61 | + "size" : "29x29" | ||
62 | + }, | ||
63 | + { | ||
64 | + "idiom" : "ipad", | ||
65 | + "scale" : "1x", | ||
66 | + "size" : "40x40" | ||
67 | + }, | ||
68 | + { | ||
69 | + "idiom" : "ipad", | ||
70 | + "scale" : "2x", | ||
71 | + "size" : "40x40" | ||
72 | + }, | ||
73 | + { | ||
74 | + "idiom" : "ipad", | ||
75 | + "scale" : "1x", | ||
76 | + "size" : "76x76" | ||
77 | + }, | ||
78 | + { | ||
79 | + "idiom" : "ipad", | ||
80 | + "scale" : "2x", | ||
81 | + "size" : "76x76" | ||
82 | + }, | ||
83 | + { | ||
84 | + "idiom" : "ipad", | ||
85 | + "scale" : "2x", | ||
86 | + "size" : "83.5x83.5" | ||
87 | + }, | ||
88 | + { | ||
89 | + "idiom" : "ios-marketing", | ||
90 | + "scale" : "1x", | ||
91 | + "size" : "1024x1024" | ||
92 | + } | ||
93 | + ], | ||
94 | + "info" : { | ||
95 | + "author" : "xcode", | ||
96 | + "version" : 1 | ||
97 | + } | ||
98 | +} |
HapticCam /Assets.xcassets/Contents.json
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8" standalone="no"?> | ||
2 | +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM"> | ||
3 | + <dependencies> | ||
4 | + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/> | ||
5 | + <capability name="Safe area layout guides" minToolsVersion="9.0"/> | ||
6 | + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||
7 | + </dependencies> | ||
8 | + <scenes> | ||
9 | + <!--View Controller--> | ||
10 | + <scene sceneID="EHf-IW-A2E"> | ||
11 | + <objects> | ||
12 | + <viewController id="01J-lp-oVM" sceneMemberID="viewController"> | ||
13 | + <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3"> | ||
14 | + <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> | ||
15 | + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||
16 | + <color key="backgroundColor" xcode11CocoaTouchSystemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/> | ||
17 | + <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/> | ||
18 | + </view> | ||
19 | + </viewController> | ||
20 | + <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/> | ||
21 | + </objects> | ||
22 | + <point key="canvasLocation" x="53" y="375"/> | ||
23 | + </scene> | ||
24 | + </scenes> | ||
25 | +</document> |
HapticCam /Base.lproj/Main.storyboard
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="17506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r"> | ||
3 | + <device id="retina5_5" orientation="portrait" appearance="light"/> | ||
4 | + <dependencies> | ||
5 | + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17505"/> | ||
6 | + <capability name="Safe area layout guides" minToolsVersion="9.0"/> | ||
7 | + <capability name="System colors in document resources" minToolsVersion="11.0"/> | ||
8 | + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> | ||
9 | + </dependencies> | ||
10 | + <scenes> | ||
11 | + <!--View Controller--> | ||
12 | + <scene sceneID="tne-QT-ifu"> | ||
13 | + <objects> | ||
14 | + <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="HapticCam" customModuleProvider="target" sceneMemberID="viewController"> | ||
15 | + <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC"> | ||
16 | + <rect key="frame" x="0.0" y="0.0" width="414" height="736"/> | ||
17 | + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> | ||
18 | + <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/> | ||
19 | + <color key="backgroundColor" systemColor="systemBackgroundColor"/> | ||
20 | + </view> | ||
21 | + </viewController> | ||
22 | + <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> | ||
23 | + </objects> | ||
24 | + <point key="canvasLocation" x="-210" y="80"/> | ||
25 | + </scene> | ||
26 | + </scenes> | ||
27 | + <resources> | ||
28 | + <systemColor name="systemBackgroundColor"> | ||
29 | + <color white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> | ||
30 | + </systemColor> | ||
31 | + </resources> | ||
32 | +</document> |
HapticCam /Info.plist
0 → 100644
1 | +<?xml version="1.0" encoding="UTF-8"?> | ||
2 | +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
3 | +<plist version="1.0"> | ||
4 | +<dict> | ||
5 | + <key>CFBundleDevelopmentRegion</key> | ||
6 | + <string>$(DEVELOPMENT_LANGUAGE)</string> | ||
7 | + <key>CFBundleExecutable</key> | ||
8 | + <string>$(EXECUTABLE_NAME)</string> | ||
9 | + <key>CFBundleIdentifier</key> | ||
10 | + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> | ||
11 | + <key>CFBundleInfoDictionaryVersion</key> | ||
12 | + <string>6.0</string> | ||
13 | + <key>CFBundleName</key> | ||
14 | + <string>$(PRODUCT_NAME)</string> | ||
15 | + <key>CFBundlePackageType</key> | ||
16 | + <string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string> | ||
17 | + <key>CFBundleShortVersionString</key> | ||
18 | + <string>1.0</string> | ||
19 | + <key>CFBundleVersion</key> | ||
20 | + <string>1</string> | ||
21 | + <key>LSRequiresIPhoneOS</key> | ||
22 | + <true/> | ||
23 | + <key>UIApplicationSceneManifest</key> | ||
24 | + <dict> | ||
25 | + <key>UIApplicationSupportsMultipleScenes</key> | ||
26 | + <false/> | ||
27 | + <key>UISceneConfigurations</key> | ||
28 | + <dict> | ||
29 | + <key>UIWindowSceneSessionRoleApplication</key> | ||
30 | + <array> | ||
31 | + <dict> | ||
32 | + <key>UISceneConfigurationName</key> | ||
33 | + <string>Default Configuration</string> | ||
34 | + <key>UISceneDelegateClassName</key> | ||
35 | + <string>$(PRODUCT_MODULE_NAME).SceneDelegate</string> | ||
36 | + <key>UISceneStoryboardFile</key> | ||
37 | + <string>Main</string> | ||
38 | + </dict> | ||
39 | + </array> | ||
40 | + </dict> | ||
41 | + </dict> | ||
42 | + <key>UIApplicationSupportsIndirectInputEvents</key> | ||
43 | + <true/> | ||
44 | + <key>UILaunchStoryboardName</key> | ||
45 | + <string>LaunchScreen</string> | ||
46 | + <key>UIMainStoryboardFile</key> | ||
47 | + <string>Main</string> | ||
48 | + <key>UIRequiredDeviceCapabilities</key> | ||
49 | + <array> | ||
50 | + <string>armv7</string> | ||
51 | + </array> | ||
52 | + <key>UISupportedInterfaceOrientations</key> | ||
53 | + <array> | ||
54 | + <string>UIInterfaceOrientationPortrait</string> | ||
55 | + <string>UIInterfaceOrientationLandscapeLeft</string> | ||
56 | + <string>UIInterfaceOrientationLandscapeRight</string> | ||
57 | + </array> | ||
58 | + <key>UISupportedInterfaceOrientations~ipad</key> | ||
59 | + <array> | ||
60 | + <string>UIInterfaceOrientationPortrait</string> | ||
61 | + <string>UIInterfaceOrientationPortraitUpsideDown</string> | ||
62 | + <string>UIInterfaceOrientationLandscapeLeft</string> | ||
63 | + <string>UIInterfaceOrientationLandscapeRight</string> | ||
64 | + </array> | ||
65 | + <key>NSCameraUsageDescription</key> | ||
66 | + <string></string> | ||
67 | + <key>NSPhotoLibraryAddUsageDescription</key> | ||
68 | + <string></string> | ||
69 | + <key>NSMicrophoneUsageDescription</key> | ||
70 | + <string></string> | ||
71 | + <key>NSPhotoLibraryUsageDescription</key> | ||
72 | + <string></string> | ||
73 | +</dict> | ||
74 | +</plist> |
HapticCam /SceneDelegate.swift
0 → 100644
1 | +// | ||
2 | +// SceneDelegate.swift | ||
3 | +// HapticCam | ||
4 | +// | ||
5 | +// Created by 박진형 on 2020/12/15. | ||
6 | +// | ||
7 | + | ||
8 | +import UIKit | ||
9 | + | ||
10 | +class SceneDelegate: UIResponder, UIWindowSceneDelegate { | ||
11 | + | ||
12 | + var window: UIWindow? | ||
13 | + | ||
14 | + | ||
15 | + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { | ||
16 | + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. | ||
17 | + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. | ||
18 | + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). | ||
19 | + guard let _ = (scene as? UIWindowScene) else { return } | ||
20 | + } | ||
21 | + | ||
22 | + func sceneDidDisconnect(_ scene: UIScene) { | ||
23 | + // Called as the scene is being released by the system. | ||
24 | + // This occurs shortly after the scene enters the background, or when its session is discarded. | ||
25 | + // Release any resources associated with this scene that can be re-created the next time the scene connects. | ||
26 | + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). | ||
27 | + } | ||
28 | + | ||
29 | + func sceneDidBecomeActive(_ scene: UIScene) { | ||
30 | + // Called when the scene has moved from an inactive state to an active state. | ||
31 | + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. | ||
32 | + } | ||
33 | + | ||
34 | + func sceneWillResignActive(_ scene: UIScene) { | ||
35 | + // Called when the scene will move from an active state to an inactive state. | ||
36 | + // This may occur due to temporary interruptions (ex. an incoming phone call). | ||
37 | + } | ||
38 | + | ||
39 | + func sceneWillEnterForeground(_ scene: UIScene) { | ||
40 | + // Called as the scene transitions from the background to the foreground. | ||
41 | + // Use this method to undo the changes made on entering the background. | ||
42 | + } | ||
43 | + | ||
44 | + func sceneDidEnterBackground(_ scene: UIScene) { | ||
45 | + // Called as the scene transitions from the foreground to the background. | ||
46 | + // Use this method to save data, release shared resources, and store enough scene-specific state information | ||
47 | + // to restore the scene back to its current state. | ||
48 | + } | ||
49 | + | ||
50 | + | ||
51 | +} | ||
52 | + |
HapticCam /ViewController.swift
0 → 100644
1 | +// | ||
2 | +// ViewController.swift | ||
3 | +// HapticCam | ||
4 | +// | ||
5 | +// Created by 박진형 on 2020/12/15. | ||
6 | +// | ||
7 | +import AVFoundation | ||
8 | +import UIKit | ||
9 | +import RxCocoa | ||
10 | +import RxSwift | ||
11 | +import CoreMotion | ||
12 | +import Then | ||
13 | +import SnapKit | ||
14 | +import Alamofire | ||
15 | + | ||
16 | +class ViewController: UIViewController { | ||
17 | + // MARK:- Properties | ||
18 | + var disposeBag = DisposeBag() | ||
19 | + let captureSession = AVCaptureSession() | ||
20 | + var videoDevice: AVCaptureDevice! | ||
21 | + var videoInput: AVCaptureDeviceInput! | ||
22 | + var audioInput: AVCaptureDeviceInput! | ||
23 | + var videoOutput: AVCaptureMovieFileOutput! | ||
24 | + | ||
25 | + lazy var previewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession).then { | ||
26 | + $0.bounds = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height) | ||
27 | + $0.position = CGPoint(x: self.view.bounds.midX, y: self.view.bounds.midY) | ||
28 | + $0.videoGravity = .resizeAspectFill | ||
29 | + } | ||
30 | + | ||
31 | + let topContainer = UIView() | ||
32 | + let recordButton = UIButton().then { $0.setTitle("Record", for: .normal) } | ||
33 | + let resultButton = UIButton().then { $0.setTitle("Result", for: .normal) } | ||
34 | + let recordPoint = UIView().then { | ||
35 | + $0.backgroundColor = UIColor(red: 1.0, green: 0.75, blue: 0.01, alpha: 1) | ||
36 | + $0.layer.cornerRadius = 3 | ||
37 | + $0.alpha = 0 | ||
38 | + } | ||
39 | + | ||
40 | + let timerLabel = UILabel().then { | ||
41 | + $0.text = "00:00:00" | ||
42 | + $0.textColor = .white | ||
43 | + } | ||
44 | + | ||
45 | + var outputURL: URL? | ||
46 | + var motionManager: CMMotionManager! | ||
47 | + var deviceOrientation: AVCaptureVideoOrientation = .portrait | ||
48 | + var timer: Timer? | ||
49 | + var secondsOfTimer = 0 | ||
50 | + | ||
51 | + // MARK:- LifeCycle Methods | ||
52 | + override func viewWillAppear(_ animated: Bool) { | ||
53 | + super.viewWillAppear(animated) | ||
54 | + if !captureSession.isRunning { | ||
55 | + captureSession.startRunning() | ||
56 | + } | ||
57 | + } | ||
58 | + | ||
59 | + override func viewWillDisappear(_ animated: Bool) { | ||
60 | + super.viewWillDisappear(animated) | ||
61 | + motionManager.stopAccelerometerUpdates() | ||
62 | + stopTimer() | ||
63 | + } | ||
64 | + | ||
65 | + override func viewDidLoad() { | ||
66 | + super.viewDidLoad() | ||
67 | + layout() | ||
68 | + bind() | ||
69 | + videoDevice = bestDevice(in: .back) | ||
70 | + setupSession() | ||
71 | + } | ||
72 | + | ||
73 | + | ||
74 | + // MARK:- View Rendering | ||
75 | + private func layout() { | ||
76 | + self.view.layer.addSublayer(previewLayer) | ||
77 | + | ||
78 | + self.view.addSubview(topContainer) | ||
79 | + topContainer.snp.makeConstraints { | ||
80 | + $0.top.equalTo(view.safeAreaLayoutGuide.snp.top).offset(10) | ||
81 | + $0.leading.trailing.equalToSuperview() | ||
82 | + $0.height.equalTo(50) | ||
83 | + } | ||
84 | + topContainer.addSubview(resultButton) | ||
85 | + resultButton.snp.makeConstraints{ | ||
86 | + $0.centerY.equalToSuperview() | ||
87 | + $0.trailing.equalToSuperview().offset(-80) | ||
88 | + $0.height.equalTo(40) | ||
89 | + } | ||
90 | + | ||
91 | + | ||
92 | + topContainer.addSubview(timerLabel) | ||
93 | + timerLabel.snp.makeConstraints { | ||
94 | + $0.centerX.centerY.equalToSuperview() | ||
95 | + } | ||
96 | + | ||
97 | + topContainer.addSubview(recordPoint) | ||
98 | + recordPoint.snp.makeConstraints { | ||
99 | + $0.centerY.equalToSuperview() | ||
100 | + $0.trailing.equalTo(timerLabel.snp.leading).offset(-5) | ||
101 | + $0.width.height.equalTo(6) | ||
102 | + } | ||
103 | + | ||
104 | + self.view.addSubview(recordButton) | ||
105 | + recordButton.snp.makeConstraints { | ||
106 | + $0.centerX.equalToSuperview() | ||
107 | + $0.bottom.equalTo(view.safeAreaLayoutGuide.snp.bottom).offset(-50) | ||
108 | + $0.height.equalTo(40) | ||
109 | + } | ||
110 | + } | ||
111 | + | ||
112 | + // MARK:- Rx Binding | ||
113 | + | ||
114 | + private func bind() { | ||
115 | + recordButton.rx.tap | ||
116 | + .subscribe(onNext: { [weak self] in | ||
117 | + guard let `self` = self else { return } | ||
118 | + | ||
119 | + if self.videoOutput.isRecording { | ||
120 | + self.stopRecording() | ||
121 | + self.recordButton.setTitle("Record", for: .normal) | ||
122 | + } else { | ||
123 | + self.startRecording() | ||
124 | + self.recordButton.setTitle("Stop", for: .normal) | ||
125 | + } | ||
126 | + }) | ||
127 | + .disposed(by: self.disposeBag) | ||
128 | + | ||
129 | + | ||
130 | + resultButton.rx.tap | ||
131 | + .subscribe(onNext: { [weak self] in | ||
132 | + guard let `self` = self else { return } | ||
133 | + self.openResultpage() | ||
134 | + }) | ||
135 | + .disposed(by: self.disposeBag) | ||
136 | + | ||
137 | + } | ||
138 | + | ||
139 | + private func setupSession() { | ||
140 | + do { | ||
141 | + captureSession.beginConfiguration() | ||
142 | + | ||
143 | + videoInput = try AVCaptureDeviceInput(device: videoDevice!) | ||
144 | + if captureSession.canAddInput(videoInput) { | ||
145 | + captureSession.addInput(videoInput) | ||
146 | + } | ||
147 | + | ||
148 | + let audioDevice = AVCaptureDevice.default(for: AVMediaType.audio)! | ||
149 | + audioInput = try AVCaptureDeviceInput(device: audioDevice) | ||
150 | + if captureSession.canAddInput(audioInput) { | ||
151 | + captureSession.addInput(audioInput) | ||
152 | + } | ||
153 | + | ||
154 | + videoOutput = AVCaptureMovieFileOutput() | ||
155 | + if captureSession.canAddOutput(videoOutput) { | ||
156 | + captureSession.addOutput(videoOutput) | ||
157 | + } | ||
158 | + | ||
159 | + captureSession.commitConfiguration() | ||
160 | + } | ||
161 | + catch let error as NSError { | ||
162 | + NSLog("\(error), \(error.localizedDescription)") | ||
163 | + } | ||
164 | + } | ||
165 | + | ||
166 | + private func bestDevice(in position: AVCaptureDevice.Position) -> AVCaptureDevice { | ||
167 | + var deviceTypes: [AVCaptureDevice.DeviceType]! | ||
168 | + | ||
169 | + if #available(iOS 11.1, *) { | ||
170 | + deviceTypes = [.builtInTrueDepthCamera, .builtInDualCamera, .builtInWideAngleCamera] | ||
171 | + } else { | ||
172 | + deviceTypes = [.builtInDualCamera, .builtInWideAngleCamera] | ||
173 | + } | ||
174 | + | ||
175 | + let discoverySession = AVCaptureDevice.DiscoverySession( | ||
176 | + deviceTypes: deviceTypes, | ||
177 | + mediaType: .video, | ||
178 | + position: .unspecified | ||
179 | + ) | ||
180 | + | ||
181 | + let devices = discoverySession.devices | ||
182 | + guard !devices.isEmpty else { fatalError("Missing capture devices.")} | ||
183 | + | ||
184 | + return devices.first(where: { device in device.position == position })! | ||
185 | + } | ||
186 | + | ||
187 | + private func openResultpage(){ | ||
188 | + UIApplication.shared.openURL(NSURL(string: "http://192.168.43.75")! as URL) | ||
189 | + | ||
190 | + } | ||
191 | + | ||
192 | + private func swapCameraType() { | ||
193 | + guard let input = captureSession.inputs.first(where: { input in | ||
194 | + guard let input = input as? AVCaptureDeviceInput else { return false } | ||
195 | + return input.device.hasMediaType(.video) | ||
196 | + }) as? AVCaptureDeviceInput else { return } | ||
197 | + | ||
198 | + captureSession.beginConfiguration() | ||
199 | + defer { captureSession.commitConfiguration() } | ||
200 | + | ||
201 | + // Create new capture device | ||
202 | + var newDevice: AVCaptureDevice? | ||
203 | + if input.device.position == .back { | ||
204 | + newDevice = bestDevice(in: .front) | ||
205 | + } else { | ||
206 | + newDevice = bestDevice(in: .back) | ||
207 | + } | ||
208 | + | ||
209 | + do { | ||
210 | + videoInput = try AVCaptureDeviceInput(device: newDevice!) | ||
211 | + } catch let error { | ||
212 | + NSLog("\(error), \(error.localizedDescription)") | ||
213 | + return | ||
214 | + } | ||
215 | + | ||
216 | + // Swap capture device inputs | ||
217 | + captureSession.removeInput(input) | ||
218 | + captureSession.addInput(videoInput!) | ||
219 | + } | ||
220 | + | ||
221 | + | ||
222 | + // MARK:- Recording Methods | ||
223 | + private func post(_ inputURL: String){ | ||
224 | + // Prepare URL | ||
225 | + let url = URL(string: inputURL) | ||
226 | + guard let requestUrl = url else { fatalError() } | ||
227 | + // Prepare URL Request Object | ||
228 | + var request = URLRequest(url: requestUrl) | ||
229 | + request.httpMethod = "POST" | ||
230 | + // HTTP Request Parameters which will be sent in HTTP Request Body | ||
231 | + let postString = "userId=300&title=My urgent task&completed=false"; | ||
232 | + // Set HTTP Request Body | ||
233 | + request.httpBody = postString.data(using: String.Encoding.utf8); | ||
234 | + // Perform HTTP Request | ||
235 | + let task = URLSession.shared.dataTask(with: request) { (data, response, error) in | ||
236 | + | ||
237 | + // Check for Error | ||
238 | + if let error = error { | ||
239 | + print("Error took place \(error)") | ||
240 | + return | ||
241 | + } | ||
242 | + | ||
243 | + // Convert HTTP Response Data to a String | ||
244 | + if let data = data, let dataString = String(data: data, encoding: .utf8) { | ||
245 | + print("Response data string:\n \(dataString)") | ||
246 | + } | ||
247 | + } | ||
248 | + task.resume() | ||
249 | + } | ||
250 | + | ||
251 | + private func startRecording() { | ||
252 | + // haptic recording start | ||
253 | + self.post("http://192.168.43.228/record") | ||
254 | + let connection = videoOutput.connection(with: AVMediaType.video) | ||
255 | + | ||
256 | + if (connection?.isVideoOrientationSupported)! { | ||
257 | + connection?.videoOrientation = self.deviceOrientation | ||
258 | + } | ||
259 | + | ||
260 | + let device = videoInput.device | ||
261 | + if (device.isSmoothAutoFocusSupported) { | ||
262 | + do { | ||
263 | + try device.lockForConfiguration() | ||
264 | + device.isSmoothAutoFocusEnabled = false | ||
265 | + device.unlockForConfiguration() | ||
266 | + } catch { | ||
267 | + print("Error setting configuration: \(error)") | ||
268 | + } | ||
269 | + } | ||
270 | + | ||
271 | + // recording point | ||
272 | + recordPoint.alpha = 1 | ||
273 | + self.fadeViewInThenOut(view: recordPoint, delay: 0) | ||
274 | + self.startTimer() | ||
275 | + outputURL = tempURL() | ||
276 | + videoOutput.startRecording(to: outputURL!, recordingDelegate: self) | ||
277 | + } | ||
278 | + | ||
279 | + private func stopRecording() { | ||
280 | + self.post("http://192.168.43.228/stop") | ||
281 | + if videoOutput.isRecording { | ||
282 | + self.stopTimer() | ||
283 | + videoOutput.stopRecording() | ||
284 | + recordPoint.layer.removeAllAnimations() | ||
285 | + } | ||
286 | + } | ||
287 | + | ||
288 | + private func fadeViewInThenOut(view : UIView, delay: TimeInterval) { | ||
289 | + let animationDuration = 0.5 | ||
290 | + | ||
291 | + UIView.animate(withDuration: animationDuration, delay: delay, options: [UIView.AnimationOptions.autoreverse, UIView.AnimationOptions.repeat], animations: { | ||
292 | + view.alpha = 0 | ||
293 | + }, completion: nil) | ||
294 | + } | ||
295 | + | ||
296 | + private func tempURL() -> URL? { | ||
297 | + let directory = NSTemporaryDirectory() as NSString | ||
298 | + | ||
299 | + if directory != "" { | ||
300 | + let path = directory.appendingPathComponent(NSUUID().uuidString + ".mp4") | ||
301 | + return URL(fileURLWithPath: path) | ||
302 | + } | ||
303 | + | ||
304 | + return nil | ||
305 | + } | ||
306 | + | ||
307 | + // MARK:- Timer methods | ||
308 | + | ||
309 | + private func startTimer() { | ||
310 | + timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in | ||
311 | + guard let `self` = self else { return } | ||
312 | + | ||
313 | + self.secondsOfTimer += 1 | ||
314 | + self.timerLabel.text = Double(self.secondsOfTimer).format(units: [.hour ,.minute, .second]) | ||
315 | + } | ||
316 | + } | ||
317 | + private func stopTimer() { | ||
318 | + timer?.invalidate() | ||
319 | + self.timerLabel.text = "00:00:00" | ||
320 | + } | ||
321 | + } | ||
322 | + | ||
323 | + extension ViewController: AVCaptureFileOutputRecordingDelegate { | ||
324 | + // 레코딩이 시작되면 호출 | ||
325 | + func fileOutput(_ output: AVCaptureFileOutput, didStartRecordingTo fileURL: URL, from connections: [AVCaptureConnection]) { | ||
326 | + | ||
327 | + } | ||
328 | + // 레코딩이 끝나면 호출 | ||
329 | + func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) { | ||
330 | + if (error != nil) { | ||
331 | + print("Error recording movie: \(error!.localizedDescription)") | ||
332 | + } else { | ||
333 | + let videoRecorded = outputURL! as URL | ||
334 | + Alamofire.upload(multipartFormData: { (multipartFormData) in | ||
335 | + multipartFormData.append(videoRecorded, withName: "Video") | ||
336 | + }, to:"http://192.168.43.75/combine") | ||
337 | + { (result) in | ||
338 | + } | ||
339 | + | ||
340 | + self.dismiss(animated: true, completion: nil) | ||
341 | + UISaveVideoAtPathToSavedPhotosAlbum(videoRecorded.path, nil, nil, nil) | ||
342 | + } | ||
343 | + } | ||
344 | + } | ||
345 | + | ||
346 | + extension Double { | ||
347 | + func format(units: NSCalendar.Unit) -> String { | ||
348 | + let formatter = DateComponentsFormatter() | ||
349 | + formatter.unitsStyle = .positional | ||
350 | + formatter.allowedUnits = units | ||
351 | + formatter.zeroFormattingBehavior = [ .pad ] | ||
352 | + | ||
353 | + return formatter.string(from: self)! | ||
354 | + } | ||
355 | +} |
Podfile
0 → 100644
1 | +# Uncomment the next line to define a global platform for your project | ||
2 | +# platform :ios, '9.0' | ||
3 | + | ||
4 | +target 'HapticCam' do | ||
5 | + # Comment the next line if you don't want to use dynamic frameworks | ||
6 | + use_frameworks! | ||
7 | + | ||
8 | + # Pods for HapticCam | ||
9 | + pod 'RxSwift', '~> 4.5.0' | ||
10 | + pod 'RxCocoa', '~> 4.5.0' | ||
11 | + pod 'Alamofire', '~> 4.8.2' | ||
12 | + pod 'Then' | ||
13 | + pod 'SnapKit' | ||
14 | + | ||
15 | + target 'HapticCamTests' do | ||
16 | + inherit! :search_paths | ||
17 | + # Pods for testing | ||
18 | + end | ||
19 | + | ||
20 | + target 'HapticCamUITests' do | ||
21 | + # Pods for testing | ||
22 | + end | ||
23 | + | ||
24 | +end |
This file is too large to display.
-
Please register or login to post a comment