박진형

hapcam source

No preview for this file type
HapticCam @ 5cecd1ec
1 -Subproject commit 5cecd1eccf2cbab550329d1d89d8de28c78e9056
No preview for this file type
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 +
No preview for this file type
1 +{
2 + "colors" : [
3 + {
4 + "idiom" : "universal"
5 + }
6 + ],
7 + "info" : {
8 + "author" : "xcode",
9 + "version" : 1
10 + }
11 +}
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 +}
1 +{
2 + "info" : {
3 + "author" : "xcode",
4 + "version" : 1
5 + }
6 +}
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>
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>
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>
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 +
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 +}
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.