CameraViewFactory.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. import Foundation
  2. import Flutter
  3. import UIKit
  4. import AliyunVideoSDKPro
  5. public class CameraViewFactory: NSObject, FlutterPlatformViewFactory {
  6. var messenger: FlutterBinaryMessenger!
  7. public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
  8. return CameraView(withFrame: frame, viewIdentifier: viewId, arguments: args, binaryMessenger: messenger)
  9. }
  10. @objc public init(messenger: (NSObject & FlutterBinaryMessenger)?) {
  11. super.init()
  12. self.messenger = messenger
  13. }
  14. public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
  15. return FlutterStandardMessageCodec.sharedInstance()
  16. }
  17. }
  18. public class CameraView: NSObject, FlutterPlatformView, AliyunIRecorderDelegate, AliyunIPlayerCallback, AliyunIExporterCallback, AliyunIRenderCallback {
  19. public func playerDidEnd() {
  20. }
  21. public func playProgress(_ playSec: Double, streamProgress streamSec: Double) {
  22. }
  23. public func playError(_ errorCode: Int32) {
  24. }
  25. public func seekDidEnd() {
  26. }
  27. public func playerDidStart() {
  28. }
  29. fileprivate var viewId: Int64!
  30. fileprivate var cameraView: UIView!
  31. fileprivate var channel: FlutterMethodChannel!
  32. fileprivate var recordPath: String?
  33. fileprivate var taskPath: String?
  34. fileprivate var recorder: AliyunIRecorder!
  35. fileprivate var composeResult: FlutterResult?
  36. public init(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?, binaryMessenger: FlutterBinaryMessenger) {
  37. super.init()
  38. self.viewId = viewId
  39. self.cameraView = UIView()
  40. self.cameraView.frame = UIScreen.main.bounds
  41. self.channel = FlutterMethodChannel(name: "flutter_ali_camera_\(viewId)", binaryMessenger: binaryMessenger)
  42. self.channel.setMethodCallHandler({
  43. [weak self]
  44. (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
  45. if let this = self {
  46. this.onMethodCall(call: call, result: result)
  47. }
  48. })
  49. }
  50. public func view() -> UIView {
  51. return self.cameraView
  52. }
  53. func onMethodCall(call: FlutterMethodCall, result: @escaping FlutterResult) {
  54. let args = call.arguments as? [String: Any]
  55. let method = call.method
  56. if call.method == "initializeSdk" {
  57. // initSdk(result)
  58. result(true)
  59. } else if call.method == "cleanAudioData" {
  60. let videoId = args!["videoId"] as! String
  61. let pathAudio = args!["pathAudio"] as! String
  62. deleteFileWithPrefix(folderPath: pathAudio, prefix: "\(videoId)_")
  63. result(true)
  64. } else if call.method == "getFilterParentPath" {
  65. print("path \(Bundle.main.path(forResource: "icon", ofType: "png"))")
  66. let url = Bundle.main.bundlePath
  67. result("\(url)/filter/")
  68. } else if method == "startPreview" {
  69. initCameraView()
  70. recorder?.clipManager?.deleteAllPart()
  71. recorder?.startPreview(withPositon: AliyunIRecorderCameraPosition.front)
  72. result(true)
  73. } else
  74. if method == "startPreview" {
  75. initCameraView()
  76. recorder?.clipManager?.deleteAllPart()
  77. recorder?.startPreview(withPositon: AliyunIRecorderCameraPosition.front)
  78. result(true)
  79. } else if method == "setRecordPath" {
  80. self.recordPath = args!["path"] as? String
  81. self.taskPath = args!["taskPath"] as? String
  82. result(true)
  83. } else if method == "onStop" {
  84. onStop()
  85. result(true)
  86. } else if method == "onResume" {
  87. recorder?.startPreview(withPositon: AliyunIRecorderCameraPosition.front)
  88. result(true)
  89. } else if method == "onDestory" {
  90. onStop()
  91. onDestory()
  92. result(true)
  93. } else if method == "switchCamera" {
  94. recorder.switchCameraPosition()
  95. result(true)
  96. } else if method == "setBeautyLevel" {
  97. let level = args!["level"] as! Int
  98. recorder.beautifyStatus = (level == 0)
  99. recorder.beautifyValue = Int32(level)
  100. result(true)
  101. } else if method == "startRecord" {
  102. let maxDuration = args!["maxDuration"] as! Int
  103. let videoId = args!["videoId"] as! String
  104. let index = args!["index"] as! Int
  105. let fileName = args!["fileName"] as! String
  106. if recorder?.clipManager?.videoAbsolutePaths != nil && recorder?.clipManager?.videoAbsolutePaths.count != 0 {
  107. recorder?.clipManager?.deletePart()
  108. }
  109. clearCacheVideo(videoId: videoId, index: index)
  110. recorder?.outputPath = self.recordPath! + "\(fileName).mp4"
  111. recorder?.clipManager?.maxDuration = CGFloat(Float(maxDuration) / 1000.0)
  112. recorder?.startRecording()
  113. result(true)
  114. } else if method == "stopRecord" {
  115. recorder?.stopRecording()
  116. result(true)
  117. } else if method == "finishRecord" {
  118. recorder?.finishRecording()
  119. result(true)
  120. } else if method == "setFilter" {
  121. let path: String? = args?["path"] as? String
  122. if path == nil || path?.count == 0 {
  123. // delete filter
  124. recorder?.deleteFilter()
  125. return
  126. }
  127. recorder?.apply(AliyunEffectFilter.init(file: path))
  128. } else if method == "startCompose" {
  129. let outputPath = args!["outputPath"] as! String
  130. let bgmPath = args!["bgmPath"] as! String
  131. let paths = args!["paths"] as! [String]
  132. let durations = args!["durations"] as! [Int]
  133. composeResult = result
  134. startCompose(outputPath: outputPath, bgmPath: bgmPath, paths: paths, durations: durations)
  135. } else if method == "create"{
  136. result(FlutterMethodNotImplemented)
  137. }
  138. }
  139. private func initCameraView() {
  140. recorder = AliyunIRecorder.init(delegate: self, videoSize: CGSize(width: 720, height: 1280))
  141. recorder?.preview = self.cameraView
  142. recorder?.taskPath = self.taskPath
  143. recorder?.clipManager?.maxDuration = 0
  144. }
  145. private func onStop() {
  146. recorder?.stopRecording()
  147. recorder?.stopPreview()
  148. }
  149. private func onDestory() {
  150. recorder?.destroy()
  151. recorder = nil
  152. cameraView = nil
  153. }
  154. var editor: AliyunEditor?
  155. private func startCompose(outputPath: String, bgmPath: String, paths: [String], durations: [Int]) {
  156. AliyunVideoSDKInfo.print()
  157. if durations.count != paths.count {
  158. return
  159. }
  160. // AliyunVideoSDKInfo.setLogLevel(AlivcLogLevel.debug)
  161. let importer = AliyunImporter.init(path: self.taskPath, outputSize: CGSize(width: 480, height: 720))
  162. let param = AliyunVideoParam.init()
  163. param.fps = 30
  164. param.gop = 30
  165. param.videoQuality = AliyunVideoQuality.medium
  166. param.scaleMode = AliyunScaleMode.fill
  167. // 编码模式
  168. param.codecType = AliyunVideoCodecType.hardware
  169. importer?.setVideoParam(param)
  170. for index in 0..<paths.count {
  171. print("paths: \(paths[index])")
  172. // 取出有效的document地址
  173. let documentRance: Range = paths[index].range(of: "/Documents/")!
  174. let distance: Int = paths[index].distance(from: paths[index].startIndex, to: documentRance.upperBound)
  175. let truePath = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0] + paths[index].subString(from: distance - 1)
  176. print("true path: \(truePath)")
  177. importer?.addMediaClip(AliyunClip.init(videoPath: truePath, animDuration: 0))
  178. }
  179. importer?.generateProjectConfigure()
  180. editor = AliyunEditor.init(path: self.taskPath, preview: nil)
  181. editor?.apply(AliyunEffectMusic.init(file: bgmPath))
  182. editor?.delegate = self
  183. editor?.startEdit()
  184. let exporter = editor?.getExporter()
  185. exporter?.startExport(outputPath)
  186. }
  187. private func clearCacheVideo(videoId: String, index: Int) {
  188. deleteFileWithPrefix(folderPath: self.recordPath!, prefix: "\(videoId)_\(index)_")
  189. }
  190. private func deleteFileWithPrefix(folderPath: String, prefix: String) {
  191. let manager = FileManager.default
  192. let files:[String] = try! manager.contentsOfDirectory(atPath: folderPath)
  193. for file in files{
  194. do {
  195. if file.hasPrefix(prefix) {
  196. try manager.removeItem(atPath: folderPath + "/\(file)")
  197. }
  198. } catch {
  199. print("faild to delete")
  200. }
  201. }
  202. }
  203. public func recorderDeviceAuthorization(_ status: AliyunIRecorderDeviceAuthor) {
  204. if status == AliyunIRecorderDeviceAuthor.enabled {
  205. print("sdk enabled")
  206. } else {
  207. print("sdk disabled")
  208. }
  209. }
  210. public func recorderVideoDuration(_ duration: CGFloat) {
  211. print("recorderVideoDuration:\(duration)")
  212. channel.invokeMethod("recordProgress", arguments: ["progress": Int(duration * 1000)])
  213. }
  214. public func recoderError(_ error: Error!) {
  215. print("recoderError")
  216. recorder?.finishRecording()
  217. }
  218. public func recorderDidStopWithMaxDuration() {
  219. print("recorderDidStopWithMaxDuration")
  220. recorder?.finishRecording()
  221. }
  222. public func recorderDidFinishRecording() {
  223. channel.invokeMethod("recordProgress", arguments: ["progress": Int((recorder?.clipManager?.maxDuration ?? 0) * 1000), "path": recorder?.outputPath])
  224. }
  225. public func exporterDidCancel() {
  226. }
  227. public func exportProgress(_ progress: Float) {
  228. print("exportProgress")
  229. }
  230. public func exportError(_ errorCode: Int32) {
  231. print("export error : \(errorCode)")
  232. composeResult?(false)
  233. }
  234. public func exporterDidStart() {
  235. print("exporterDidStart")
  236. }
  237. public func exporterDidEnd(_ outputPath: String!) {
  238. composeResult?(true)
  239. }
  240. }
  241. extension String {
  242. func toInt() -> Int? {
  243. return Int(self)
  244. }
  245. func toFloat() -> Float? {
  246. return Float(self)
  247. }
  248. func toDouble() -> Double? {
  249. return Double(self)
  250. }
  251. //MARK:- 去除字符串两端的空白字符
  252. func trim() -> String {
  253. return self.trimmingCharacters(in: CharacterSet.whitespaces)
  254. }
  255. func subString(to: Int) -> String {
  256. var to = to
  257. if to > self.count {
  258. to = self.count
  259. }
  260. return String(self.prefix(to))
  261. }
  262. func subString(from: Int) -> String {
  263. if from >= self.count {
  264. return ""
  265. }
  266. let startIndex = self.index(self.startIndex, offsetBy: from)
  267. let endIndex = self.endIndex
  268. return String(self[startIndex..<endIndex])
  269. }
  270. func subString(start: Int, end: Int) -> String {
  271. if start < end {
  272. let startIndex = self.index(self.startIndex, offsetBy: start)
  273. let endIndex = self.index(self.startIndex, offsetBy: end)
  274. return String(self[startIndex..<endIndex])
  275. }
  276. return ""
  277. }
  278. }