ZLCustomCamera.swift 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073
  1. //
  2. // ZLCustomCamera.swift
  3. // ZLPhotoBrowser
  4. //
  5. // Created by long on 2020/8/11.
  6. //
  7. // Copyright (c) 2020 Long Zhang <495181165@qq.com>
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy
  10. // of this software and associated documentation files (the "Software"), to deal
  11. // in the Software without restriction, including without limitation the rights
  12. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. // copies of the Software, and to permit persons to whom the Software is
  14. // furnished to do so, subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in
  17. // all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. // THE SOFTWARE.
  26. import UIKit
  27. import AVFoundation
  28. import CoreMotion
  29. public class ZLCustomCamera: UIViewController, CAAnimationDelegate {
  30. struct Layout {
  31. static let bottomViewH: CGFloat = 150
  32. static let largeCircleRadius: CGFloat = 85
  33. static let smallCircleRadius: CGFloat = 62
  34. static let largeCircleRecordScale: CGFloat = 1.2
  35. static let smallCircleRecordScale: CGFloat = 0.7
  36. }
  37. @objc public var takeDoneBlock: ( (UIImage?, URL?) -> Void )?
  38. var tipsLabel: UILabel!
  39. var hideTipsTimer: Timer?
  40. var bottomView: UIView!
  41. var largeCircleView: UIVisualEffectView!
  42. var smallCircleView: UIView!
  43. var animateLayer: CAShapeLayer!
  44. var retakeBtn: UIButton!
  45. var editBtn: UIButton!
  46. var doneBtn: UIButton!
  47. var dismissBtn: UIButton!
  48. var switchCameraBtn: UIButton!
  49. var focusCursorView: UIImageView!
  50. var takedImageView: UIImageView!
  51. var takedImage: UIImage?
  52. var videoUrl: URL?
  53. var motionManager: CMMotionManager?
  54. var orientation: AVCaptureVideoOrientation = .portrait
  55. let session = AVCaptureSession()
  56. var videoInput: AVCaptureDeviceInput?
  57. var imageOutput: AVCapturePhotoOutput!
  58. var movieFileOutput: AVCaptureMovieFileOutput!
  59. var previewLayer: AVCaptureVideoPreviewLayer?
  60. var recordVideoPlayerLayer: AVPlayerLayer?
  61. var cameraConfigureFinish = false
  62. var layoutOK = false
  63. var dragStart = false
  64. var viewDidAppearCount = 0
  65. var restartRecordAfterSwitchCamera = false
  66. var cacheVideoOrientation: AVCaptureVideoOrientation = .portrait
  67. var recordUrls: [URL] = []
  68. // 仅支持竖屏
  69. public override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
  70. return .portrait
  71. }
  72. public override var prefersStatusBarHidden: Bool {
  73. return true
  74. }
  75. deinit {
  76. zl_debugPrint("ZLCustomCamera deinit")
  77. self.cleanTimer()
  78. if self.session.isRunning {
  79. self.session.stopRunning()
  80. }
  81. try? AVAudioSession.sharedInstance().setActive(false, options: .notifyOthersOnDeactivation)
  82. }
  83. @objc public init() {
  84. super.init(nibName: nil, bundle: nil)
  85. self.modalPresentationStyle = .fullScreen
  86. }
  87. required init?(coder: NSCoder) {
  88. fatalError("init(coder:) has not been implemented")
  89. }
  90. public override func viewDidLoad() {
  91. super.viewDidLoad()
  92. self.setupUI()
  93. if !UIImagePickerController.isSourceTypeAvailable(.camera) {
  94. return
  95. }
  96. self.setupCamera()
  97. self.observerDeviceMotion()
  98. self.addNotification()
  99. AVCaptureDevice.requestAccess(for: .video) { (videoGranted) in
  100. if videoGranted {
  101. if ZLPhotoConfiguration.default().allowRecordVideo {
  102. AVCaptureDevice.requestAccess(for: .audio) { (audioGranted) in
  103. if !audioGranted {
  104. DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
  105. self.showAlertAndDismissAfterDoneAction(message: String(format: localLanguageTextValue(.noMicrophoneAuthority), getAppName()), type: .microphone)
  106. }
  107. }
  108. }
  109. }
  110. } else {
  111. DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
  112. self.showAlertAndDismissAfterDoneAction(message: String(format: localLanguageTextValue(.noCameraAuthority), getAppName()), type: .camera)
  113. })
  114. }
  115. }
  116. if ZLPhotoConfiguration.default().allowRecordVideo {
  117. try? AVAudioSession.sharedInstance().setCategory(.playAndRecord)
  118. try? AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
  119. }
  120. }
  121. public override func viewDidAppear(_ animated: Bool) {
  122. super.viewDidAppear(animated)
  123. if !UIImagePickerController.isSourceTypeAvailable(.camera) {
  124. showAlertAndDismissAfterDoneAction(message: localLanguageTextValue(.cameraUnavailable), type: .camera)
  125. } else if !ZLPhotoConfiguration.default().allowTakePhoto, !ZLPhotoConfiguration.default().allowRecordVideo {
  126. #if DEBUG
  127. fatalError("Error configuration of camera")
  128. #else
  129. showAlertAndDismissAfterDoneAction(message: "Error configuration of camera", type: nil)
  130. #endif
  131. } else if self.cameraConfigureFinish, self.viewDidAppearCount == 0 {
  132. self.showTipsLabel(animate: true)
  133. DispatchQueue.main.async {
  134. self.session.startRunning()
  135. }
  136. self.setFocusCusor(point: self.view.center)
  137. }
  138. self.viewDidAppearCount += 1
  139. }
  140. public override func viewWillDisappear(_ animated: Bool) {
  141. super.viewWillDisappear(animated)
  142. self.motionManager?.stopDeviceMotionUpdates()
  143. self.motionManager = nil
  144. }
  145. public override func viewDidDisappear(_ animated: Bool) {
  146. super.viewDidDisappear(animated)
  147. self.session.stopRunning()
  148. }
  149. public override func viewDidLayoutSubviews() {
  150. super.viewDidLayoutSubviews()
  151. guard !self.layoutOK else { return }
  152. self.layoutOK = true
  153. var insets = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0)
  154. if #available(iOS 11.0, *) {
  155. insets = self.view.safeAreaInsets
  156. }
  157. let previewLayerY: CGFloat = deviceSafeAreaInsets().top > 0 ? 20 : 0
  158. self.previewLayer?.frame = CGRect(x: 0, y: previewLayerY, width: self.view.bounds.width, height: self.view.bounds.height)
  159. self.recordVideoPlayerLayer?.frame = self.view.bounds
  160. self.takedImageView.frame = self.view.bounds
  161. self.bottomView.frame = CGRect(x: 0, y: self.view.bounds.height-insets.bottom-ZLCustomCamera.Layout.bottomViewH-50, width: self.view.bounds.width, height: ZLCustomCamera.Layout.bottomViewH)
  162. let largeCircleH = ZLCustomCamera.Layout.largeCircleRadius
  163. self.largeCircleView.frame = CGRect(x: (self.view.bounds.width-largeCircleH)/2, y: (ZLCustomCamera.Layout.bottomViewH-largeCircleH)/2, width: largeCircleH, height: largeCircleH)
  164. let smallCircleH = ZLCustomCamera.Layout.smallCircleRadius
  165. self.smallCircleView.frame = CGRect(x: (self.view.bounds.width-smallCircleH)/2, y: (ZLCustomCamera.Layout.bottomViewH-smallCircleH)/2, width: smallCircleH, height: smallCircleH)
  166. self.dismissBtn.frame = CGRect(x: 60, y: (ZLCustomCamera.Layout.bottomViewH-25)/2, width: 25, height: 25)
  167. self.tipsLabel.frame = CGRect(x: 0, y: self.bottomView.frame.minY-20, width: self.view.bounds.width, height: 20)
  168. self.retakeBtn.frame = CGRect(x: 30, y: insets.top+10, width: 28, height: 28)
  169. self.switchCameraBtn.frame = CGRect(x: self.view.bounds.width-30-28, y: insets.top+10, width: 28, height: 28)
  170. let editBtnW = localLanguageTextValue(.edit).boundingRect(font: ZLLayout.bottomToolTitleFont, limitSize: CGSize(width: CGFloat.greatestFiniteMagnitude, height: 40)).width
  171. self.editBtn.frame = CGRect(x: 20, y: self.view.bounds.height - insets.bottom - ZLLayout.bottomToolBtnH - 40, width: editBtnW, height: ZLLayout.bottomToolBtnH)
  172. let doneBtnW = localLanguageTextValue(.done).boundingRect(font: ZLLayout.bottomToolTitleFont, limitSize: CGSize(width: CGFloat.greatestFiniteMagnitude, height: 40)).width + 20
  173. self.doneBtn.frame = CGRect(x: self.view.bounds.width - doneBtnW - 20, y: self.view.bounds.height - insets.bottom - ZLLayout.bottomToolBtnH - 40, width: doneBtnW, height: ZLLayout.bottomToolBtnH)
  174. }
  175. func setupUI() {
  176. self.view.backgroundColor = .black
  177. self.takedImageView = UIImageView()
  178. self.takedImageView.backgroundColor = .black
  179. self.takedImageView.isHidden = true
  180. self.takedImageView.contentMode = .scaleAspectFit
  181. self.view.addSubview(self.takedImageView)
  182. self.focusCursorView = UIImageView(image: getImage("zl_focus"))
  183. self.focusCursorView.contentMode = .scaleAspectFit
  184. self.focusCursorView.clipsToBounds = true
  185. self.focusCursorView.frame = CGRect(x: 0, y: 0, width: 70, height: 70)
  186. self.focusCursorView.alpha = 0
  187. self.view.addSubview(self.focusCursorView)
  188. self.tipsLabel = UILabel()
  189. self.tipsLabel.font = getFont(14)
  190. self.tipsLabel.textColor = .white
  191. self.tipsLabel.textAlignment = .center
  192. self.tipsLabel.alpha = 0
  193. if ZLPhotoConfiguration.default().allowTakePhoto, ZLPhotoConfiguration.default().allowRecordVideo {
  194. self.tipsLabel.text = localLanguageTextValue(.customCameraTips)
  195. } else if ZLPhotoConfiguration.default().allowTakePhoto {
  196. self.tipsLabel.text = localLanguageTextValue(.customCameraTakePhotoTips)
  197. } else if ZLPhotoConfiguration.default().allowRecordVideo {
  198. self.tipsLabel.text = localLanguageTextValue(.customCameraRecordVideoTips)
  199. }
  200. self.view.addSubview(self.tipsLabel)
  201. self.bottomView = UIView()
  202. self.view.addSubview(self.bottomView)
  203. self.dismissBtn = UIButton(type: .custom)
  204. self.dismissBtn.setImage(getImage("zl_arrow_down"), for: .normal)
  205. self.dismissBtn.addTarget(self, action: #selector(dismissBtnClick), for: .touchUpInside)
  206. self.dismissBtn.adjustsImageWhenHighlighted = false
  207. self.dismissBtn.zl_enlargeValidTouchArea(inset: 30)
  208. self.bottomView.addSubview(self.dismissBtn)
  209. if #available(iOS 13.0, *) {
  210. self.largeCircleView = UIVisualEffectView(effect: UIBlurEffect(style: .systemThinMaterialLight))
  211. } else {
  212. self.largeCircleView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
  213. }
  214. self.largeCircleView.layer.masksToBounds = true
  215. self.largeCircleView.layer.cornerRadius = ZLCustomCamera.Layout.largeCircleRadius / 2
  216. self.bottomView.addSubview(self.largeCircleView)
  217. self.smallCircleView = UIView()
  218. self.smallCircleView.layer.masksToBounds = true
  219. self.smallCircleView.layer.cornerRadius = ZLCustomCamera.Layout.smallCircleRadius / 2
  220. self.smallCircleView.isUserInteractionEnabled = false
  221. self.smallCircleView.backgroundColor = .white
  222. self.bottomView.addSubview(self.smallCircleView)
  223. self.animateLayer = CAShapeLayer()
  224. let animateLayerRadius = ZLCustomCamera.Layout.largeCircleRadius
  225. let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: animateLayerRadius, height: animateLayerRadius), cornerRadius: animateLayerRadius/2)
  226. self.animateLayer.path = path.cgPath
  227. self.animateLayer.strokeColor = UIColor.cameraRecodeProgressColor.cgColor
  228. self.animateLayer.fillColor = UIColor.clear.cgColor
  229. self.animateLayer.lineWidth = 8
  230. var takePictureTap: UITapGestureRecognizer?
  231. if ZLPhotoConfiguration.default().allowTakePhoto {
  232. takePictureTap = UITapGestureRecognizer(target: self, action: #selector(takePicture))
  233. self.largeCircleView.addGestureRecognizer(takePictureTap!)
  234. }
  235. if ZLPhotoConfiguration.default().allowRecordVideo {
  236. let recordLongPress = UILongPressGestureRecognizer(target: self, action: #selector(longPressAction(_:)))
  237. recordLongPress.minimumPressDuration = 0.3
  238. recordLongPress.delegate = self
  239. self.largeCircleView.addGestureRecognizer(recordLongPress)
  240. takePictureTap?.require(toFail: recordLongPress)
  241. }
  242. self.retakeBtn = UIButton(type: .custom)
  243. self.retakeBtn.setImage(getImage("zl_retake"), for: .normal)
  244. self.retakeBtn.addTarget(self, action: #selector(retakeBtnClick), for: .touchUpInside)
  245. self.retakeBtn.isHidden = true
  246. self.retakeBtn.adjustsImageWhenHighlighted = false
  247. self.retakeBtn.zl_enlargeValidTouchArea(inset: 30)
  248. self.view.addSubview(self.retakeBtn)
  249. let cameraCount = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: .unspecified).devices.count
  250. self.switchCameraBtn = UIButton(type: .custom)
  251. self.switchCameraBtn.setImage(getImage("zl_toggle_camera"), for: .normal)
  252. self.switchCameraBtn.addTarget(self, action: #selector(switchCameraBtnClick), for: .touchUpInside)
  253. self.switchCameraBtn.adjustsImageWhenHighlighted = false
  254. self.switchCameraBtn.zl_enlargeValidTouchArea(inset: 30)
  255. self.switchCameraBtn.isHidden = cameraCount <= 1
  256. self.view.addSubview(self.switchCameraBtn)
  257. self.editBtn = UIButton(type: .custom)
  258. self.editBtn.titleLabel?.font = ZLLayout.bottomToolTitleFont
  259. self.editBtn.setTitle(localLanguageTextValue(.edit), for: .normal)
  260. self.editBtn.setTitleColor(.bottomToolViewBtnNormalTitleColor, for: .normal)
  261. self.editBtn.addTarget(self, action: #selector(editBtnClick), for: .touchUpInside)
  262. self.editBtn.isHidden = true
  263. // 字体周围添加一点阴影
  264. self.editBtn.titleLabel?.layer.shadowColor = UIColor.black.withAlphaComponent(0.3).cgColor
  265. self.editBtn.titleLabel?.layer.shadowOffset = .zero
  266. self.editBtn.titleLabel?.layer.shadowOpacity = 1;
  267. self.view.addSubview(self.editBtn)
  268. self.doneBtn = UIButton(type: .custom)
  269. self.doneBtn.titleLabel?.font = ZLLayout.bottomToolTitleFont
  270. self.doneBtn.setTitle(localLanguageTextValue(.done), for: .normal)
  271. self.doneBtn.setTitleColor(.bottomToolViewBtnNormalTitleColor, for: .normal)
  272. self.doneBtn.backgroundColor = .bottomToolViewBtnNormalBgColor
  273. self.doneBtn.addTarget(self, action: #selector(doneBtnClick), for: .touchUpInside)
  274. self.doneBtn.isHidden = true
  275. self.doneBtn.layer.masksToBounds = true
  276. self.doneBtn.layer.cornerRadius = ZLLayout.bottomToolBtnCornerRadius
  277. self.view.addSubview(self.doneBtn)
  278. let focusCursorTap = UITapGestureRecognizer(target: self, action: #selector(adjustFocusPoint))
  279. focusCursorTap.delegate = self
  280. self.view.addGestureRecognizer(focusCursorTap)
  281. if ZLPhotoConfiguration.default().allowRecordVideo {
  282. let pan = UIPanGestureRecognizer(target: self, action: #selector(adjustCameraFocus(_:)))
  283. pan.delegate = self
  284. pan.maximumNumberOfTouches = 1
  285. self.view.addGestureRecognizer(pan)
  286. self.recordVideoPlayerLayer = AVPlayerLayer()
  287. self.recordVideoPlayerLayer?.backgroundColor = UIColor.black.cgColor
  288. self.recordVideoPlayerLayer?.videoGravity = .resizeAspect
  289. self.recordVideoPlayerLayer?.isHidden = true
  290. self.view.layer.insertSublayer(self.recordVideoPlayerLayer!, at: 0)
  291. NotificationCenter.default.addObserver(self, selector: #selector(recordVideoPlayFinished), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
  292. }
  293. let pinchGes = UIPinchGestureRecognizer(target: self, action: #selector(pinchToAdjustCameraFocus(_:)))
  294. self.view.addGestureRecognizer(pinchGes)
  295. }
  296. func observerDeviceMotion() {
  297. if !Thread.isMainThread {
  298. DispatchQueue.main.async {
  299. self.observerDeviceMotion()
  300. }
  301. return
  302. }
  303. self.motionManager = CMMotionManager()
  304. self.motionManager?.deviceMotionUpdateInterval = 0.5
  305. if self.motionManager?.isDeviceMotionAvailable == true {
  306. self.motionManager?.startDeviceMotionUpdates(to: OperationQueue.main, withHandler: { (motion, error) in
  307. if let _ = motion {
  308. self.handleDeviceMotion(motion!)
  309. }
  310. })
  311. } else {
  312. self.motionManager = nil
  313. }
  314. }
  315. func handleDeviceMotion(_ motion: CMDeviceMotion) {
  316. let x = motion.gravity.x
  317. let y = motion.gravity.y
  318. if abs(y) >= abs(x) {
  319. if y >= 0 {
  320. self.orientation = .portraitUpsideDown
  321. } else {
  322. self.orientation = .portrait
  323. }
  324. } else {
  325. if x >= 0 {
  326. self.orientation = .landscapeLeft
  327. } else {
  328. self.orientation = .landscapeRight
  329. }
  330. }
  331. }
  332. func setupCamera() {
  333. guard let backCamera = self.getCamera(position: .back) else { return }
  334. guard let input = try? AVCaptureDeviceInput(device: backCamera) else { return }
  335. // 相机画面输入流
  336. self.videoInput = input
  337. // 照片输出流
  338. self.imageOutput = AVCapturePhotoOutput()
  339. // 音频输入流
  340. var audioInput: AVCaptureDeviceInput?
  341. if ZLPhotoConfiguration.default().allowRecordVideo, let microphone = self.getMicrophone() {
  342. audioInput = try? AVCaptureDeviceInput(device: microphone)
  343. }
  344. let preset = ZLPhotoConfiguration.default().sessionPreset.avSessionPreset
  345. if self.session.canSetSessionPreset(preset) {
  346. self.session.sessionPreset = preset
  347. } else {
  348. self.session.sessionPreset = .hd1280x720
  349. }
  350. self.movieFileOutput = AVCaptureMovieFileOutput()
  351. // 解决视频录制超过10s没有声音的bug
  352. self.movieFileOutput.movieFragmentInterval = .invalid
  353. // 将视频及音频输入流添加到session
  354. if let vi = self.videoInput, self.session.canAddInput(vi) {
  355. self.session.addInput(vi)
  356. }
  357. if let ai = audioInput, self.session.canAddInput(ai) {
  358. self.session.addInput(ai)
  359. }
  360. // 将输出流添加到session
  361. if self.session.canAddOutput(self.imageOutput) {
  362. self.session.addOutput(self.imageOutput)
  363. }
  364. if self.session.canAddOutput(self.movieFileOutput) {
  365. self.session.addOutput(self.movieFileOutput)
  366. }
  367. // 预览layer
  368. self.previewLayer = AVCaptureVideoPreviewLayer(session: self.session)
  369. self.previewLayer?.videoGravity = .resizeAspect
  370. self.view.layer.masksToBounds = true
  371. self.view.layer.insertSublayer(self.previewLayer!, at: 0)
  372. self.cameraConfigureFinish = true
  373. }
  374. func getCamera(position: AVCaptureDevice.Position) -> AVCaptureDevice? {
  375. let devices = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: position).devices
  376. for device in devices {
  377. if device.position == position {
  378. return device
  379. }
  380. }
  381. return nil
  382. }
  383. func getMicrophone() -> AVCaptureDevice? {
  384. return AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInMicrophone], mediaType: .audio, position: .unspecified).devices.first
  385. }
  386. func addNotification() {
  387. NotificationCenter.default.addObserver(self, selector: #selector(appWillResignActive), name: UIApplication.willResignActiveNotification, object: nil)
  388. if ZLPhotoConfiguration.default().allowRecordVideo {
  389. NotificationCenter.default.addObserver(self, selector: #selector(appDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
  390. }
  391. }
  392. func showAlertAndDismissAfterDoneAction(message: String, type: ZLNoAuthorityType?) {
  393. let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
  394. let action = UIAlertAction(title: localLanguageTextValue(.done), style: .default) { (_) in
  395. self.dismiss(animated: true) {
  396. if let t = type {
  397. ZLPhotoConfiguration.default().noAuthorityCallback?(t)
  398. }
  399. }
  400. }
  401. alert.addAction(action)
  402. self.showDetailViewController(alert, sender: nil)
  403. }
  404. func showTipsLabel(animate: Bool) {
  405. self.tipsLabel.layer.removeAllAnimations()
  406. if animate {
  407. UIView.animate(withDuration: 0.25) {
  408. self.tipsLabel.alpha = 1
  409. }
  410. } else {
  411. self.tipsLabel.alpha = 1
  412. }
  413. self.startHideTipsLabelTimer()
  414. }
  415. func hideTipsLabel(animate: Bool) {
  416. self.tipsLabel.layer.removeAllAnimations()
  417. if animate {
  418. UIView.animate(withDuration: 0.25) {
  419. self.tipsLabel.alpha = 0
  420. }
  421. } else {
  422. self.tipsLabel.alpha = 0
  423. }
  424. }
  425. @objc func hideTipsLabel_timerFunc() {
  426. self.cleanTimer()
  427. self.hideTipsLabel(animate: true)
  428. }
  429. func startHideTipsLabelTimer() {
  430. self.cleanTimer()
  431. self.hideTipsTimer = Timer.scheduledTimer(timeInterval: 3, target: ZLWeakProxy(target: self), selector: #selector(hideTipsLabel_timerFunc), userInfo: nil, repeats: false)
  432. RunLoop.current.add(self.hideTipsTimer!, forMode: .common)
  433. }
  434. func cleanTimer() {
  435. self.hideTipsTimer?.invalidate()
  436. self.hideTipsTimer = nil
  437. }
  438. @objc func appWillResignActive() {
  439. if self.session.isRunning {
  440. self.dismiss(animated: true, completion: nil)
  441. }
  442. if self.videoUrl != nil, let player = self.recordVideoPlayerLayer?.player {
  443. player.pause()
  444. }
  445. }
  446. @objc func appDidBecomeActive() {
  447. if self.videoUrl != nil, let player = self.recordVideoPlayerLayer?.player {
  448. player.play()
  449. }
  450. }
  451. @objc func dismissBtnClick() {
  452. self.dismiss(animated: true, completion: nil)
  453. }
  454. @objc func retakeBtnClick() {
  455. self.session.startRunning()
  456. self.resetSubViewStatus()
  457. self.takedImage = nil
  458. self.stopRecordAnimation()
  459. if let url = self.videoUrl {
  460. self.recordVideoPlayerLayer?.player?.pause()
  461. self.recordVideoPlayerLayer?.player = nil
  462. self.recordVideoPlayerLayer?.isHidden = true
  463. self.videoUrl = nil
  464. try? FileManager.default.removeItem(at: url)
  465. }
  466. }
  467. @objc func switchCameraBtnClick() {
  468. do {
  469. guard !self.restartRecordAfterSwitchCamera else {
  470. return
  471. }
  472. guard let currInput = self.videoInput else {
  473. return
  474. }
  475. var newVideoInput: AVCaptureDeviceInput?
  476. if currInput.device.position == .back, let front = self.getCamera(position: .front) {
  477. newVideoInput = try AVCaptureDeviceInput(device: front)
  478. } else if currInput.device.position == .front, let back = self.getCamera(position: .back) {
  479. newVideoInput = try AVCaptureDeviceInput(device: back)
  480. } else {
  481. return
  482. }
  483. let zoomFactor = currInput.device.videoZoomFactor
  484. if let ni = newVideoInput {
  485. self.session.beginConfiguration()
  486. self.session.removeInput(currInput)
  487. if self.session.canAddInput(ni) {
  488. self.session.addInput(ni)
  489. self.videoInput = ni
  490. ni.device.videoZoomFactor = zoomFactor
  491. } else {
  492. self.session.addInput(currInput)
  493. }
  494. self.session.commitConfiguration()
  495. if self.movieFileOutput.isRecording {
  496. let pauseTime = self.animateLayer.convertTime(CACurrentMediaTime(), from: nil)
  497. self.animateLayer.speed = 0
  498. self.animateLayer.timeOffset = pauseTime
  499. self.restartRecordAfterSwitchCamera = true
  500. }
  501. }
  502. } catch {
  503. zl_debugPrint("切换摄像头失败 \(error.localizedDescription)")
  504. }
  505. }
  506. @objc func editBtnClick() {
  507. guard let image = self.takedImage else {
  508. return
  509. }
  510. ZLEditImageViewController.showEditImageVC(parentVC: self, image: image) { [weak self] (ei, _) in
  511. self?.takedImage = ei
  512. self?.takedImageView.image = ei
  513. }
  514. }
  515. @objc func doneBtnClick() {
  516. self.recordVideoPlayerLayer?.player?.pause()
  517. self.recordVideoPlayerLayer?.player = nil
  518. self.dismiss(animated: true) {
  519. self.takeDoneBlock?(self.takedImage, self.videoUrl)
  520. }
  521. }
  522. // 点击拍照
  523. @objc func takePicture() {
  524. guard ZLPhotoManager.hasCameraAuthority() else {
  525. return
  526. }
  527. let connection = self.imageOutput.connection(with: .video)
  528. connection?.videoOrientation = self.orientation
  529. if self.videoInput?.device.position == .front, connection?.isVideoMirroringSupported == true {
  530. connection?.isVideoMirrored = true
  531. }
  532. let setting = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecJPEG])
  533. if self.videoInput?.device.hasFlash == true {
  534. setting.flashMode = ZLPhotoConfiguration.default().cameraFlashMode.avFlashMode
  535. }
  536. self.imageOutput.capturePhoto(with: setting, delegate: self)
  537. }
  538. // 长按录像
  539. @objc func longPressAction(_ longGes: UILongPressGestureRecognizer) {
  540. if longGes.state == .began {
  541. guard ZLPhotoManager.hasCameraAuthority(), ZLPhotoManager.hasMicrophoneAuthority() else {
  542. return
  543. }
  544. self.startRecord()
  545. } else if longGes.state == .cancelled || longGes.state == .ended {
  546. self.finishRecord()
  547. }
  548. }
  549. // 调整焦点
  550. @objc func adjustFocusPoint(_ tap: UITapGestureRecognizer) {
  551. guard self.session.isRunning else {
  552. return
  553. }
  554. let point = tap.location(in: self.view)
  555. if point.y > self.bottomView.frame.minY - 30 {
  556. return
  557. }
  558. self.setFocusCusor(point: point)
  559. }
  560. func setFocusCusor(point: CGPoint) {
  561. self.focusCursorView.center = point
  562. self.focusCursorView.layer.removeAllAnimations()
  563. self.focusCursorView.alpha = 1
  564. self.focusCursorView.layer.transform = CATransform3DMakeScale(1.2, 1.2, 1)
  565. UIView.animate(withDuration: 0.5, animations: {
  566. self.focusCursorView.layer.transform = CATransform3DIdentity
  567. }) { (_) in
  568. self.focusCursorView.alpha = 0
  569. }
  570. // ui坐标转换为摄像头坐标
  571. let cameraPoint = self.previewLayer?.captureDevicePointConverted(fromLayerPoint: point) ?? self.view.center
  572. self.focusCamera(mode: .autoFocus, exposureMode: .autoExpose, point: cameraPoint)
  573. }
  574. // 调整焦距
  575. @objc func adjustCameraFocus(_ pan: UIPanGestureRecognizer) {
  576. let convertRect = self.bottomView.convert(self.largeCircleView.frame, to: self.view)
  577. let point = pan.location(in: self.view)
  578. if pan.state == .began {
  579. if !convertRect.contains(point) {
  580. return
  581. }
  582. self.dragStart = true
  583. self.startRecord()
  584. } else if pan.state == .changed {
  585. guard self.dragStart else {
  586. return
  587. }
  588. let maxZoomFactor = self.getMaxZoomFactor()
  589. var zoomFactor = (convertRect.midY - point.y) / convertRect.midY * maxZoomFactor
  590. zoomFactor = max(1, min(zoomFactor, maxZoomFactor))
  591. self.setVideoZoomFactor(zoomFactor)
  592. } else if pan.state == .cancelled || pan.state == .ended {
  593. guard self.dragStart else {
  594. return
  595. }
  596. self.dragStart = false
  597. self.finishRecord()
  598. }
  599. }
  600. @objc func pinchToAdjustCameraFocus(_ pinch: UIPinchGestureRecognizer) {
  601. guard let device = self.videoInput?.device else {
  602. return
  603. }
  604. var zoomFactor = device.videoZoomFactor * pinch.scale
  605. zoomFactor = max(1, min(zoomFactor, self.getMaxZoomFactor()))
  606. self.setVideoZoomFactor(zoomFactor)
  607. pinch.scale = 1
  608. }
  609. func getMaxZoomFactor() -> CGFloat {
  610. guard let device = self.videoInput?.device else {
  611. return 1
  612. }
  613. if #available(iOS 11.0, *) {
  614. return device.maxAvailableVideoZoomFactor
  615. } else {
  616. return device.activeFormat.videoMaxZoomFactor
  617. }
  618. }
  619. func setVideoZoomFactor(_ zoomFactor: CGFloat) {
  620. guard let device = self.videoInput?.device else {
  621. return
  622. }
  623. do {
  624. try device.lockForConfiguration()
  625. device.videoZoomFactor = zoomFactor
  626. device.unlockForConfiguration()
  627. } catch {
  628. zl_debugPrint("调整焦距失败 \(error.localizedDescription)")
  629. }
  630. }
  631. func focusCamera(mode: AVCaptureDevice.FocusMode, exposureMode: AVCaptureDevice.ExposureMode, point: CGPoint) {
  632. do {
  633. guard let device = self.videoInput?.device else {
  634. return
  635. }
  636. try device.lockForConfiguration()
  637. if device.isFocusModeSupported(mode) {
  638. device.focusMode = mode
  639. }
  640. if device.isFocusPointOfInterestSupported {
  641. device.focusPointOfInterest = point
  642. }
  643. if device.isExposureModeSupported(exposureMode) {
  644. device.exposureMode = exposureMode
  645. }
  646. if device.isExposurePointOfInterestSupported {
  647. device.exposurePointOfInterest = point
  648. }
  649. device.unlockForConfiguration()
  650. } catch {
  651. zl_debugPrint("相机聚焦设置失败 \(error.localizedDescription)")
  652. }
  653. }
  654. func startRecord() {
  655. guard !self.movieFileOutput.isRecording else {
  656. return
  657. }
  658. self.dismissBtn.isHidden = true
  659. let connection = self.movieFileOutput.connection(with: .video)
  660. connection?.videoScaleAndCropFactor = 1
  661. if !self.restartRecordAfterSwitchCamera {
  662. connection?.videoOrientation = self.orientation
  663. self.cacheVideoOrientation = self.orientation
  664. } else {
  665. connection?.videoOrientation = self.cacheVideoOrientation
  666. }
  667. // 解决前置摄像头录制视频时候左右颠倒的问题
  668. if self.videoInput?.device.position == .front, connection?.isVideoMirroringSupported == true {
  669. // 镜像设置
  670. connection?.isVideoMirrored = true
  671. }
  672. let url = URL(fileURLWithPath: ZLVideoManager.getVideoExportFilePath())
  673. self.movieFileOutput.startRecording(to: url, recordingDelegate: self)
  674. }
  675. func finishRecord() {
  676. guard self.movieFileOutput.isRecording else {
  677. return
  678. }
  679. self.movieFileOutput.stopRecording()
  680. self.stopRecordAnimation()
  681. }
  682. func startRecordAnimation() {
  683. UIView.animate(withDuration: 0.1, animations: {
  684. self.largeCircleView.layer.transform = CATransform3DScale(CATransform3DIdentity, ZLCustomCamera.Layout.largeCircleRecordScale, ZLCustomCamera.Layout.largeCircleRecordScale, 1)
  685. self.smallCircleView.layer.transform = CATransform3DScale(CATransform3DIdentity, ZLCustomCamera.Layout.smallCircleRecordScale, ZLCustomCamera.Layout.smallCircleRecordScale, 1)
  686. }) { (_) in
  687. self.largeCircleView.layer.addSublayer(self.animateLayer)
  688. let animation = CABasicAnimation(keyPath: "strokeEnd")
  689. animation.fromValue = 0
  690. animation.toValue = 1
  691. animation.duration = Double(ZLPhotoConfiguration.default().maxRecordDuration)
  692. animation.delegate = self
  693. self.animateLayer.add(animation, forKey: nil)
  694. }
  695. }
  696. func stopRecordAnimation() {
  697. self.animateLayer.removeFromSuperlayer()
  698. self.animateLayer.removeAllAnimations()
  699. self.largeCircleView.transform = .identity
  700. self.smallCircleView.transform = .identity
  701. }
  702. public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
  703. self.finishRecord()
  704. }
  705. func resetSubViewStatus() {
  706. if self.session.isRunning {
  707. self.showTipsLabel(animate: true)
  708. self.bottomView.isHidden = false
  709. self.dismissBtn.isHidden = false
  710. self.switchCameraBtn.isHidden = false
  711. self.retakeBtn.isHidden = true
  712. self.editBtn.isHidden = true
  713. self.doneBtn.isHidden = true
  714. self.takedImageView.isHidden = true
  715. self.takedImage = nil
  716. } else {
  717. self.hideTipsLabel(animate: false)
  718. self.bottomView.isHidden = true
  719. self.dismissBtn.isHidden = true
  720. self.switchCameraBtn.isHidden = true
  721. self.retakeBtn.isHidden = false
  722. if ZLPhotoConfiguration.default().allowEditImage {
  723. self.editBtn.isHidden = self.takedImage == nil
  724. }
  725. self.doneBtn.isHidden = false
  726. }
  727. }
  728. func playRecordVideo(fileUrl: URL) {
  729. self.recordVideoPlayerLayer?.isHidden = false
  730. let player = AVPlayer(url: fileUrl)
  731. player.automaticallyWaitsToMinimizeStalling = false
  732. self.recordVideoPlayerLayer?.player = player
  733. player.play()
  734. }
  735. @objc func recordVideoPlayFinished() {
  736. self.recordVideoPlayerLayer?.player?.seek(to: .zero)
  737. self.recordVideoPlayerLayer?.player?.play()
  738. }
  739. }
  740. extension ZLCustomCamera: AVCapturePhotoCaptureDelegate {
  741. public func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photoSampleBuffer: CMSampleBuffer?, previewPhoto previewPhotoSampleBuffer: CMSampleBuffer?, resolvedSettings: AVCaptureResolvedPhotoSettings, bracketSettings: AVCaptureBracketedStillImageSettings?, error: Error?) {
  742. if photoSampleBuffer == nil || error != nil {
  743. zl_debugPrint("拍照失败 \(error?.localizedDescription ?? "")")
  744. return
  745. }
  746. if let data = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: photoSampleBuffer!, previewPhotoSampleBuffer: previewPhotoSampleBuffer) {
  747. self.session.stopRunning()
  748. self.takedImage = UIImage(data: data)?.fixOrientation()
  749. self.takedImageView.image = self.takedImage
  750. self.takedImageView.isHidden = false
  751. self.resetSubViewStatus()
  752. } else {
  753. zl_debugPrint("拍照失败,data为空")
  754. }
  755. }
  756. }
  757. extension ZLCustomCamera: AVCaptureFileOutputRecordingDelegate {
  758. public func fileOutput(_ output: AVCaptureFileOutput, didStartRecordingTo fileURL: URL, from connections: [AVCaptureConnection]) {
  759. if self.restartRecordAfterSwitchCamera {
  760. self.restartRecordAfterSwitchCamera = false
  761. // 稍微加一个延时,否则切换摄像头后拍摄时间会略小于设置的最大值
  762. DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
  763. let pauseTime = self.animateLayer.timeOffset
  764. self.animateLayer.speed = 1
  765. self.animateLayer.timeOffset = 0
  766. self.animateLayer.beginTime = 0
  767. let timeSincePause = self.animateLayer.convertTime(CACurrentMediaTime(), from: nil) - pauseTime
  768. self.animateLayer.beginTime = timeSincePause
  769. }
  770. } else {
  771. self.startRecordAnimation()
  772. }
  773. }
  774. public func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
  775. if self.restartRecordAfterSwitchCamera {
  776. self.recordUrls.append(outputFileURL)
  777. self.startRecord()
  778. return
  779. }
  780. self.recordUrls.append(outputFileURL)
  781. var duration: Double = 0
  782. if self.recordUrls.count == 1 {
  783. duration = output.recordedDuration.seconds
  784. } else {
  785. for url in self.recordUrls {
  786. let temp = AVAsset(url: url)
  787. duration += temp.duration.seconds
  788. }
  789. }
  790. // 重置焦距
  791. self.setVideoZoomFactor(1)
  792. if duration < Double(ZLPhotoConfiguration.default().minRecordDuration) {
  793. showAlertView(String(format: localLanguageTextValue(.minRecordTimeTips), ZLPhotoConfiguration.default().minRecordDuration), self)
  794. self.resetSubViewStatus()
  795. self.recordUrls.forEach { try? FileManager.default.removeItem(at: $0) }
  796. self.recordUrls.removeAll()
  797. return
  798. }
  799. // 拼接视频
  800. self.session.stopRunning()
  801. self.resetSubViewStatus()
  802. if self.recordUrls.count > 1 {
  803. ZLVideoManager.mergeVideos(fileUrls: self.recordUrls) { [weak self] (url, error) in
  804. if let url = url, error == nil {
  805. self?.videoUrl = url
  806. self?.playRecordVideo(fileUrl: url)
  807. } else if let error = error {
  808. self?.videoUrl = nil
  809. showAlertView(error.localizedDescription, self)
  810. }
  811. self?.recordUrls.forEach { try? FileManager.default.removeItem(at: $0) }
  812. self?.recordUrls.removeAll()
  813. }
  814. } else {
  815. self.videoUrl = outputFileURL
  816. self.playRecordVideo(fileUrl: outputFileURL)
  817. self.recordUrls.removeAll()
  818. }
  819. }
  820. }
  821. extension ZLCustomCamera: UIGestureRecognizerDelegate {
  822. public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
  823. // if gestureRecognizer is UILongPressGestureRecognizer, otherGestureRecognizer is UIPanGestureRecognizer {
  824. // return true
  825. // }
  826. return true
  827. }
  828. public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
  829. if gestureRecognizer is UIPanGestureRecognizer, touch.view is UIControl {
  830. // 解决拖动改变焦距时,无法点击其他按钮的问题
  831. return false
  832. }
  833. return true
  834. }
  835. }
  836. extension ZLCustomCamera {
  837. @objc public enum CaptureSessionPreset: Int {
  838. var avSessionPreset: AVCaptureSession.Preset {
  839. switch self {
  840. case .cif352x288:
  841. return .cif352x288
  842. case .vga640x480:
  843. return .vga640x480
  844. case .hd1280x720:
  845. return .hd1280x720
  846. case .hd1920x1080:
  847. return .hd1920x1080
  848. case .hd4K3840x2160:
  849. return .hd4K3840x2160
  850. }
  851. }
  852. case cif352x288
  853. case vga640x480
  854. case hd1280x720
  855. case hd1920x1080
  856. case hd4K3840x2160
  857. }
  858. @objc public enum CameraFlashMode: Int {
  859. // 转自定义相机
  860. var avFlashMode: AVCaptureDevice.FlashMode {
  861. switch self {
  862. case .auto:
  863. return .auto
  864. case .on:
  865. return .on
  866. case .off:
  867. return .off
  868. }
  869. }
  870. // 转系统相机
  871. var imagePickerFlashMode: UIImagePickerController.CameraFlashMode {
  872. switch self {
  873. case .auto:
  874. return .auto
  875. case .on:
  876. return .on
  877. case .off:
  878. return .off
  879. }
  880. }
  881. case auto
  882. case on
  883. case off
  884. }
  885. @objc public enum VideoExportType: Int {
  886. var format: String {
  887. switch self {
  888. case .mov:
  889. return "mov"
  890. case .mp4:
  891. return "mp4"
  892. }
  893. }
  894. var avFileType: AVFileType {
  895. switch self {
  896. case .mov:
  897. return .mov
  898. case .mp4:
  899. return .mp4
  900. }
  901. }
  902. case mov
  903. case mp4
  904. }
  905. }