Forráskód Böngészése

Add: example修改,Android、iOS端适配完成,添加注释说明。

hwh97 5 éve
szülő
commit
033157ecad

+ 0 - 1
android/src/main/kotlin/com/i2edu/flutter_ali_camera/AliCameraView.kt

@@ -64,7 +64,6 @@ class AliCameraView(private val context: Context, private val binaryMessenger: B
         if (surfaceView != null) {
             surfaceView = null
         }
-        onDestroy()
     }
 
     override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {

+ 4 - 4
example/ios/Podfile

@@ -33,8 +33,11 @@ def parse_KV_file(file, separator='=')
 end
 
 target 'Runner' do
-  # Flutter Pod
   use_frameworks!
+  use_modular_headers!
+
+  # Flutter Pod
+
   copied_flutter_dir = File.join(__dir__, 'Flutter')
   copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
   copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
@@ -75,9 +78,6 @@ target 'Runner' do
   end
 end
 
-# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
-install! 'cocoapods', :disable_input_output_paths => true
-
 post_install do |installer|
   installer.pods_project.targets.each do |target|
     target.build_configurations.each do |config|

+ 20 - 14
example/ios/Runner.xcodeproj/project.pbxproj

@@ -9,10 +9,6 @@
 /* Begin PBXBuildFile section */
 		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
 		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
-		3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
-		3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
-		9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
-		9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
 		97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
@@ -28,8 +24,6 @@
 			dstPath = "";
 			dstSubfolderSpec = 10;
 			files = (
-				3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
-				9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
 			);
 			name = "Embed Frameworks";
 			runOnlyForDeploymentPostprocessing = 0;
@@ -40,14 +34,13 @@
 		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
 		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
 		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
-		3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
+		5501C9C024F4AE190041183C /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
 		7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
 		7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
 		929D52BAC39E5D0949DD8DFB /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
 		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
-		9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
 		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
 		97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
 		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
@@ -64,8 +57,6 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
-				3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
 				F91D63C9AA7C465339878974 /* Pods_Runner.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -94,9 +85,7 @@
 		9740EEB11CF90186004384FC /* Flutter */ = {
 			isa = PBXGroup;
 			children = (
-				3B80C3931E831B6300D905FE /* App.framework */,
 				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
-				9740EEBA1CF902C7004384FC /* Flutter.framework */,
 				9740EEB21CF90195004384FC /* Debug.xcconfig */,
 				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
 				9740EEB31CF90195004384FC /* Generated.xcconfig */,
@@ -126,6 +115,7 @@
 		97C146F01CF9000F007C117D /* Runner */ = {
 			isa = PBXGroup;
 			children = (
+				5501C9C024F4AE190041183C /* Runner-Bridging-Header.h */,
 				7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
 				7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
 				97C146FA1CF9000F007C117D /* Main.storyboard */,
@@ -233,7 +223,7 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
 		};
 		9740EEB61CF901F6004384FC /* Run Script */ = {
 			isa = PBXShellScriptBuildPhase;
@@ -247,7 +237,7 @@
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
-			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
 		};
 		C023E04C78FE087C6748E4CE /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
@@ -277,9 +267,22 @@
 			files = (
 			);
 			inputPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
+				"${PODS_ROOT}/AlivcConan/AlivcConan.framework",
+				"${BUILT_PRODUCTS_DIR}/AliyunOSSiOS/AliyunOSSiOS.framework",
+				"${PODS_ROOT}/AliyunVideoSDKStd/AliyunVideoCore.framework",
+				"${PODS_ROOT}/../Flutter/Flutter.framework",
+				"${PODS_ROOT}/QuCore-ThirdParty/frameworks/alivcffmpeg.framework",
+				"${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework",
 			);
 			name = "[CP] Embed Pods Frameworks";
 			outputPaths = (
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AlivcConan.framework",
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AliyunOSSiOS.framework",
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AliyunVideoCore.framework",
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework",
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/alivcffmpeg.framework",
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
@@ -379,6 +382,7 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				DEFINES_MODULE = NO;
 				DEVELOPMENT_TEAM = LRXRX75D5X;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
@@ -513,6 +517,7 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				DEFINES_MODULE = NO;
 				DEVELOPMENT_TEAM = LRXRX75D5X;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
@@ -541,6 +546,7 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+				DEFINES_MODULE = NO;
 				DEVELOPMENT_TEAM = LRXRX75D5X;
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (

+ 2 - 6
example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme

@@ -27,8 +27,6 @@
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       shouldUseLaunchSchemeArgsEnv = "YES">
-      <Testables>
-      </Testables>
       <MacroExpansion>
          <BuildableReference
             BuildableIdentifier = "primary"
@@ -38,8 +36,8 @@
             ReferencedContainer = "container:Runner.xcodeproj">
          </BuildableReference>
       </MacroExpansion>
-      <AdditionalOptions>
-      </AdditionalOptions>
+      <Testables>
+      </Testables>
    </TestAction>
    <LaunchAction
       buildConfiguration = "Debug"
@@ -61,8 +59,6 @@
             ReferencedContainer = "container:Runner.xcodeproj">
          </BuildableReference>
       </BuildableProductRunnable>
-      <AdditionalOptions>
-      </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
       buildConfiguration = "Profile"

+ 9 - 5
example/lib/main.dart

@@ -56,6 +56,7 @@ class _MyAppState extends State<MyApp> {
   final Completer<CameraViewController> _controller = Completer<CameraViewController>();
   StreamSubscription _durationSubscription; // 播放进度订阅
   final int duration = 5000;
+  final double videoAspect = 9 / 16;
   double value = 0;
   bool recording = false;
   List<String> recordPath = List();
@@ -81,8 +82,9 @@ class _MyAppState extends State<MyApp> {
 
   void _onPlatformViewCreated(CameraViewController controller) async {
     await controller.create(
+        iosTaskPath: await _findLocalPath(),
         recordOption: CameraRecordOption(
-            videoWidth: 720, videoHeight: 1280, quality: VideoQuality.SD));
+            videoWidth: 720, videoHeight: (720 * videoAspect).toInt(), quality: VideoQuality.SD));
     _durationSubscription = controller.recordUpdate.listen((result) {
       print("record update" + result.toString());
       if (result.containsKey("path")) {
@@ -120,7 +122,7 @@ class _MyAppState extends State<MyApp> {
       recordPath.map((f) => duration).toList(),
       CameraComposeOption(
           outputWidth: 480,
-          outputHeight: 480 * 16 ~/ 9,
+          outputHeight: (480 * videoAspect).toInt(),
           videoCodecs: VideoCodecs.H264_HARDWARE,
           quality: VideoQuality.SD,
           frameRate: 20,
@@ -131,22 +133,24 @@ class _MyAppState extends State<MyApp> {
 
   @override
   Widget build(BuildContext context) {
+    double width = MediaQuery.of(context).size.width;
     return Scaffold(
       backgroundColor: Colors.white,
       appBar: AppBar(
         title: const Text('Plugin example app'),
       ),
       body: ListView(
-        padding: EdgeInsets.symmetric(horizontal: 10),
+//        padding: EdgeInsets.symmetric(horizontal: 10),
         children: <Widget>[
           SizedBox(
-            width: double.maxFinite,
-            height: 200,
+            width: width,
+            height: width * videoAspect,
             child: CameraView(
               onCameraViewCreated: (controller) {
                 _controller.complete(controller);
                 _onPlatformViewCreated(controller);
               },
+              widgetRect: Rect.fromLTWH(0, 0, width, width * videoAspect),
             ),
           ),
           FutureBuilder(

+ 346 - 0
ios/Classes/AliCameraViewFactory.swift

@@ -0,0 +1,346 @@
+//
+//  AliCameraViewFactory.swift
+//  AliyunOSSiOS
+//
+//  Created by weihong huang on 2020/8/20.
+//
+
+import Flutter
+import Foundation
+import AliyunVideoSDKPro
+
+public class AliCameraViewFactory : NSObject, FlutterPlatformViewFactory {
+    var messeneger: FlutterBinaryMessenger!
+    
+    public func create(withFrame frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?) -> FlutterPlatformView {
+        return AliCameraView(frame: frame, viewIdentifier: viewId, arguments: args, binaryMessenger: self.messeneger)
+    }
+    
+    init(messeneger: FlutterBinaryMessenger) {
+        super.init()
+        self.messeneger = messeneger
+    }
+    
+    public func createArgsCodec() -> FlutterMessageCodec & NSObjectProtocol {
+        return FlutterStandardMessageCodec.sharedInstance()
+    }
+}
+
+public class AliCameraView : NSObject, FlutterPlatformView, AliyunIRecorderDelegate, AliyunIExporterCallback {
+    fileprivate var uiView: UIView = UIView()
+    fileprivate var recorder: AliyunIRecorder!
+    fileprivate var methodChannel: FlutterMethodChannel!
+    fileprivate var taskPath: String!
+    fileprivate var aliyunEditor: AliyunEditor!
+    fileprivate var exportResult: FlutterResult!
+    
+    init(frame: CGRect, viewIdentifier viewId: Int64, arguments args: Any?, binaryMessenger: FlutterBinaryMessenger) {
+        super.init()
+        // init view size
+        let dict = args as! [String: Any]
+        self.uiView.frame = CGRect.init(x: dict["x"] as? Float64 ?? 0,
+                                        y: dict["y"] as? Float64 ?? 0,
+                                        width: dict["width"] as? Float64 ?? Float64(UIScreen.main.bounds.width),
+                                        height: dict["height"] as? Float64 ?? Float64(UIScreen.main.bounds.height))
+        // init method channel
+        self.methodChannel = FlutterMethodChannel(name: "com.i2edu.cameraLib/camera_view_\(viewId)", binaryMessenger: binaryMessenger)
+        self.methodChannel?.setMethodCallHandler ({
+            [weak self]
+            (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
+            if let this = self {
+                this.onMethonCall(call: call, result: result)
+            }
+        })
+    }
+    
+    public func view() -> UIView {
+        return self.uiView
+    }
+    
+    func onMethonCall(call: FlutterMethodCall, result: @escaping FlutterResult) {
+        let args = call.arguments as? [String: Any]
+        switch call.method {
+        case "create":
+            let recordOptions = args?["recordOption"] as? [String: Any]
+            let taskPath = args?["taskPath"] as! String
+            do {
+                try setUpCameraView(recordOption: recordOptions!, taskPath: taskPath)
+                result(true)
+            } catch {
+                result(false)
+            }
+            break
+        case "startPreview":
+            do {
+                try startPreview()
+                result(true)
+            } catch {
+                result(false)
+            }
+            break;
+        case "stopPreview":
+            do {
+                try stopPreview()
+                result(true)
+            } catch {
+                result(false)
+            }
+            break;
+        case "switchCamera":
+            do {
+                try switchCamera()
+                result(true)
+            } catch {
+                result(false)
+            }
+            break;
+        case "setBeauty":
+            do {
+                try setBeauty(level: args?["level"] as? Int32 ?? 0)
+                result(true)
+            } catch {
+                result(false)
+            }
+            break;
+        case "setFilter":
+            do {
+                try setFilter(path: args?["path"] as? String)
+                result(true)
+            } catch {
+                result(false)
+            }
+            break;
+        case "startRecord":
+            do {
+                try startRecord(
+                    maxDuration: args?["max"] as! Int32,
+                    recordPath: args?["recordPath"] as! String)
+                result(true)
+            } catch {
+                result(FlutterError.init(code: "1001", message: error.localizedDescription, details: nil))
+            }
+            break
+        case "startCompose":
+            do {
+                self.exportResult = result
+                let composeOption = args?["composeOption"] as! [String: Any]
+                try startCompose(
+                    outputPath: args?["outputPath"] as! String,
+                    bgmPath: args?["bgmPath"] as? String,
+                    paths: args?["paths"] as! [String],
+                    durations: args?["durations"] as! [Int32],
+                    composeOption: composeOption
+                )
+            } catch {
+                result(FlutterError.init(code: "1002", message: error.localizedDescription, details: nil))
+            }
+            break
+        case "onDestroy":
+            do {
+                try onDestroy()
+                result(true)
+            } catch {
+                result(false)
+            }
+            break
+        default:
+            result(FlutterMethodNotImplemented)
+        }
+    }
+    
+    func setUpCameraView(recordOption: [String: Any], taskPath: String) throws {
+        self.taskPath = taskPath
+
+        self.recorder = AliyunIRecorder.init(delegate: self, videoSize: CGSize(width: recordOption["videoWidth"] as! Int, height: recordOption["videoHeight"] as! Int))
+        let keys = recordOption.keys
+        if (keys.contains("fps")) {
+            self.recorder.recordFps = recordOption["fps"] as! Int32
+        }
+        if (keys.contains("videoCodecs")) {
+            if (recordOption["videoCodecs"] as! Int == 0) {
+                self.recorder.encodeMode = 0
+            } else {
+                self.recorder.encodeMode = 1
+            }
+        }
+        if (keys.contains("quality")) {
+            self.recorder.videoQuality = AliyunVideoQuality.init(rawValue: recordOption["quality"] as! Int) ?? AliyunVideoQuality.medium
+        }
+        if (keys.contains("videoBitrate")) {
+            self.recorder.bitrate = recordOption["videoBitrate"] as! Int32
+        }
+        if (keys.contains("gop")) {
+            self.recorder.gop = recordOption["gop"] as! Int32
+        }
+        
+        self.recorder?.beautifyStatus = false
+        self.recorder?.beautifyValue = 0
+        self.recorder?.preview = self.uiView
+    }
+    
+    func startPreview() throws {
+        self.recorder?.startPreview(withPositon: AliyunIRecorderCameraPosition.front)
+    }
+    
+    func stopPreview() throws {
+        self.recorder?.stopPreview()
+    }
+    
+    func switchCamera() throws {
+        self.recorder?.switchCameraPosition()
+    }
+    
+    func setBeauty(level: Int32) throws {
+        self.recorder?.beautifyStatus = level != 0
+        self.recorder?.beautifyValue = level
+    }
+    
+    func setFilter(path: String?) throws {
+        if path == nil || path?.count == 0 {
+            recorder?.deleteFilter()
+        } else {
+            self.recorder?.apply(AliyunEffectFilter.init(file: path))
+        }
+    }
+    
+    func startRecord(maxDuration: Int32, recordPath: String) throws {
+        recorder?.taskPath = taskPath
+        recorder?.clipManager?.deleteAllPart()
+        recorder?.outputPath = recordPath
+        recorder?.clipManager?.maxDuration = CGFloat(Float(maxDuration) / 1000.0)
+        recorder?.startRecording()
+    }
+    
+    func startCompose(outputPath: String, bgmPath: String?, paths: [String], durations: [Int32], composeOption: [String: Any]) throws {
+        // set up AliyunImporter
+        let importer = AliyunImporter.init(
+            path: self.taskPath,
+            outputSize: CGSize(width: composeOption["outputWidth"] as! CGFloat, height: composeOption["outputHeight"] as! CGFloat))
+        let composeParam = AliyunVideoParam.init()
+        let keys = composeOption.keys
+        if keys.contains("gop") {
+            composeParam.gop = composeOption["gop"] as! Int32
+        }
+        if keys.contains("bitrate") {
+            composeParam.bitrate = composeOption["bitrate"] as! Int32
+        }
+        if keys.contains("frameRate") {
+            composeParam.fps = composeOption["frameRate"] as! Int32
+        }
+        if keys.contains("videoCodecs") {
+            var codecType:AliyunVideoCodecType
+            let type = composeOption["videoCodecs"] as! Int32
+            if type == 0 {
+                codecType = AliyunVideoCodecType.hardware
+            } else if type == 1 {
+                codecType = AliyunVideoCodecType.openh264
+            } else if type == 2 {
+                codecType = AliyunVideoCodecType.fFmpeg
+            } else {
+                codecType = AliyunVideoCodecType.typeAuto
+            }
+            composeParam.codecType = codecType
+        }
+        if keys.contains("quality") {
+            composeParam.videoQuality =
+                AliyunVideoQuality.init(rawValue: composeOption["quality"] as! Int) ?? AliyunVideoQuality.medium
+        }
+        if keys.contains("scaleMode") {
+            composeParam.scaleMode =
+                AliyunScaleMode.init(rawValue: composeOption["scaleMode"] as! Int) ?? AliyunScaleMode.fit
+        }
+        for index in 0..<paths.count {
+            importer?.addMediaClip(AliyunClip.init(videoPath: paths[index], animDuration: 0))
+        }
+        importer?.setVideoParam(composeParam)
+        importer?.generateProjectConfigure()
+        // set up AliyunEditor
+        self.aliyunEditor = AliyunEditor.init(path: self.taskPath, preview: nil)
+        if bgmPath != nil && bgmPath?.count != 0 {
+            self.aliyunEditor.apply(AliyunEffectMusic.init(file: bgmPath))
+        }
+        self.aliyunEditor.delegate = self
+        self.aliyunEditor.startEdit()
+        
+        let exporter = self.aliyunEditor.getExporter()
+        exporter?.startExport(outputPath)
+    }
+    
+    func onDestroy() throws {
+        try stopPreview()
+        recorder?.stopRecording()
+        recorder?.destroy()
+        recorder = nil
+    }
+    
+    // ----- record delegate
+    
+    public func recorderDeviceAuthorization(_ status: AliyunIRecorderDeviceAuthor) {
+        if status == AliyunIRecorderDeviceAuthor.enabled {
+            print("sdk enabled")
+        } else {
+            print("sdk disabled")
+        }
+    }
+    
+    public func recorderVideoDuration(_ duration: CGFloat) {
+        print("record update \(duration)")
+        methodChannel.invokeMethod("recordUpdate", arguments: ["progress": Int(duration * 1000)])
+    }
+    
+    public func recorderDidStopWithMaxDuration() {
+        print("record stop with max duration")
+        recorder?.finishRecording()
+    }
+    
+    public func recorderDidFinishRecording() {
+        print("record completed")
+        methodChannel.invokeMethod("recordUpdate", arguments: ["progress": Int((recorder?.clipManager?.maxDuration ?? 0) * 1000), "path": recorder?.outputPath ?? ""])
+    }
+    
+    public func recoderError(_ error: Error!) {
+        print("record error \(error.localizedDescription)")
+        recorder?.finishRecording()
+    }
+    
+    // ----- export delegate
+    public func exporterDidStart() {
+        print("export did start")
+    }
+    
+    public func exportProgress(_ progress: Float) {
+        print("export progress \(progress)")
+    }
+    
+    public func exportError(_ errorCode: Int32) {
+        print("export error \(errorCode)")
+        exportResult(false)
+    }
+    
+    public func exporterDidEnd(_ outputPath: String!) {
+        print("export did end \(outputPath ?? "")")
+        exportResult(true)
+    }
+    
+    public func exporterDidCancel() {
+        print("export did cancel")
+    }
+}
+
+
+extension AliCameraView : AliyunIPlayerCallback, AliyunIRenderCallback{
+    public func playerDidEnd() {
+    }
+    
+    public func playProgress(_ playSec: Double, streamProgress streamSec: Double) {
+    }
+    
+    public func playError(_ errorCode: Int32) {
+    }
+    
+    public func seekDidEnd() {
+    }
+    
+    public func playerDidStart() {
+    }
+}

+ 9 - 13
ios/Classes/FlutterAliCameraPlugin.m

@@ -1,20 +1,16 @@
 #import "FlutterAliCameraPlugin.h"
+#if __has_include(<flutter_ali_camera/flutter_ali_camera-Swift.h>)
+#import <flutter_ali_camera/flutter_ali_camera-Swift.h>
+#else
+// Support project import fallback if the generated compatibility header
+// is not copied when this plugin is created as a library.
+// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816
+#import "flutter_ali_camera-Swift.h"
+#endif
 
 @implementation FlutterAliCameraPlugin
 + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
-  FlutterMethodChannel* channel = [FlutterMethodChannel
-      methodChannelWithName:@"flutter_ali_camera"
-            binaryMessenger:[registrar messenger]];
-  FlutterAliCameraPlugin* instance = [[FlutterAliCameraPlugin alloc] init];
-  [registrar addMethodCallDelegate:instance channel:channel];
-}
-
-- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
-  if ([@"getPlatformVersion" isEqualToString:call.method]) {
-    result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
-  } else {
-    result(FlutterMethodNotImplemented);
-  }
+   [SwiftFlutterAliCameraPlugin registerWithRegistrar:registrar];
 }
 
 @end

+ 4 - 0
ios/Classes/SwiftFlutterAliCameraPlugin.swift

@@ -6,6 +6,10 @@ public class SwiftFlutterAliCameraPlugin: NSObject, FlutterPlugin {
     let channel = FlutterMethodChannel(name: "flutter_ali_camera", binaryMessenger: registrar.messenger())
     let instance = SwiftFlutterAliCameraPlugin()
     registrar.addMethodCallDelegate(instance, channel: channel)
+    
+    // register view factory
+    let factory = AliCameraViewFactory(messeneger: registrar.messenger())
+    registrar.register(factory, withId: "com.i2edu.cameraLib")
   }
 
   public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {

+ 3 - 3
lib/camera_option.dart

@@ -3,8 +3,8 @@ class CameraRecordOption {
   int videoHeight;
   int fps;
   VideoCodecs videoCodecs;
-  int crf;
-  int encoderFps;
+  int crf; // only use in android
+  int encoderFps; // only use in android
   VideoQuality quality;
   int videoBitrate;
   int gop;
@@ -41,7 +41,7 @@ class CameraComposeOption {
   int outputWidth;
   int outputHeight;
   int frameRate;
-  int crf;
+  int crf; // only use in android
   int gop;
   int bitrate;
   double scaleRate;

+ 8 - 2
lib/camera_view.dart

@@ -9,8 +9,9 @@ typedef OnCameraViewCreated(CameraViewController controller);
 
 class CameraView extends StatefulWidget {
   final OnCameraViewCreated onCameraViewCreated;
+  final Rect widgetRect; // use for measure view size on iOS
 
-  const CameraView({Key key, this.onCameraViewCreated}) : super(key: key);
+  const CameraView({Key key, this.onCameraViewCreated, this.widgetRect}) : super(key: key);
 
   @override
   _CameraViewState createState() => _CameraViewState();
@@ -30,7 +31,12 @@ class _CameraViewState extends State<CameraView> {
           )
         : UiKitView(
             viewType: viewType,
-            creationParams: {},
+            creationParams: {
+              "x": widget.widgetRect.left,
+              "y": widget.widgetRect.top,
+              "width": widget.widgetRect.width,
+              "height": widget.widgetRect.height,
+            },
             creationParamsCodec: const StandardMessageCodec(),
             onPlatformViewCreated: onPlatformViewCreated,
           );

+ 4 - 2
lib/camera_view_controller.dart

@@ -16,8 +16,9 @@ class CameraViewController {
     _methodChannel.setMethodCallHandler(_onMethodCallHandler);
   }
 
-  Future<void> create({@required CameraRecordOption recordOption}) async {
-    return _methodChannel.invokeMethod("create", {"recordOption": recordOption.toMap()});
+  Future<void> create({@required CameraRecordOption recordOption, String iosTaskPath}) async {
+    return _methodChannel.invokeMethod("create",
+        {"recordOption": recordOption.toMap(), "iosTaskPath": iosTaskPath});
   }
 
   Future<void> startPreview() {
@@ -63,6 +64,7 @@ class CameraViewController {
     if (!_recordController.isClosed) {
       _recordController.close();
     }
+    return _methodChannel.invokeMethod("onDestroy");
   }
 
   Future _onMethodCallHandler(MethodCall call) {