AliCameraViewFactory.swift 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. //
  2. // AliCameraViewFactory.swift
  3. // AliyunOSSiOS
  4. //
  5. // Created by weihong huang on 2020/8/20.
  6. //
  7. import Flutter
  8. import Foundation
  9. import AliyunVideoSDKPro
  10. public class AliCameraViewFactory : NSObject, FlutterPlatformViewFactory {
  11. var messeneger: FlutterBinaryMessenger!
  12. public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
  13. return AliCameraView(frame: frame, viewIdentifier: viewId, arguments: args, binaryMessenger: self.messeneger)
  14. }
  15. init(messeneger: FlutterBinaryMessenger) {
  16. super.init()
  17. self.messeneger = messeneger
  18. }
  19. public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
  20. return FlutterStandardMessageCodec.sharedInstance()
  21. }
  22. }
  23. public class AliCameraView : NSObject, FlutterPlatformView, AliyunIRecorderDelegate, AliyunIExporterCallback {
  24. fileprivate var uiView: UIView = UIView()
  25. fileprivate var recorder: AliyunIRecorder!
  26. fileprivate var methodChannel: FlutterMethodChannel!
  27. fileprivate var taskPath: String!
  28. fileprivate var aliyunEditor: AliyunEditor!
  29. fileprivate var exportResult: FlutterResult!
  30. init(frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?, binaryMessenger: FlutterBinaryMessenger) {
  31. super.init()
  32. // init view size
  33. let dict = args as! [String: Any]
  34. self.uiView.frame = CGRect.init(x: dict["x"] as? Float64 ?? 0,
  35. y: dict["y"] as? Float64 ?? 0,
  36. width: dict["width"] as? Float64 ?? Float64(UIScreen.main.bounds.width),
  37. height: dict["height"] as? Float64 ?? Float64(UIScreen.main.bounds.height))
  38. // init method channel
  39. self.methodChannel = FlutterMethodChannel(name: "com.i2edu.cameraLib/camera_view_\(viewId)", binaryMessenger: binaryMessenger)
  40. self.methodChannel?.setMethodCallHandler ({
  41. [weak self]
  42. (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
  43. if let this = self {
  44. this.onMethonCall(call: call, result: result)
  45. }
  46. })
  47. }
  48. public func view() -> UIView {
  49. return self.uiView
  50. }
  51. func onMethonCall(call: FlutterMethodCall, result: @escaping FlutterResult) {
  52. let args = call.arguments as? [String: Any]
  53. switch call.method {
  54. case "create":
  55. let recordOptions = args?["recordOption"] as? [String: Any]
  56. let taskPath = args?["iosTaskPath"] as! String
  57. do {
  58. try setUpCameraView(recordOption: recordOptions!, taskPath: taskPath)
  59. result(true)
  60. } catch {
  61. result(false)
  62. }
  63. break
  64. case "startPreview":
  65. do {
  66. try startPreview()
  67. result(true)
  68. } catch {
  69. result(false)
  70. }
  71. break;
  72. case "stopPreview":
  73. do {
  74. try stopPreview()
  75. result(true)
  76. } catch {
  77. result(false)
  78. }
  79. break;
  80. case "switchCamera":
  81. do {
  82. try switchCamera()
  83. result(true)
  84. } catch {
  85. result(false)
  86. }
  87. break;
  88. case "setBeauty":
  89. do {
  90. try setBeauty(level: args?["level"] as? Int32 ?? 0)
  91. result(true)
  92. } catch {
  93. result(false)
  94. }
  95. break;
  96. case "setFilter":
  97. do {
  98. try setFilter(path: args?["path"] as? String)
  99. result(true)
  100. } catch {
  101. result(false)
  102. }
  103. break;
  104. case "startRecord":
  105. do {
  106. try startRecord(
  107. maxDuration: args?["max"] as! Int32,
  108. recordPath: args?["recordPath"] as! String)
  109. result(true)
  110. } catch {
  111. result(FlutterError.init(code: "1001", message: error.localizedDescription, details: nil))
  112. }
  113. break
  114. case "startCompose":
  115. do {
  116. self.exportResult = result
  117. let composeOption = args?["composeOption"] as! [String: Any]
  118. try startCompose(
  119. outputPath: args?["outputPath"] as! String,
  120. bgmPath: args?["bgmPath"] as? String,
  121. paths: args?["paths"] as! [String],
  122. durations: args?["durations"] as! [Int32],
  123. composeOption: composeOption
  124. )
  125. } catch {
  126. result(FlutterError.init(code: "1002", message: error.localizedDescription, details: nil))
  127. }
  128. break
  129. case "onDestroy":
  130. do {
  131. try onDestroy()
  132. result(true)
  133. } catch {
  134. result(false)
  135. }
  136. break
  137. default:
  138. result(FlutterMethodNotImplemented)
  139. }
  140. }
  141. func setUpCameraView(recordOption: [String: Any], taskPath: String) throws {
  142. self.taskPath = taskPath
  143. self.recorder = AliyunIRecorder.init(delegate: self, videoSize: CGSize(width: recordOption["videoWidth"] as! Int, height: recordOption["videoHeight"] as! Int))
  144. let keys = recordOption.keys
  145. if (keys.contains("fps")) {
  146. self.recorder.recordFps = recordOption["fps"] as! Int32
  147. }
  148. if (keys.contains("videoCodecs")) {
  149. if (recordOption["videoCodecs"] as! Int == 0) {
  150. self.recorder.encodeMode = 0
  151. } else {
  152. self.recorder.encodeMode = 1
  153. }
  154. }
  155. if (keys.contains("quality")) {
  156. self.recorder.videoQuality = AliyunVideoQuality.init(rawValue: recordOption["quality"] as! Int) ?? AliyunVideoQuality.medium
  157. }
  158. if (keys.contains("videoBitrate")) {
  159. self.recorder.bitrate = recordOption["videoBitrate"] as! Int32
  160. }
  161. if (keys.contains("gop")) {
  162. self.recorder.gop = recordOption["gop"] as! Int32
  163. }
  164. self.recorder?.beautifyStatus = false
  165. self.recorder?.beautifyValue = 0
  166. self.recorder?.preview = self.uiView
  167. }
  168. func startPreview() throws {
  169. self.recorder?.startPreview(withPositon: AliyunIRecorderCameraPosition.front)
  170. }
  171. func stopPreview() throws {
  172. self.recorder?.stopPreview()
  173. }
  174. func switchCamera() throws {
  175. self.recorder?.switchCameraPosition()
  176. }
  177. func setBeauty(level: Int32) throws {
  178. self.recorder?.beautifyStatus = level != 0
  179. self.recorder?.beautifyValue = level
  180. }
  181. func setFilter(path: String?) throws {
  182. if path == nil || path?.count == 0 {
  183. recorder?.deleteFilter()
  184. } else {
  185. self.recorder?.apply(AliyunEffectFilter.init(file: path))
  186. }
  187. }
  188. func startRecord(maxDuration: Int32, recordPath: String) throws {
  189. recorder?.taskPath = taskPath
  190. recorder?.clipManager?.deleteAllPart()
  191. recorder?.outputPath = recordPath
  192. recorder?.clipManager?.maxDuration = CGFloat(Float(maxDuration) / 1000.0)
  193. recorder?.startRecording()
  194. }
  195. func startCompose(outputPath: String, bgmPath: String?, paths: [String], durations: [Int32], composeOption: [String: Any]) throws {
  196. // set up AliyunImporter
  197. let importer = AliyunImporter.init(
  198. path: self.taskPath,
  199. outputSize: CGSize(width: composeOption["outputWidth"] as! CGFloat, height: composeOption["outputHeight"] as! CGFloat))
  200. let composeParam = AliyunVideoParam.init()
  201. let keys = composeOption.keys
  202. if keys.contains("gop") {
  203. composeParam.gop = composeOption["gop"] as! Int32
  204. }
  205. if keys.contains("bitrate") {
  206. composeParam.bitrate = composeOption["bitrate"] as! Int32
  207. }
  208. if keys.contains("frameRate") {
  209. composeParam.fps = composeOption["frameRate"] as! Int32
  210. }
  211. if keys.contains("videoCodecs") {
  212. var codecType:AliyunVideoCodecType
  213. let type = composeOption["videoCodecs"] as! Int32
  214. if type == 0 {
  215. codecType = AliyunVideoCodecType.hardware
  216. } else if type == 1 {
  217. codecType = AliyunVideoCodecType.openh264
  218. } else if type == 2 {
  219. codecType = AliyunVideoCodecType.fFmpeg
  220. } else {
  221. codecType = AliyunVideoCodecType.typeAuto
  222. }
  223. composeParam.codecType = codecType
  224. }
  225. if keys.contains("quality") {
  226. composeParam.videoQuality =
  227. AliyunVideoQuality.init(rawValue: composeOption["quality"] as! Int) ?? AliyunVideoQuality.medium
  228. }
  229. if keys.contains("scaleMode") {
  230. composeParam.scaleMode =
  231. AliyunScaleMode.init(rawValue: composeOption["scaleMode"] as! Int) ?? AliyunScaleMode.fit
  232. }
  233. for index in 0..<paths.count {
  234. importer?.addMediaClip(AliyunClip.init(videoPath: paths[index], animDuration: 0))
  235. }
  236. importer?.setVideoParam(composeParam)
  237. importer?.generateProjectConfigure()
  238. // set up AliyunEditor
  239. self.aliyunEditor = AliyunEditor.init(path: self.taskPath, preview: nil)
  240. if bgmPath != nil && bgmPath?.count != 0 {
  241. self.aliyunEditor.apply(AliyunEffectMusic.init(file: bgmPath))
  242. }
  243. self.aliyunEditor.delegate = self
  244. self.aliyunEditor.startEdit()
  245. let exporter = self.aliyunEditor.getExporter()
  246. exporter?.startExport(outputPath)
  247. }
  248. func onDestroy() throws {
  249. try stopPreview()
  250. recorder?.stopRecording()
  251. recorder?.destroy()
  252. recorder = nil
  253. }
  254. // ----- record delegate
  255. public func recorderDeviceAuthorization(_ status: AliyunIRecorderDeviceAuthor) {
  256. if status == AliyunIRecorderDeviceAuthor.enabled {
  257. print("sdk enabled")
  258. } else {
  259. print("sdk disabled")
  260. }
  261. }
  262. public func recorderVideoDuration(_ duration: CGFloat) {
  263. print("record update \(duration)")
  264. methodChannel.invokeMethod("recordUpdate", arguments: ["progress": Int(duration * 1000)])
  265. }
  266. public func recorderDidStopWithMaxDuration() {
  267. print("record stop with max duration")
  268. recorder?.finishRecording()
  269. }
  270. public func recorderDidFinishRecording() {
  271. print("record completed")
  272. methodChannel.invokeMethod("recordUpdate", arguments: ["progress": Int((recorder?.clipManager?.maxDuration ?? 0) * 1000), "path": recorder?.outputPath ?? ""])
  273. }
  274. public func recoderError(_ error: Error!) {
  275. print("record error \(error.localizedDescription)")
  276. recorder?.finishRecording()
  277. }
  278. // ----- export delegate
  279. public func exporterDidStart() {
  280. print("export did start")
  281. }
  282. public func exportProgress(_ progress: Float) {
  283. print("export progress \(progress)")
  284. }
  285. public func exportError(_ errorCode: Int32) {
  286. print("export error \(errorCode)")
  287. exportResult(false)
  288. }
  289. public func exporterDidEnd(_ outputPath: String!) {
  290. print("export did end \(outputPath ?? "")")
  291. exportResult(true)
  292. }
  293. public func exporterDidCancel() {
  294. print("export did cancel")
  295. }
  296. }
  297. extension AliCameraView : AliyunIPlayerCallback, AliyunIRenderCallback{
  298. public func playerDidEnd() {
  299. }
  300. public func playProgress(_ playSec: Double, streamProgress streamSec: Double) {
  301. }
  302. public func playError(_ errorCode: Int32) {
  303. }
  304. public func seekDidEnd() {
  305. }
  306. public func playerDidStart() {
  307. }
  308. }