소스 검색

update: 重写配音代码、更新至v2分支

hwh97 5 년 전
부모
커밋
31417019b9
32개의 변경된 파일770개의 추가작업 그리고 1530개의 파일을 삭제
  1. 123 225
      .idea/workspace.xml
  2. 2 2
      android/build.gradle
  3. 28 241
      android/src/main/kotlin/cn/i2edu/dubbing_lib/DubbingLibPlugin.kt
  4. 0 41
      android/src/main/kotlin/cn/i2edu/dubbing_lib/api/DownloadHelper.kt
  5. 0 13
      android/src/main/kotlin/cn/i2edu/dubbing_lib/api/DownloadService.kt
  6. 0 11
      android/src/main/kotlin/cn/i2edu/dubbing_lib/listener/DownloadListener.kt
  7. 0 141
      android/src/main/kotlin/cn/i2edu/dubbing_lib/util/DownloadUtil.kt
  8. 8 8
      android/src/main/kotlin/cn/i2edu/dubbing_lib/util/MixinPaintedUtil.kt
  9. 0 221
      android/src/main/kotlin/cn/i2edu/dubbing_lib/util/MixinVideoUtil.kt
  10. 6 11
      android/src/main/kotlin/cn/i2edu/dubbing_lib/util/Mp4ParserMixer.java
  11. 129 0
      android/src/main/kotlin/cn/i2edu/dubbing_lib/util/VideoAudioMixer.java
  12. 11 0
      example/android/.gitignore
  13. 6 0
      example/android/app/src/main/kotlin/cn/i2edu/example/MainActivity.kt
  14. 1 1
      example/android/gradle/wrapper/gradle-wrapper.properties
  15. 32 0
      example/ios/.gitignore
  16. 1 0
      example/ios/Flutter/.last_build_id
  17. 87 0
      example/ios/Podfile
  18. 22 0
      example/ios/Podfile.lock
  19. 60 73
      example/ios/Runner.xcodeproj/project.pbxproj
  20. 8 0
      example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  21. 8 0
      example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  22. 8 0
      example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
  23. 8 0
      example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
  24. 1 12
      example/ios/Runner/Info.plist
  25. 0 1
      example/ios/Runner/Runner-Bridging-Header.h
  26. 29 107
      example/pubspec.lock
  27. 1 5
      example/pubspec.yaml
  28. 10 31
      ios/Classes/DubbingComposer.swift
  29. 27 175
      ios/Classes/SwiftDubbingLibPlugin.swift
  30. 87 0
      ios/Classes/VideoComposer.swift
  31. 24 126
      lib/dubbing_lib.dart
  32. 43 85
      pubspec.lock

+ 123 - 225
.idea/workspace.xml

@@ -1,18 +1,77 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
+  <component name="BranchesTreeState">
+    <expand>
+      <path>
+        <item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
+        <item name="LOCAL_ROOT" type="e8cecc67:BranchNodeDescriptor" />
+      </path>
+      <path>
+        <item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
+        <item name="REMOTE_ROOT" type="e8cecc67:BranchNodeDescriptor" />
+      </path>
+      <path>
+        <item name="ROOT" type="e8cecc67:BranchNodeDescriptor" />
+        <item name="REMOTE_ROOT" type="e8cecc67:BranchNodeDescriptor" />
+        <item name="GROUP_NODE:origin" type="e8cecc67:BranchNodeDescriptor" />
+      </path>
+    </expand>
+    <select />
+  </component>
   <component name="ChangeListManager">
     <list default="true" id="96a4f947-f66a-4efc-b495-ae979b3315bb" name="Default Changelist" comment="">
+      <change afterPath="$PROJECT_DIR$/ios/Classes/VideoComposer.swift" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/android/build.gradle" beforeDir="false" afterPath="$PROJECT_DIR$/android/build.gradle" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/DubbingLibPlugin.kt" beforeDir="false" afterPath="$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/DubbingLibPlugin.kt" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/api/DownloadHelper.kt" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/api/DownloadService.kt" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/audioUtils/VideoAudioMixer.java" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/listener/DownloadListener.kt" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/util/DownloadUtil.kt" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/util/MixinPaintedUtil.kt" beforeDir="false" afterPath="$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/util/MixinPaintedUtil.kt" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/util/MixinVideoUtil.kt" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/android/gradle/wrapper/gradle-wrapper.properties" beforeDir="false" afterPath="$PROJECT_DIR$/example/android/gradle/wrapper/gradle-wrapper.properties" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Flutter/AppFrameworkInfo.plist" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Flutter/Debug.xcconfig" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Flutter/Release.xcconfig" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner.xcodeproj/project.pbxproj" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner.xcworkspace/contents.xcworkspacedata" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/AppDelegate.swift" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Base.lproj/LaunchScreen.storyboard" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Base.lproj/Main.storyboard" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Info.plist" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/ios/Runner/Runner-Bridging-Header.h" beforeDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/pubspec.lock" beforeDir="false" afterPath="$PROJECT_DIR$/example/pubspec.lock" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/example/pubspec.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/example/pubspec.yaml" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/ios/Classes/DubbingComposer.swift" beforeDir="false" afterPath="$PROJECT_DIR$/ios/Classes/DubbingComposer.swift" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/ios/Classes/SwiftDubbingLibPlugin.swift" beforeDir="false" afterPath="$PROJECT_DIR$/ios/Classes/SwiftDubbingLibPlugin.swift" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/lib/dubbing_lib.dart" beforeDir="false" afterPath="$PROJECT_DIR$/lib/dubbing_lib.dart" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/pubspec.lock" beforeDir="false" afterPath="$PROJECT_DIR$/pubspec.lock" afterDir="false" />
     </list>
-    <ignored path="$PROJECT_DIR$/.dart_tool/" />
-    <ignored path="$PROJECT_DIR$/.idea/" />
-    <ignored path="$PROJECT_DIR$/.pub/" />
-    <ignored path="$PROJECT_DIR$/build/" />
-    <ignored path="$PROJECT_DIR$/example/.pub/" />
-    <ignored path="$PROJECT_DIR$/example/build/" />
-    <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
     <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
@@ -21,47 +80,6 @@
   <component name="DefaultGradleProjectSettings">
     <option name="isMigrated" value="true" />
   </component>
-  <component name="ExecutionTargetManager" SELECTED_TARGET="Pixel_2_API_29" />
-  <component name="FileEditorManager">
-    <leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
-      <file pinned="false" current-in-tab="true">
-        <entry file="file://$PROJECT_DIR$/lib/dubbing_lib.dart">
-          <provider selected="true" editor-type-id="text-editor">
-            <state relative-caret-position="174">
-              <caret line="67" column="31" lean-forward="true" selection-start-line="67" selection-start-column="31" selection-end-line="67" selection-end-column="31" />
-            </state>
-          </provider>
-        </entry>
-      </file>
-      <file pinned="false" current-in-tab="false">
-        <entry file="file://$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/DubbingLibPlugin.kt">
-          <provider selected="true" editor-type-id="text-editor">
-            <state relative-caret-position="630">
-              <caret line="60" column="65" selection-start-line="60" selection-start-column="54" selection-end-line="60" selection-end-column="65" />
-            </state>
-          </provider>
-        </entry>
-      </file>
-      <file pinned="false" current-in-tab="false">
-        <entry file="file://$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/audioUtils/compose/AudioComposer.java">
-          <provider selected="true" editor-type-id="text-editor">
-            <state relative-caret-position="2025">
-              <caret line="142" column="52" selection-start-line="142" selection-start-column="32" selection-end-line="142" selection-end-column="52" />
-            </state>
-          </provider>
-        </entry>
-      </file>
-      <file pinned="false" current-in-tab="false">
-        <entry file="file://$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/audioUtils/compose/FileFunction.java">
-          <provider selected="true" editor-type-id="text-editor">
-            <state relative-caret-position="585">
-              <caret line="45" column="62" selection-start-line="45" selection-start-column="35" selection-end-line="45" selection-end-column="62" />
-            </state>
-          </provider>
-        </entry>
-      </file>
-    </leaf>
-  </component>
   <component name="FindInProjectRecents">
     <findStrings>
       <find>startCompose</find>
@@ -87,78 +105,50 @@
   <component name="Git.Settings">
     <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
   </component>
-  <component name="IdeDocumentHistory">
-    <option name="CHANGED_PATHS">
-      <list>
-        <option value="$PROJECT_DIR$/.gitignore" />
-        <option value="$PROJECT_DIR$/android/build.gradle" />
-        <option value="$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/DubbingLibPlugin.kt" />
-        <option value="$PROJECT_DIR$/lib/dubbing_lib.dart" />
-        <option value="$PROJECT_DIR$/example/lib/main.dart" />
-      </list>
-    </option>
-  </component>
   <component name="ProjectFrameBounds">
     <option name="y" value="23" />
     <option name="width" value="1346" />
     <option name="height" value="877" />
   </component>
+  <component name="ProjectId" id="1hfMQuWFLyb0dAgqcLX2tp3IDR3" />
   <component name="ProjectLevelVcsManager" settingsEditedManually="true" />
-  <component name="ProjectView">
-    <navigator currentView="ProjectPane" proportions="" version="1">
-      <foldersAlwaysOnTop value="true" />
-    </navigator>
-    <panes>
-      <pane id="ProjectPane">
-        <subPane>
-          <expand>
-            <path>
-              <item name="dubbing_lib" type="b2602c69:ProjectViewProjectNode" />
-              <item name="dubbing_lib" type="462c0819:PsiDirectoryNode" />
-            </path>
-            <path>
-              <item name="dubbing_lib" type="b2602c69:ProjectViewProjectNode" />
-              <item name="dubbing_lib" type="462c0819:PsiDirectoryNode" />
-              <item name="android" type="462c0819:PsiDirectoryNode" />
-            </path>
-            <path>
-              <item name="dubbing_lib" type="b2602c69:ProjectViewProjectNode" />
-              <item name="dubbing_lib" type="462c0819:PsiDirectoryNode" />
-              <item name="lib" type="462c0819:PsiDirectoryNode" />
-            </path>
-          </expand>
-          <select />
-        </subPane>
-      </pane>
-      <pane id="PackagesPane" />
-      <pane id="Scope" />
-    </panes>
+  <component name="ProjectReloadState">
+    <option name="STATE" value="1" />
+  </component>
+  <component name="ProjectViewState">
+    <option name="hideEmptyMiddlePackages" value="true" />
+    <option name="showLibraryContents" value="true" />
   </component>
   <component name="PropertiesComponent">
     <property name="SHARE_PROJECT_CONFIGURATION_FILES" value="true" />
+    <property name="WebServerToolWindowFactoryState" value="false" />
+    <property name="aspect.path.notification.shown" value="true" />
     <property name="dart.analysis.tool.window.force.activate" value="false" />
+    <property name="dart.analysis.tool.window.visible" value="false" />
+    <property name="go.import.settings.migrated" value="true" />
     <property name="last_opened_file_path" value="$PROJECT_DIR$" />
+    <property name="nodejs_package_manager_path" value="npm" />
     <property name="settings.editor.selected.configurable" value="flutter.settings" />
     <property name="show.migrate.to.gradle.popup" value="false" />
   </component>
-  <component name="RunDashboard">
-    <option name="ruleStates">
-      <list>
-        <RuleState>
-          <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
-        </RuleState>
-        <RuleState>
-          <option name="name" value="StatusDashboardGroupingRule" />
-        </RuleState>
-      </list>
-    </option>
-  </component>
   <component name="RunManager">
+    <configuration default="true" type="ArquillianJUnit" factoryName="" nameIsGenerated="true">
+      <option name="arquillianRunConfiguration">
+        <value>
+          <option name="containerStateName" value="" />
+        </value>
+      </option>
+      <option name="TEST_OBJECT" value="class" />
+      <method v="2">
+        <option name="Make" enabled="true" />
+      </method>
+    </configuration>
     <configuration name="main.dart" type="FlutterRunConfigurationType" factoryName="Flutter">
       <option name="filePath" value="$PROJECT_DIR$/example/lib/main.dart" />
       <method v="2" />
     </configuration>
   </component>
+  <component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
   <component name="SvnConfiguration">
     <configuration />
   </component>
@@ -169,62 +159,29 @@
       <option name="number" value="Default" />
       <option name="presentableId" value="Default" />
       <updated>1571458484872</updated>
+      <workItem from="1600397686741" duration="6647000" />
     </task>
     <servers />
   </component>
-  <component name="ToolWindowManager">
-    <frame x="0" y="23" width="1346" height="877" extended-state="0" />
-    <editor active="true" />
-    <layout>
-      <window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.2231595" />
-      <window_info id="Captures" order="1" side_tool="true" />
-      <window_info id="Structure" order="2" side_tool="true" />
-      <window_info id="Image Layers" order="3" />
-      <window_info id="Designer" order="4" />
-      <window_info id="Build Variants" order="5" side_tool="true" />
-      <window_info id="Resources Explorer" order="6" />
-      <window_info id="Capture Tool" order="7" />
-      <window_info id="Favorites" order="8" side_tool="true" />
-      <window_info anchor="bottom" id="Dart Analysis" order="0" weight="0.32956153" />
-      <window_info anchor="bottom" id="Run" order="1" weight="0.3286119" />
-      <window_info anchor="bottom" id="TODO" order="2" />
-      <window_info anchor="bottom" id="Android Profiler" order="3" show_stripe_button="false" />
-      <window_info anchor="bottom" id="Logcat" order="4" />
-      <window_info anchor="bottom" id="Debug" order="5" />
-      <window_info anchor="bottom" id="Terminal" order="6" weight="0.18743229" />
-      <window_info anchor="bottom" id="Event Log" order="7" side_tool="true" />
-      <window_info anchor="bottom" id="Flutter Performance" order="8" side_tool="true" />
-      <window_info anchor="bottom" id="Version Control" order="9" weight="0.37028015" />
-      <window_info anchor="bottom" id="Messages" order="10" visible="true" weight="0.32939634" />
-      <window_info anchor="right" id="Device File Explorer" order="0" side_tool="true" />
-      <window_info anchor="right" id="Capture Analysis" order="1" />
-      <window_info anchor="right" id="Theme Preview" order="2" />
-      <window_info anchor="right" id="Flutter Inspector" order="3" />
-      <window_info anchor="right" id="Flutter Outline" order="4" weight="0.32995737" />
-      <window_info anchor="right" id="Palette&#9;" order="5" />
-    </layout>
+  <component name="TypeScriptGeneratedFilesManager">
+    <option name="version" value="3" />
+  </component>
+  <component name="Vcs.Log.History.Properties">
+    <option name="COLUMN_ORDER">
+      <list>
+        <option value="0" />
+        <option value="2" />
+        <option value="3" />
+        <option value="1" />
+      </list>
+    </option>
   </component>
   <component name="Vcs.Log.Tabs.Properties">
     <option name="TAB_STATES">
       <map>
         <entry key="MAIN">
           <value>
-            <State>
-              <option name="RECENTLY_FILTERED_USER_GROUPS">
-                <collection />
-              </option>
-              <option name="RECENTLY_FILTERED_BRANCH_GROUPS">
-                <collection />
-              </option>
-              <option name="COLUMN_ORDER">
-                <list>
-                  <option value="0" />
-                  <option value="1" />
-                  <option value="2" />
-                  <option value="3" />
-                </list>
-              </option>
-            </State>
+            <State />
           </value>
         </entry>
       </map>
@@ -243,83 +200,24 @@
         </entry>
       </map>
     </option>
-  </component>
-  <component name="editorHistoryManager">
-    <entry file="file://C:/flutter/flutter_windows_v1.9.1+hotfix.4-stable/flutter/packages/flutter/lib/src/services/platform_channel.dart" />
-    <entry file="file://C:/flutter/flutter_windows_v1.9.1+hotfix.4-stable/flutter/bin/cache/pkg/sky_engine/lib/async/stream_controller.dart" />
-    <entry file="file://C:/flutter/flutter_windows_v1.9.1+hotfix.4-stable/flutter/bin/cache/pkg/sky_engine/lib/async/broadcast_stream_controller.dart" />
-    <entry file="file://$PROJECT_DIR$/android/build.gradle">
-      <provider selected="true" editor-type-id="text-editor">
-        <state relative-caret-position="88">
-          <caret line="4" column="33" selection-start-line="4" selection-start-column="33" selection-end-line="4" selection-end-column="33" />
-        </state>
-      </provider>
-    </entry>
-    <entry file="file://$PROJECT_DIR$/android/src/main/AndroidManifest.xml">
-      <provider selected="true" editor-type-id="text-editor">
-        <state relative-caret-position="76">
-          <caret line="4" column="69" selection-start-line="4" selection-start-column="69" selection-end-line="4" selection-end-column="69" />
-        </state>
-      </provider>
-    </entry>
-    <entry file="file://$PROJECT_DIR$/README.md">
-      <provider selected="true" editor-type-id="text-editor">
-        <state relative-caret-position="266">
-          <caret line="14" selection-start-line="14" selection-end-line="14" />
-        </state>
-      </provider>
-    </entry>
-    <entry file="file://$PROJECT_DIR$/.gitignore">
-      <provider selected="true" editor-type-id="text-editor">
-        <state relative-caret-position="323">
-          <caret line="17" column="5" selection-start-line="17" selection-start-column="5" selection-end-line="17" selection-end-column="5" />
-        </state>
-      </provider>
-    </entry>
-    <entry file="file://$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/util/DownloadUtil.kt">
-      <provider selected="true" editor-type-id="text-editor">
-        <state relative-caret-position="361">
-          <caret line="19" column="98" selection-start-line="19" selection-start-column="92" selection-end-line="19" selection-end-column="98" />
-        </state>
-      </provider>
-    </entry>
-    <entry file="file://$PROJECT_DIR$/example/lib/main.dart">
-      <provider selected="true" editor-type-id="text-editor">
-        <state relative-caret-position="690">
-          <caret line="46" column="1" selection-start-line="46" selection-start-column="1" selection-end-line="46" selection-end-column="1" />
-          <folding>
-            <element signature="e#0#39#0" expanded="true" />
-          </folding>
-        </state>
-      </provider>
-    </entry>
-    <entry file="file://$PROJECT_DIR$/lib/dubbing_lib.dart">
-      <provider selected="true" editor-type-id="text-editor">
-        <state relative-caret-position="174">
-          <caret line="67" column="31" lean-forward="true" selection-start-line="67" selection-start-column="31" selection-end-line="67" selection-end-column="31" />
-        </state>
-      </provider>
-    </entry>
-    <entry file="file://$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/DubbingLibPlugin.kt">
-      <provider selected="true" editor-type-id="text-editor">
-        <state relative-caret-position="630">
-          <caret line="60" column="65" selection-start-line="60" selection-start-column="54" selection-end-line="60" selection-end-column="65" />
-        </state>
-      </provider>
-    </entry>
-    <entry file="file://$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/audioUtils/compose/AudioComposer.java">
-      <provider selected="true" editor-type-id="text-editor">
-        <state relative-caret-position="2025">
-          <caret line="142" column="52" selection-start-line="142" selection-start-column="32" selection-end-line="142" selection-end-column="52" />
-        </state>
-      </provider>
-    </entry>
-    <entry file="file://$PROJECT_DIR$/android/src/main/kotlin/cn/i2edu/dubbing_lib/audioUtils/compose/FileFunction.java">
-      <provider selected="true" editor-type-id="text-editor">
-        <state relative-caret-position="585">
-          <caret line="45" column="62" selection-start-line="45" selection-start-column="35" selection-end-line="45" selection-end-column="62" />
-        </state>
-      </provider>
-    </entry>
+    <option name="oldMeFiltersMigrated" value="true" />
+  </component>
+  <component name="WindowStateProjectService">
+    <state width="1398" height="244" key="GridCell.Tab.0.bottom" timestamp="1600399519810">
+      <screen x="0" y="0" width="1440" height="900" />
+    </state>
+    <state width="1398" height="244" key="GridCell.Tab.0.bottom/0.0.1440.900@0.0.1440.900" timestamp="1600399519810" />
+    <state width="1398" height="244" key="GridCell.Tab.0.center" timestamp="1600399519808">
+      <screen x="0" y="0" width="1440" height="900" />
+    </state>
+    <state width="1398" height="244" key="GridCell.Tab.0.center/0.0.1440.900@0.0.1440.900" timestamp="1600399519808" />
+    <state width="1398" height="244" key="GridCell.Tab.0.left" timestamp="1600399519807">
+      <screen x="0" y="0" width="1440" height="900" />
+    </state>
+    <state width="1398" height="244" key="GridCell.Tab.0.left/0.0.1440.900@0.0.1440.900" timestamp="1600399519807" />
+    <state width="1398" height="244" key="GridCell.Tab.0.right" timestamp="1600399519809">
+      <screen x="0" y="0" width="1440" height="900" />
+    </state>
+    <state width="1398" height="244" key="GridCell.Tab.0.right/0.0.1440.900@0.0.1440.900" timestamp="1600399519809" />
   </component>
 </project>

+ 2 - 2
android/build.gradle

@@ -36,7 +36,7 @@ android {
         }
     }
     defaultConfig {
-        minSdkVersion 16
+        minSdkVersion 21
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
     }
     lintOptions {
@@ -47,6 +47,6 @@ android {
 dependencies {
     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
     compileOnly  files('libs/flutter.jar')
-    implementation 'com.googlecode.mp4parser:isoparser:1.0-RC-37'
+    implementation 'com.googlecode.mp4parser:isoparser:1.1.22'
     implementation 'com.alibaba:fastjson:1.1.71.android'
 }

+ 28 - 241
android/src/main/kotlin/cn/i2edu/dubbing_lib/DubbingLibPlugin.kt

@@ -2,33 +2,19 @@ package cn.i2edu.dubbing_lib
 
 import android.annotation.SuppressLint
 import android.app.Activity
-import android.media.MediaPlayer
-import android.media.MediaRecorder
 import android.os.Build
-import android.text.TextUtils
 import android.view.View
-import cn.i2edu.dubbing_lib.audioUtils.AudioDecoder
-import cn.i2edu.dubbing_lib.audioUtils.AudioEncoder
-import cn.i2edu.dubbing_lib.audioUtils.compose.AudioComposer
 import cn.i2edu.dubbing_lib.util.*
 import io.flutter.plugin.common.MethodCall
 import io.flutter.plugin.common.MethodChannel
 import io.flutter.plugin.common.MethodChannel.MethodCallHandler
 import io.flutter.plugin.common.MethodChannel.Result
 import io.flutter.plugin.common.PluginRegistry.Registrar
-import java.io.File
-import java.io.IOException
-import java.util.*
 
 class DubbingLibPlugin : MethodCallHandler {
 
     private val activity: Activity
-    private var mediaRecorder: MediaRecorder? = null
-    private var mediaPlayer: MediaPlayer? = null
-    private var isRecording: Boolean = false
-    private var timer: Timer? = null
     private val pausableThreadPool: PausableThreadPool = PausableThreadPool(1)
-    private val Tag: String = "DubbingPlugin"
 
     companion object {
 
@@ -56,77 +42,14 @@ class DubbingLibPlugin : MethodCallHandler {
                     activity.window.decorView.systemUiVisibility = activity.window.decorView.systemUiVisibility.or(View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)
                 }
             }
-            "startRecord" -> {
-                val duration = call.argument<Int>("duration")!!
-                val fileName = call.argument<String>("fileName")!!
-                val index = call.argument<Int>("index")!!
-                val pathAudio = call.argument<String>("pathAudio")!!
-                val pathAudioDecode = call.argument<String>("pathAudioDecode")!!
-                // 开启录音
-                initMediaRecorder(pathAudio, pathAudioDecode, index, duration, fileName, result)
-            }
-            "playRecordAudio" -> {
-                val fileName = call.argument<String>("fileName")!!
-                playRecord(fileName, result)
-            }
-            "pauseRecordAudio" -> {
-                mediaPlayer?.pause()
-                result.success(true)
-            }
-            "getIsMediaPlayPause" -> {
-                result.success(mediaPlayer != null && !mediaPlayer!!.isPlaying)
-            }
-            "cleanAudioData" -> {
-                try {
-                    // 取得 video id, 根据前缀进行删除操作
-                    val videoId = call.argument<String>("videoId")!!
-                    val pathAudio = call.argument<String>("pathAudio")!!
-                    val pathAudioDecode = call.argument<String>("pathAudioDecode")!!
-                    deleteFileWithPrefix(videoId, pathAudio)
-                    deleteFileWithPrefix(videoId, pathAudioDecode)
-                    result.success(true)
-                } catch (e: Exception) {
-                    e.printStackTrace()
-                    result.error("1003", "clean cache file failed", null)
-                }
-            }
-            "startMixinAudio" -> {
-                try {
-                    MixinVideoUtil.getInstance(context = activity.applicationContext)
-                            .initParams(videoId = call.argument<String>("videoId")!!, bgmPath = call.argument<String>("bgmPath")!!,
-                                    videoPath = call.argument<String>("videoPath")!!, durationList = call.argument<List<Long>>("durationList")!!,
-                                    endTimeList = call.argument<List<Long>>("endTimeList")!!, audioDecodePaths = call.argument<List<String>>("audioDecodePaths")!!,
-                                    pathBgmDecodeDir = call.argument<String>("pathBgmDecode")!!, pathBgmRecordSyncDir = call.argument<String>("pathBgmRecordSync")!!,
-                                    pathBgmRecordDecodeSyncDir = call.argument<String>("pathBgmRecordDecodeSync")!!, pathVideoMixinDir = call.argument<String>("pathVideoMixin")!!,
-                                    mixinName = call.argument<String>("mixinName")!!)
-                            .setComposeCallBack(
-                                    object : MixinVideoCallBack {
-                                        override fun onResult(resultPath: String) {
-                                            activity.runOnUiThread {
-                                                result.success(resultPath)
-                                            }
-                                        }
-
-                                        override fun onError(message: String) {
-                                            activity.runOnUiThread {
-                                                result.error("1005", "message", null)
-                                            }
-                                        }
-                                    }
-                            )
-                            .startMixin()
-                } catch (e: Exception) {
-                    e.printStackTrace()
-                    result.error("1005", "mixin audio failed", null)
-                }
-            }
-            "startMixinPaintedAudio" -> {
+            "mixinAudio" -> {
                 try {
                     MixinPaintedUtil.getInstance(context = activity.applicationContext)
                             .initParams(audioPaths = call.argument<List<String>>("audioPaths")!!, bgmPath = call.argument<String>("bgmPath")!!,
                                     durationList = call.argument<List<Long>>("durationList")!!, endTimeList = call.argument<List<Long>>("endTimeList")!!,
-                                    audioDecodePath = call.argument<String>("audioDecodePath")!!, mixinFilePath = call.argument<String>("mixinFilePath")!!,
-                                    encodePath = call.argument<String>("encodePath")!!)
+                                    decodeDirPath = call.argument<String>("decodeDirPath")!!,
+                                    mixinFilePath = call.argument<String>("mixinFilePath")!!,
+                                    resultPath = call.argument<String>("resultPath")!!)
                             .setComposeCallBack(
                                     object : MixinPaintedCallBack {
                                         override fun onResult(resultPath: String) {
@@ -145,174 +68,38 @@ class DubbingLibPlugin : MethodCallHandler {
                             .startMixin()
                 } catch (e: Exception) {
                     e.printStackTrace()
-                    result.error("1006", "mixin painted failed", null)
+                    result.error("1005", "mixin audio failed", null)
                 }
             }
-            else -> result.notImplemented()
-        }
-    }
-
-    private fun initMediaRecorder(pathAudio: String, pathAudioDecode: String,
-                                  index: Int, duration: Int, fileName: String, result: Result) {
-        // create file
-        stopRecord()
-        // config media recorder
-        if (mediaRecorder == null) {
-            mediaRecorder = MediaRecorder()
-            mediaRecorder?.setAudioSource(MediaRecorder.AudioSource.MIC)
-            mediaRecorder?.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)// 设置所录制的音视频文件的格式
-            mediaRecorder?.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)// 设置所录制的声音的编码格式
-            mediaRecorder?.setAudioEncodingBitRate(96000)// 比特率
-            mediaRecorder?.setAudioChannels(2)// 通道
-            mediaRecorder?.setAudioSamplingRate(44100)// 采样率
-        }
-        // start record
-        startRecord(pathAudio, pathAudioDecode, index, duration, fileName, result)
-    }
+            "mixinVideoAndAudio" -> {
+                try {
+                    val resultPath = call.argument<String>("resultPath")!!
+                    val videoAudioMixer = Mp4ParserMixer(activity.applicationContext)
+                    videoAudioMixer.setListener(object : Mp4ParserMixer.VideoAudioMixListener {
+                        override fun mixSuccess() {
+                            activity.runOnUiThread {
+                                result.success(resultPath)
+                            }
+                        }
 
-    private fun startRecord(pathAudio: String, pathAudioDecode: String, index: Int, duration: Int, fileName: String, result: Result) {
-        if (mediaRecorder == null) return
-        val filePath = pathAudio + fileName
-        try {
-            mediaRecorder?.setOutputFile(filePath)
-            mediaRecorder?.prepare()
-            isRecording = true
-            mediaRecorder?.start()
-        } catch (e: IOException) {
-            e.printStackTrace()
-            isRecording = false
-        }
-        if (isRecording) {
-            var piecePosition = 0
-            if (timer != null) {
-                timer?.cancel()
-            }
-            timer = Timer()
-            // 这里参照少儿英语秀之前的逻辑
-            timer?.schedule(object : TimerTask() {
-                override fun run() {
-                    activity.runOnUiThread {
-                        isRecording = true
-                        piecePosition++
-                        // 传递进度
-                        channel.invokeMethod("recordProgress", mapOf("progress" to piecePosition * 5))
-                        while (piecePosition * 5 >= duration) {
-                            // 录制完毕
-                            try {
-                                result.success(filePath)
-                            } catch (e: Exception) {
-                                e.printStackTrace()
-                            } finally {
-                                timer?.cancel()
-                                stopRecord()
-                                // 解码音频
-                                Thread {
-                                    decodeRecord(pathAudioDecode, index, fileName, filePath)
-                                }.start()
+                        override fun mixFail(reason: String?) {
+                            activity.runOnUiThread {
+                                result.error("1005", "error: $reason", null)
                             }
-                            break
                         }
+                    })
+                    pausableThreadPool.execute {
+                        videoAudioMixer.muxAudioAndVideo(
+                                call.argument<String>("bgmPath")!!,
+                                call.argument<String>("videoPath")!!,
+                                resultPath)
                     }
+                } catch (e: Exception) {
+                    e.printStackTrace()
+                    result.error("1006", "mixin video failed", null)
                 }
-            }, 0, 5)
-        } else {
-            result.error("1002", "start record failed", null)
-        }
-    }
-
-    private fun decodeRecord(pathAudioDecode: String, index: Int, fileName: String, recordPath: String) {
-        pausableThreadPool.execute {
-            val decodePath = doDecode(fileName, recordPath, pathAudioDecode)
-            if (decodePath != null && File(decodePath).exists()) {
-                // 解码成功
-                activity.runOnUiThread {
-                    channel.invokeMethod("decodeResult", mapOf("index" to index, "path" to decodePath))
-                }
-            } else {
-                // 解码失败
-                activity.runOnUiThread {
-                    channel.invokeMethod("decodeResult", mapOf("index" to index, "path" to ""))
-                }
-            }
-        }
-    }
-
-    private fun stopRecord() {
-        try {
-            mediaRecorder?.stop()
-        } catch (e: Exception) {
-            // TODO from i2 dubit
-            // 这里有可能会抛出两种异常,一种是IllegalStateException,抛出这种异常是由于在没有start的情况下使用了stop
-            // 另外一种是RuntimeException, 这种异常的产生原因是在执行了start()过后马上执行stop(),具体可看mRecorder.stop()的源码注释
-            e.printStackTrace()
-        } finally {
-            isRecording = false
-            try {
-                mediaRecorder?.reset()
-                mediaRecorder?.release()
-                mediaRecorder = null
-            } catch (e: Exception) {
-                e.printStackTrace()
-            }
-        }
-    }
-
-    private fun playRecord(fileName: String, result: Result) {
-        resetMediaPlayer()
-        mediaPlayer = MediaPlayer()
-        mediaPlayer?.setDataSource(fileName)
-        mediaPlayer?.prepare()
-        mediaPlayer?.start()
-
-        mediaPlayer?.setOnCompletionListener {
-            resetMediaPlayer()
-            // 播放完成
-            result.success(true)
-        }
-    }
-
-    private fun resetMediaPlayer() {
-        mediaPlayer?.stop()
-        mediaPlayer?.release()
-        mediaPlayer = null
-    }
-
-    @Throws(Exception::class)
-    private fun deleteFileWithPrefix(video: String, path: String): Boolean {
-        val file = File(path)
-        if (!file.exists()) return false
-        // 取得所有文件
-        val files = file.listFiles()
-        if (files == null || files.isEmpty()) return false
-        for (fileExist in files) {
-            if (fileExist.isFile) {
-                // 验证文件名
-                if (fileExist.name.startsWith("${video}_")) {
-                    fileExist.delete()
-                }
-            }
-        }
-        return true
-    }
-
-    private fun doDecode(fileName: String, path: String, saveDirPath: String): String? {
-        try {
-            // 解码后的路径
-            val decodeFile = File(saveDirPath)
-            if (!decodeFile.exists()) {
-                decodeFile.mkdirs()
             }
-            val finalFile = File(decodeFile.absolutePath + "/" + fileName)
-            if (!finalFile.exists()) {
-                finalFile.createNewFile()
-            }
-            val audioDec = AudioDecoder
-                    .createDefualtDecoder(path)
-            audioDec.decodeToFile(finalFile.absolutePath)
-            return finalFile.absolutePath
-        } catch (e: Exception) {
-            e.printStackTrace()
+            else -> result.notImplemented()
         }
-        return null
     }
 }

+ 0 - 41
android/src/main/kotlin/cn/i2edu/dubbing_lib/api/DownloadHelper.kt

@@ -1,41 +0,0 @@
-//package cn.i2edu.dubbing_lib.api
-//
-//import okhttp3.OkHttpClient
-//import retrofit2.Retrofit
-//import java.util.concurrent.TimeUnit
-//
-//class DownloadHelper {
-//    companion object {
-//        val Tag: String = "DownloadHelper"
-//        // kotlin DLC mode
-//        val instance: DownloadHelper by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
-//            DownloadHelper()
-//        }
-//    }
-//
-//    private lateinit var mRetrofit: Retrofit
-//    private lateinit var mHttpClient: OkHttpClient
-//
-//    constructor(): this(30, 30, 30)
-//
-//    constructor(connectTimeOut: Long, readTimeOut: Long, writeTimeOut: Long) {
-//        val builder = OkHttpClient.Builder()
-//                .connectTimeout(connectTimeOut, TimeUnit.SECONDS)
-//                .readTimeout(readTimeOut, TimeUnit.SECONDS)
-//                .writeTimeout(writeTimeOut, TimeUnit.SECONDS);
-//        mHttpClient = builder.build()
-//    }
-//
-//    fun buildRetrofit(baseUrl: String): DownloadHelper {
-//        mRetrofit = Retrofit.Builder()
-//                .baseUrl(baseUrl)
-//                .client(mHttpClient)
-//                .build()
-//        return this
-//    }
-//
-//    public fun <T> createRetrofit(serviceClass: Class<T>): T {
-//        return mRetrofit.create(serviceClass)
-//    }
-//
-//}

+ 0 - 13
android/src/main/kotlin/cn/i2edu/dubbing_lib/api/DownloadService.kt

@@ -1,13 +0,0 @@
-//package cn.i2edu.dubbing_lib.api
-//
-//import okhttp3.ResponseBody
-//import retrofit2.Call
-//import retrofit2.http.GET
-//import retrofit2.http.Streaming
-//import retrofit2.http.Url
-//
-//interface DownloadService {
-//    @Streaming
-//    @GET
-//    fun downFile(@Url fileUrl:String): Call<ResponseBody>
-//}

+ 0 - 11
android/src/main/kotlin/cn/i2edu/dubbing_lib/listener/DownloadListener.kt

@@ -1,11 +0,0 @@
-//package cn.i2edu.dubbing_lib.listener
-//
-//interface DownloadListener {
-//    fun onStart()
-//
-//    fun onProgress(progress: Int)
-//
-//    fun onFinish(localPath: String)
-//
-//    fun onFailure();
-//}

+ 0 - 141
android/src/main/kotlin/cn/i2edu/dubbing_lib/util/DownloadUtil.kt

@@ -1,141 +0,0 @@
-//package cn.i2edu.dubbing_lib.util
-//
-//import android.os.Environment
-//import android.util.Log
-//import cn.i2edu.dubbing_lib.api.DownloadHelper
-//import okhttp3.ResponseBody
-//import retrofit2.Call
-//import cn.i2edu.dubbing_lib.api.DownloadService
-//import cn.i2edu.dubbing_lib.listener.DownloadListener
-//import retrofit2.Callback
-//import retrofit2.Response
-//import java.io.*
-//
-//
-//class DownloadUtil {
-//
-//    companion object {
-//        private val Tag: String = "DownloadUtil"
-//        val PATH_VIDEO = "${Environment.getExternalStorageDirectory().absoluteFile}/i2_file/i2_video/"
-//        val PATH_BGM = "${Environment.getExternalStorageDirectory().absoluteFile}/i2_file/i2_bgm/"
-//    }
-//
-//    protected lateinit var mApi: DownloadService
-//    private var mCall: Call<ResponseBody>? = null
-//    private var mThread: Thread? = null
-//
-//    constructor() {
-//        if (!this::mApi.isInitialized) {
-//            mApi = DownloadHelper.instance.buildRetrofit(" http://cdn.i2nexted.com/upload/video/file/")
-//                    .createRetrofit(DownloadService::class.java)
-//        }
-//    }
-//
-//    fun downloadFile(url: String, downloadListener: DownloadListener) {
-//        downloadListener.onStart()
-//        val file = File(PATH_VIDEO)
-//        if (!file.exists()) {
-//            file.mkdirs()
-//        }
-//        var name = url
-//        val i = url.lastIndexOf('/')
-//        if (i == -1) {
-//            return
-//        }
-//        name = PATH_VIDEO + name.substring(i)
-//        val mFile = File(name)
-//        if (!mFile.exists()) {
-//            if (!mFile.createNewFile()) {
-//                return
-//            }
-//        }
-//        // 下载
-//        mCall = mApi.downFile(url)
-//        mCall?.enqueue(object : Callback<ResponseBody>{
-//            override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
-//                downloadListener.onFailure()
-//            }
-//
-//            override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
-//                mThread = Thread {
-//                    saveToFile(response, mFile, downloadListener)
-//                }
-//                mThread?.start()
-//            }
-//        })
-//    }
-//
-//    /**
-//     * download BGM file with [url] and  [downloadListener]
-//     */
-//    fun downloadBGMFile(url: String, downloadListener: DownloadListener) {
-//        downloadListener.onStart()
-//        val file = File(PATH_BGM)
-//        if (!file.exists()) {
-//            file.mkdirs()
-//        }
-//        var name = url
-//        val i = url.lastIndexOf('/')
-//        if (i == -1) {
-//            return
-//        }
-//        name = PATH_BGM + name.substring(i)
-//        val mFile = File(name)
-//        if (!mFile.exists()) {
-//            if (!mFile.createNewFile()) {
-//                return
-//            }
-//        }
-//        // 下载
-//        mCall = mApi.downFile(url)
-//        mCall?.enqueue(object : Callback<ResponseBody>{
-//            override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
-//                downloadListener.onFailure()
-//            }
-//
-//            override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
-//                mThread = Thread {
-//                    saveToFile(response, mFile, downloadListener)
-//                }
-//                mThread?.start()
-//            }
-//        })
-//    }
-//
-//    private fun saveToFile(response: Response<ResponseBody>, mFile: File, downloadListener: DownloadListener) {
-//        var currentLength: Long = 0
-//        var os: OutputStream? = null
-//
-//        val iStream = response.body()?.byteStream()
-//        val totalLength = response.body()?.contentLength()
-//
-//        try {
-//            os = FileOutputStream(mFile)
-//            val buff = ByteArray(1024)
-//            do{
-//                val len = iStream!!.read(buff)
-//                if (len == -1) {
-//                    break
-//                }
-//                os.write(buff, 0, len)
-//                currentLength += len
-//                Log.i(Tag, "当前进度: ${(100 * currentLength / totalLength!!).toInt()}")
-//                //计算当前下载百分比,并经由回调传出
-//                downloadListener.onProgress((100 * currentLength / totalLength!!).toInt())
-//                //当百分比为100时下载结束,调用结束回调,并传出下载后的本地路径
-//                if ((100 * currentLength / totalLength).toInt() == 100) {
-//                    downloadListener.onFinish(mFile.absolutePath)//下载完成
-//                }
-//            } while (true)
-//        } catch (e: FileNotFoundException) {
-//            e.printStackTrace()
-//        } catch (e: IOException) {
-//            e.printStackTrace()
-//        } finally {
-//            os?.close()
-//            iStream?.close()
-//        }
-//
-//    }
-//
-//}

+ 8 - 8
android/src/main/kotlin/cn/i2edu/dubbing_lib/util/MixinPaintedUtil.kt

@@ -28,7 +28,7 @@ class MixinPaintedUtil private constructor(private val context: Context) : Mixin
     private lateinit var decodeBgmPath: String // 解码背景音频路径
     private lateinit var decodeAsyncBgmPath: String // 合成音频后的未解码路径
     // dir
-    private lateinit var audioDecodePath: String
+    private lateinit var decodeDirPath: String // 解码文件夹地址
     private lateinit var mixinFilePath: String
     private lateinit var encodePath: String // 指定合成音频后的文件.mp3路径
 
@@ -49,16 +49,16 @@ class MixinPaintedUtil private constructor(private val context: Context) : Mixin
     }
 
     fun initParams(
-            audioPaths: List<String>, bgmPath: String, durationList: List<Long>, endTimeList: List<Long>, audioDecodePath: String,
-            mixinFilePath: String, encodePath: String
+            audioPaths: List<String>, bgmPath: String, durationList: List<Long>, endTimeList: List<Long>, decodeDirPath: String,
+            mixinFilePath: String, resultPath: String
     ): MixinPaintedUtil {
         this.audioPaths = audioPaths
         this.bgmPath = bgmPath
         this.durationList = durationList
         this.endTimeList = endTimeList
-        this.audioDecodePath = audioDecodePath
+        this.decodeDirPath = decodeDirPath
         this.mixinFilePath = mixinFilePath
-        this.encodePath = encodePath
+        this.encodePath = resultPath
         return instance!!
     }
 
@@ -74,12 +74,12 @@ class MixinPaintedUtil private constructor(private val context: Context) : Mixin
     }
 
     private fun decodeAudio() {
-        if (TextUtils.isEmpty(audioDecodePath)) return
+        if (TextUtils.isEmpty(decodeDirPath)) return
         decodeAudioPaths = arrayListOf()
         pausableThreadPool.execute {
             // 解码录音音频
             for (path in audioPaths) {
-                val decodedPath = doDecode(UUID.randomUUID().toString(), path, audioDecodePath)
+                val decodedPath = doDecode(UUID.randomUUID().toString(), path, decodeDirPath)
                 if (decodedPath == null) {
                     mixinPaintedCallBack?.onError("decodeBgmAudio failed")
                     return@execute
@@ -87,7 +87,7 @@ class MixinPaintedUtil private constructor(private val context: Context) : Mixin
                 decodeAudioPaths.add(decodedPath)
             }
             // 解码背景音乐
-            val decodedPath = doDecode(UUID.randomUUID().toString(), bgmPath, audioDecodePath)
+            val decodedPath = doDecode(UUID.randomUUID().toString(), bgmPath, decodeDirPath)
             if (decodedPath == null) {
                 mixinPaintedCallBack?.onError("decodeBgmAudio failed")
                 return@execute

+ 0 - 221
android/src/main/kotlin/cn/i2edu/dubbing_lib/util/MixinVideoUtil.kt

@@ -1,221 +0,0 @@
-package cn.i2edu.dubbing_lib.util
-
-import android.content.Context
-import android.os.Message
-import android.text.TextUtils
-import cn.i2edu.dubbing_lib.audioUtils.AudioDecoder
-import cn.i2edu.dubbing_lib.audioUtils.AudioEncoder
-import cn.i2edu.dubbing_lib.audioUtils.VideoAudioMixer
-import cn.i2edu.dubbing_lib.audioUtils.compose.AudioComposer
-import cn.i2edu.dubbing_lib.callback.MixinHandlerCallback
-import java.io.File
-
-interface MixinVideoCallBack {
-    fun onResult(resultPath: String)
-    fun onError(message: String)
-}
-
-class MixinVideoUtil private constructor(private val context: Context) : MixinHandlerCallback {
-    private val mixinHandler: MixinHandler<MixinVideoUtil> = MixinHandler(this)
-    // mixin params needs
-    private lateinit var videoId: String
-    private lateinit var bgmPath: String
-    private lateinit var videoPath: String
-    private lateinit var durationList: List<Long>
-    private lateinit var endTimeList: List<Long>
-    private lateinit var audioDecodePaths: List<String>
-    // dir
-    private lateinit var pathBgmDecodeDir: String
-    private lateinit var pathBgmRecordSyncDir: String
-    private lateinit var pathBgmRecordDecodeSyncDir: String
-    private lateinit var pathVideoMixinDir: String
-    // result save
-    private lateinit var decodeBgmPath: String
-    private lateinit var decodeAsyncBgmPath: String
-    private lateinit var encodeAudioWithBgmPath: String
-    private lateinit var mixVideoPath: String
-    private lateinit var mixinName: String
-
-    private var mixinVideoCallBack: MixinVideoCallBack? = null
-    private val pausableThreadPool: PausableThreadPool = PausableThreadPool(1)
-    private val TAG: String = "MixinVideoUtil"
-
-    companion object {
-        @Volatile
-        private var instance: MixinVideoUtil? = null
-
-        fun getInstance(context: Context) = instance
-                ?: synchronized(this) {
-                    instance
-                            ?: MixinVideoUtil(context)
-                                    .also { instance = it }
-                }
-    }
-
-    fun initParams(
-            videoId: String, bgmPath: String, videoPath: String, durationList: List<Long>, endTimeList: List<Long>, audioDecodePaths: List<String>,
-            pathBgmDecodeDir: String, pathBgmRecordSyncDir: String, pathBgmRecordDecodeSyncDir: String, pathVideoMixinDir: String, mixinName: String
-    ): MixinVideoUtil {
-        this.videoId = videoId
-        this.bgmPath = bgmPath
-        this.videoPath = videoPath
-        this.durationList = durationList
-        this.endTimeList = endTimeList
-        this.audioDecodePaths = audioDecodePaths
-        this.pathBgmDecodeDir = pathBgmDecodeDir
-        this.pathBgmRecordSyncDir = pathBgmRecordSyncDir
-        this.pathBgmRecordDecodeSyncDir = pathBgmRecordDecodeSyncDir
-        this.pathVideoMixinDir = pathVideoMixinDir
-        this.mixinName = mixinName
-        return instance!!
-    }
-
-    fun setComposeCallBack(callback: MixinVideoCallBack): MixinVideoUtil {
-        this.mixinVideoCallBack = callback
-        return instance!!
-    }
-
-    fun startMixin() {
-        val message = Message.obtain()
-        message.what = HandlerMessage.START_MIX_IN.value
-        mixinHandler.sendMessage(message)
-    }
-
-    private fun decodeBgmAudio(fileName: String, localPath: String) {
-        if (TextUtils.isEmpty(localPath)) return
-        pausableThreadPool.execute {
-            val decodedPath = doDecode(fileName, localPath, pathBgmDecodeDir)
-            if (decodedPath == null) {
-                this.mixinVideoCallBack?.onError("decodeBgmAudio failed")
-                return@execute
-            }
-            decodeBgmPath = decodedPath
-            // step3  背景音乐与录音合成
-            val message = Message.obtain()
-            message.what = HandlerMessage.AUDIO_DECODE_FINISHED.value
-            mixinHandler.sendMessage(message)
-        }
-    }
-
-    private fun syncAudios() {
-        // 合成背景音乐和录音 (从尾部开始)
-        val fileDIR = File(pathBgmRecordSyncDir)
-        if (!fileDIR.exists()) {
-            fileDIR.mkdirs()
-        }
-        val mixFilePath = File(fileDIR.absolutePath + "/" + "mixin.mp3")
-        // 合成操作
-        val tempPath = arrayOf<String>(decodeBgmPath)
-        pausableThreadPool.execute {
-            AudioComposer.composeAudio(tempPath[0],
-                    audioDecodePaths,
-                    mixFilePath.absolutePath,
-                    false,
-                    endTimeList,
-                    durationList,
-                    object : AudioComposer.ComposeAudioInterface {
-                        override fun composeSuccess(result: String?) {
-                            decodeAsyncBgmPath = result!!
-                            val message = Message.obtain()
-                            message.what = HandlerMessage.AUDIO_SYN_FINISHED.value
-                            mixinHandler.sendMessage(message)
-                        }
-
-                        override fun composeFail() {
-                            mixinVideoCallBack?.onError("composeAudio failed")
-                        }
-                    })
-        }
-    }
-
-    private fun encodeAsynAudio() {
-        pausableThreadPool.execute {
-            val accEncoder = AudioEncoder
-                    .createAccEncoder(decodeAsyncBgmPath)
-            val file = File(pathBgmRecordDecodeSyncDir)
-            if (!file.exists()) file.mkdirs()
-            val finalMixPath = pathBgmRecordDecodeSyncDir + "mixinDecode.aac"
-            val isEncodeFinished = !TextUtils.isEmpty(accEncoder.encodeToFile(finalMixPath))
-            if (!isEncodeFinished) {
-                mixinVideoCallBack?.onError("encodeToFile failed")
-                return@execute
-            }
-            encodeAudioWithBgmPath = finalMixPath
-            val message = Message.obtain()
-            message.what = HandlerMessage.AUDIO_ENCODE_FINISHED.value
-            mixinHandler.sendMessage(message)
-        }
-    }
-
-    private fun mixinAudioAndVideo() {
-        pausableThreadPool.execute {
-            val videoAudioMixer = VideoAudioMixer(context)
-            videoAudioMixer.setListener(object : VideoAudioMixer.VideoAudioMixListener {
-                override fun mixSuccess() {
-//                    mixVideoPath = pathVideoMixinDir + "${videoId}_${System.currentTimeMillis()}.mp4"
-                    mixVideoPath = pathVideoMixinDir + mixinName
-                    val msg = Message()
-                    msg.what = HandlerMessage.AUDIO_MIX_VIDIO_FINISHED.value
-                    mixinHandler.sendMessage(msg)
-                }
-
-                override fun mixFail(reason: String?) {
-                    mixinVideoCallBack?.onError("mix video and audio failed")
-                }
-            })
-            videoAudioMixer.mux(encodeAudioWithBgmPath, videoPath,
-                    mixinName, pathVideoMixinDir)
-        }
-    }
-
-    private fun audioMixVideoFinish() {
-        mixinVideoCallBack?.onResult(mixVideoPath)
-    }
-
-    private fun doDecode(fileName: String, path: String, saveDirPath: String): String? {
-        try {
-            // 解码后的路径
-            val decodeFile = File(saveDirPath)
-            if (!decodeFile.exists()) {
-                decodeFile.mkdirs()
-            }
-            val finalFile = File(decodeFile.absolutePath + "/" + fileName)
-            if (!finalFile.exists()) {
-                finalFile.createNewFile()
-            }
-            val audioDec = AudioDecoder
-                    .createDefualtDecoder(path)
-            audioDec.decodeToFile(finalFile.absolutePath)
-            return finalFile.absolutePath
-        } catch (e: Exception) {
-            e.printStackTrace()
-        }
-        return null
-    }
-
-    override fun onHandleMessage(msg: Message) {
-        when (msg.what) {
-            HandlerMessage.START_MIX_IN.value -> {
-                // step2 解码背景音乐
-                val i = bgmPath.lastIndexOf('/')
-                val name = bgmPath.substring(i)
-                decodeBgmAudio(name, bgmPath)
-            }
-            HandlerMessage.AUDIO_DECODE_FINISHED.value -> {
-                // step3  背景音乐与录音合成
-                syncAudios()
-            }
-            HandlerMessage.AUDIO_SYN_FINISHED.value -> {
-                // step4 编码音频
-                encodeAsynAudio()
-            }
-            HandlerMessage.AUDIO_ENCODE_FINISHED.value -> {
-                // step5 音视频合并
-                mixinAudioAndVideo()
-            }
-            HandlerMessage.AUDIO_MIX_VIDIO_FINISHED.value -> {
-                audioMixVideoFinish()
-            }
-        }
-    }
-}

+ 6 - 11
android/src/main/kotlin/cn/i2edu/dubbing_lib/audioUtils/VideoAudioMixer.java → android/src/main/kotlin/cn/i2edu/dubbing_lib/util/Mp4ParserMixer.java

@@ -1,4 +1,4 @@
-package cn.i2edu.dubbing_lib.audioUtils;
+package cn.i2edu.dubbing_lib.util;
 
 import android.content.Context;
 
@@ -26,11 +26,11 @@ import java.util.List;
  * Created by Administrator on 2016/11/24.
  */
 
-public class VideoAudioMixer {
+public class Mp4ParserMixer {
     private VideoAudioMixListener mListener = null;
     private Context context;
 
-    public VideoAudioMixer(Context context) {
+    public Mp4ParserMixer(Context context) {
         this.context = context;
     }
 
@@ -49,7 +49,6 @@ public class VideoAudioMixer {
      *
      * @param audioPath     原音频文件路径
      * @param vedioPath     原视频路径
-     * @param mixedFileName 合成后的文件名
      * @param mixedFilePath 合成后的文件路径
      */
 //    public void mux(String audioPath, String vedioPath, String mixedFileName, String mixedFilePath) {
@@ -111,11 +110,7 @@ public class VideoAudioMixer {
 //
 //    }
 
-    public void mux(String audioPath, String vedioPath, String mixedFileName, String mixedFilePath) {
-        File mixDir = new File(mixedFilePath);
-        if (!mixDir.exists()) {
-            mixDir.mkdir();
-        }
+    public void muxAudioAndVideo(String audioPath, String vedioPath, String mixedFilePath) {
         try {
             Movie countVideo = MovieCreator.build(vedioPath);
             // 创建新的视频
@@ -138,7 +133,7 @@ public class VideoAudioMixer {
             // 开始合成并且输出
             {
                 Container out = new DefaultMp4Builder().build(newMovie);
-                FileOutputStream fos = new FileOutputStream(new File(mixDir.getAbsolutePath() + "/" + mixedFileName));
+                FileOutputStream fos = new FileOutputStream(new File(mixedFilePath));
                 BufferedWritableFileByteChannel byteBufferByteChannel = new BufferedWritableFileByteChannel(fos);
                 out.writeContainer(byteBufferByteChannel);
                 byteBufferByteChannel.close();
@@ -219,4 +214,4 @@ public class VideoAudioMixer {
         }
     }
 
-}
+}

+ 129 - 0
android/src/main/kotlin/cn/i2edu/dubbing_lib/util/VideoAudioMixer.java

@@ -0,0 +1,129 @@
+package cn.i2edu.dubbing_lib.util;
+
+import android.content.Context;
+
+
+import android.media.MediaCodec;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMuxer;
+import android.os.Build;
+import androidx.annotation.RequiresApi;
+
+import java.nio.ByteBuffer;
+/**
+ * Created by Administrator on 2016/11/24.
+ */
+
+public class VideoAudioMixer {
+    private VideoAudioMixListener mListener = null;
+    private Context context;
+
+    public VideoAudioMixer(Context context) {
+        this.context = context;
+    }
+
+    public interface VideoAudioMixListener {
+        void mixSuccess();
+
+        void mixFail(String reason);
+    }
+
+    public void setListener(VideoAudioMixListener listener) {
+        mListener = listener;
+    }
+
+    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
+    public void muxAudioAndVideo(String audioPath, String videoPath, String mixedFilePath) {
+        try {
+            MediaMuxer mMediaMuxer = new MediaMuxer(mixedFilePath,
+                    MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+
+            // 视频的MediaExtractor
+            MediaExtractor mVideoExtractor = new MediaExtractor();
+            mVideoExtractor.setDataSource(videoPath);
+            int videoTrackIndex = -1;
+            for (int i = 0; i < mVideoExtractor.getTrackCount(); i++) {
+                MediaFormat format = mVideoExtractor.getTrackFormat(i);
+                if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+                    mVideoExtractor.selectTrack(i);
+                    videoTrackIndex = mMediaMuxer.addTrack(format);
+                    break;
+                }
+            }
+
+            // 音频的MediaExtractor
+            MediaExtractor mAudioExtractor = new MediaExtractor();
+            mAudioExtractor.setDataSource(audioPath);
+            int audioTrackIndex = -1;
+            for (int i = 0; i < mAudioExtractor.getTrackCount(); i++) {
+                MediaFormat format = mAudioExtractor.getTrackFormat(i);
+                if (format.getString(MediaFormat.KEY_MIME).startsWith("audio/")) {
+                    mAudioExtractor.selectTrack(i);
+                    audioTrackIndex = mMediaMuxer.addTrack(format);
+                }
+            }
+
+            // 添加完所有轨道后start
+            mMediaMuxer.start();
+
+            // 封装视频track
+            if (-1 != videoTrackIndex) {
+                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+                info.presentationTimeUs = 0;
+                ByteBuffer buffer = ByteBuffer.allocate(100 * 1024);
+                while (true) {
+                    int sampleSize = mVideoExtractor.readSampleData(buffer, 0);
+                    if (sampleSize < 0) {
+                        break;
+                    }
+
+                    info.offset = 0;
+                    info.size = sampleSize;
+                    info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME;
+                    info.presentationTimeUs = mVideoExtractor.getSampleTime();
+                    mMediaMuxer.writeSampleData(videoTrackIndex, buffer, info);
+
+                    mVideoExtractor.advance();
+                }
+            }
+
+            // 封装音频track
+            if (-1 != audioTrackIndex) {
+                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+                info.presentationTimeUs = 0;
+                ByteBuffer buffer = ByteBuffer.allocate(100 * 1024);
+                while (true) {
+                    int sampleSize = mAudioExtractor.readSampleData(buffer, 0);
+                    if (sampleSize < 0) {
+                        break;
+                    }
+
+                    info.offset = 0;
+                    info.size = sampleSize;
+                    info.flags = MediaCodec.BUFFER_FLAG_SYNC_FRAME;
+                    info.presentationTimeUs = mAudioExtractor.getSampleTime();
+                    mMediaMuxer.writeSampleData(audioTrackIndex, buffer, info);
+
+                    mAudioExtractor.advance();
+                }
+            }
+            if (mListener != null) {
+                mListener.mixSuccess();
+            }
+
+            // 释放MediaExtractor
+            mVideoExtractor.release();
+            mAudioExtractor.release();
+
+            // 释放MediaMuxer
+            mMediaMuxer.stop();
+            mMediaMuxer.release();
+        } catch (Exception e) {
+            if (mListener != null) {
+                mListener.mixFail(e.toString());
+            }
+        }
+    }
+
+}

+ 11 - 0
example/android/.gitignore

@@ -0,0 +1,11 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties

+ 6 - 0
example/android/app/src/main/kotlin/cn/i2edu/example/MainActivity.kt

@@ -0,0 +1,6 @@
+package cn.i2edu.example
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}

+ 1 - 1
example/android/gradle/wrapper/gradle-wrapper.properties

@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip

+ 32 - 0
example/ios/.gitignore

@@ -0,0 +1,32 @@
+*.mode1v3
+*.mode2v3
+*.moved-aside
+*.pbxuser
+*.perspectivev3
+**/*sync/
+.sconsign.dblite
+.tags*
+**/.vagrant/
+**/DerivedData/
+Icon?
+**/Pods/
+**/.symlinks/
+profile
+xcuserdata
+**/.generated/
+Flutter/App.framework
+Flutter/Flutter.framework
+Flutter/Flutter.podspec
+Flutter/Generated.xcconfig
+Flutter/app.flx
+Flutter/app.zip
+Flutter/flutter_assets/
+Flutter/flutter_export_environment.sh
+ServiceDefinitions.json
+Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!default.mode1v3
+!default.mode2v3
+!default.pbxuser
+!default.perspectivev3

+ 1 - 0
example/ios/Flutter/.last_build_id

@@ -0,0 +1 @@
+a9735790872028d74a1b605f7b4dacb0

+ 87 - 0
example/ios/Podfile

@@ -0,0 +1,87 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '9.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+  'Debug' => :debug,
+  'Profile' => :release,
+  'Release' => :release,
+}
+
+def parse_KV_file(file, separator='=')
+  file_abs_path = File.expand_path(file)
+  if !File.exists? file_abs_path
+    return [];
+  end
+  generated_key_values = {}
+  skip_line_start_symbols = ["#", "/"]
+  File.foreach(file_abs_path) do |line|
+    next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
+    plugin = line.split(pattern=separator)
+    if plugin.length == 2
+      podname = plugin[0].strip()
+      path = plugin[1].strip()
+      podpath = File.expand_path("#{path}", file_abs_path)
+      generated_key_values[podname] = podpath
+    else
+      puts "Invalid plugin specification: #{line}"
+    end
+  end
+  generated_key_values
+end
+
+target 'Runner' do
+  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')
+  unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
+    # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
+    # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
+    # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
+
+    generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
+    unless File.exist?(generated_xcode_build_settings_path)
+      raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+    end
+    generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
+    cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
+
+    unless File.exist?(copied_framework_path)
+      FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
+    end
+    unless File.exist?(copied_podspec_path)
+      FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
+    end
+  end
+
+  # Keep pod path relative so it can be checked into Podfile.lock.
+  pod 'Flutter', :path => 'Flutter'
+
+  # Plugin Pods
+
+  # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
+  # referring to absolute paths on developers' machines.
+  system('rm -rf .symlinks')
+  system('mkdir -p .symlinks/plugins')
+  plugin_pods = parse_KV_file('../.flutter-plugins')
+  plugin_pods.each do |name, path|
+    symlink = File.join('.symlinks', 'plugins', name)
+    File.symlink(path, symlink)
+    pod name, :path => File.join(symlink, 'ios')
+  end
+end
+
+post_install do |installer|
+  installer.pods_project.targets.each do |target|
+    target.build_configurations.each do |config|
+      config.build_settings['ENABLE_BITCODE'] = 'NO'
+    end
+  end
+end

+ 22 - 0
example/ios/Podfile.lock

@@ -0,0 +1,22 @@
+PODS:
+  - dubbing_lib (0.0.1):
+    - Flutter
+  - Flutter (1.0.0)
+
+DEPENDENCIES:
+  - dubbing_lib (from `.symlinks/plugins/dubbing_lib/ios`)
+  - Flutter (from `Flutter`)
+
+EXTERNAL SOURCES:
+  dubbing_lib:
+    :path: ".symlinks/plugins/dubbing_lib/ios"
+  Flutter:
+    :path: Flutter
+
+SPEC CHECKSUMS:
+  dubbing_lib: da8fc70d55e0a92bf6711ce8678156e0860ac933
+  Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
+
+PODFILE CHECKSUM: bcf748b84ead3c1e4250ac40429c79f27df4be25
+
+COCOAPODS: 1.9.3

+ 60 - 73
example/ios/Runner.xcodeproj/project.pbxproj

@@ -3,21 +3,17 @@
 	archiveVersion = 1;
 	classes = {
 	};
-	objectVersion = 46;
+	objectVersion = 50;
 	objects = {
 
 /* 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, ); }; };
-		5A3123EBBA2F7133AE72C07F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2FFB1AE433D873D43776E365 /* Pods_Runner.framework */; };
 		74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
-		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, ); }; };
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
 		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+		E69156303620C34BEAD6D1B4 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C8B57752AFC6ABB657701A3D /* Pods_Runner.framework */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXCopyFilesBuildPhase section */
@@ -27,8 +23,6 @@
 			dstPath = "";
 			dstSubfolderSpec = 10;
 			files = (
-				3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
-				9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
 			);
 			name = "Embed Frameworks";
 			runOnlyForDeploymentPostprocessing = 0;
@@ -38,23 +32,21 @@
 /* Begin PBXFileReference section */
 		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>"; };
-		2FFB1AE433D873D43776E365 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		2442E428DCA73CD134800AE0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; 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>"; };
-		48DF98BDEC399D176EDDEF7D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
-		5BFA0FF4128B46E2B684A763 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
+		3BC0C67106BFF919E913D3A3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
+		72EF97EB084514DD64CDEB63 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
 		74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
 		74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
 		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; };
 		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
 		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
 		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
-		EE33530075AAED39B7954170 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
+		C8B57752AFC6ABB657701A3D /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -62,39 +54,25 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
-				9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
-				3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
-				5A3123EBBA2F7133AE72C07F /* Pods_Runner.framework in Frameworks */,
+				E69156303620C34BEAD6D1B4 /* Pods_Runner.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
-		2C907FEB3CF7AD930F7E3B41 /* Frameworks */ = {
+		01498C543DEB8C40FE1A5D0D /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
-				2FFB1AE433D873D43776E365 /* Pods_Runner.framework */,
+				C8B57752AFC6ABB657701A3D /* Pods_Runner.framework */,
 			);
 			name = Frameworks;
 			sourceTree = "<group>";
 		};
-		58AB398B9E82F7B16FB3CFF8 /* Pods */ = {
-			isa = PBXGroup;
-			children = (
-				5BFA0FF4128B46E2B684A763 /* Pods-Runner.debug.xcconfig */,
-				EE33530075AAED39B7954170 /* Pods-Runner.release.xcconfig */,
-				48DF98BDEC399D176EDDEF7D /* Pods-Runner.profile.xcconfig */,
-			);
-			path = Pods;
-			sourceTree = "<group>";
-		};
 		9740EEB11CF90186004384FC /* Flutter */ = {
 			isa = PBXGroup;
 			children = (
-				3B80C3931E831B6300D905FE /* App.framework */,
 				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
-				9740EEBA1CF902C7004384FC /* Flutter.framework */,
 				9740EEB21CF90195004384FC /* Debug.xcconfig */,
 				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
 				9740EEB31CF90195004384FC /* Generated.xcconfig */,
@@ -108,8 +86,8 @@
 				9740EEB11CF90186004384FC /* Flutter */,
 				97C146F01CF9000F007C117D /* Runner */,
 				97C146EF1CF9000F007C117D /* Products */,
-				58AB398B9E82F7B16FB3CFF8 /* Pods */,
-				2C907FEB3CF7AD930F7E3B41 /* Frameworks */,
+				AE1F9084B0339A270693D271 /* Pods */,
+				01498C543DEB8C40FE1A5D0D /* Frameworks */,
 			);
 			sourceTree = "<group>";
 		};
@@ -128,7 +106,6 @@
 				97C146FD1CF9000F007C117D /* Assets.xcassets */,
 				97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
 				97C147021CF9000F007C117D /* Info.plist */,
-				97C146F11CF9000F007C117D /* Supporting Files */,
 				1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
 				1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
 				74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
@@ -137,11 +114,14 @@
 			path = Runner;
 			sourceTree = "<group>";
 		};
-		97C146F11CF9000F007C117D /* Supporting Files */ = {
+		AE1F9084B0339A270693D271 /* Pods */ = {
 			isa = PBXGroup;
 			children = (
+				72EF97EB084514DD64CDEB63 /* Pods-Runner.debug.xcconfig */,
+				3BC0C67106BFF919E913D3A3 /* Pods-Runner.release.xcconfig */,
+				2442E428DCA73CD134800AE0 /* Pods-Runner.profile.xcconfig */,
 			);
-			name = "Supporting Files";
+			path = Pods;
 			sourceTree = "<group>";
 		};
 /* End PBXGroup section */
@@ -151,14 +131,14 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
 			buildPhases = (
-				684E9F097E90E00067541831 /* [CP] Check Pods Manifest.lock */,
+				083FEE557350B645C3BD6CF0 /* [CP] Check Pods Manifest.lock */,
 				9740EEB61CF901F6004384FC /* Run Script */,
 				97C146EA1CF9000F007C117D /* Sources */,
 				97C146EB1CF9000F007C117D /* Frameworks */,
 				97C146EC1CF9000F007C117D /* Resources */,
 				9705A1C41CF9048500538489 /* Embed Frameworks */,
 				3B06AD1E1E4923F5004D2608 /* Thin Binary */,
-				E110F7554BC722F3C5868A3C /* [CP] Embed Pods Frameworks */,
+				7FEA44A6220027FEBF140E83 /* [CP] Embed Pods Frameworks */,
 			);
 			buildRules = (
 			);
@@ -176,17 +156,16 @@
 			isa = PBXProject;
 			attributes = {
 				LastUpgradeCheck = 1020;
-				ORGANIZATIONNAME = "The Chromium Authors";
+				ORGANIZATIONNAME = "";
 				TargetAttributes = {
 					97C146ED1CF9000F007C117D = {
 						CreatedOnToolsVersion = 7.3.1;
-						DevelopmentTeam = LRXRX75D5X;
 						LastSwiftMigration = 1100;
 					};
 				};
 			};
 			buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
-			compatibilityVersion = "Xcode 3.2";
+			compatibilityVersion = "Xcode 9.3";
 			developmentRegion = en;
 			hasScannedForEncodings = 0;
 			knownRegions = (
@@ -218,21 +197,7 @@
 /* End PBXResourcesBuildPhase section */
 
 /* Begin PBXShellScriptBuildPhase section */
-		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputPaths = (
-			);
-			name = "Thin Binary";
-			outputPaths = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
-		};
-		684E9F097E90E00067541831 /* [CP] Check Pods Manifest.lock */ = {
+		083FEE557350B645C3BD6CF0 /* [CP] Check Pods Manifest.lock */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
@@ -254,35 +219,51 @@
 			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
 			showEnvVarsInLog = 0;
 		};
-		9740EEB61CF901F6004384FC /* Run Script */ = {
+		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
 			);
 			inputPaths = (
 			);
-			name = "Run Script";
+			name = "Thin Binary";
 			outputPaths = (
 			);
 			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\" embed_and_thin";
 		};
-		E110F7554BC722F3C5868A3C /* [CP] Embed Pods Frameworks */ = {
+		7FEA44A6220027FEBF140E83 /* [CP] Embed Pods Frameworks */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
 			);
-			inputPaths = (
+			inputFileListPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
 			);
 			name = "[CP] Embed Pods Frameworks";
-			outputPaths = (
+			outputFileListPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 			shellPath = /bin/sh;
 			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
 			showEnvVarsInLog = 0;
 		};
+		9740EEB61CF901F6004384FC /* Run Script */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Run Script";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+		};
 /* End PBXShellScriptBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */
@@ -319,7 +300,6 @@
 /* Begin XCBuildConfiguration section */
 		249021D3217E4FDB00AE95B9 /* Profile */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				CLANG_ANALYZER_NONNULL = YES;
@@ -382,12 +362,15 @@
 					"$(PROJECT_DIR)/Flutter",
 				);
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = cn.i2edu.dubbingLibExample;
+				PRODUCT_BUNDLE_IDENTIFIER = cn.i2edu.example;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
 				SWIFT_VERSION = 5.0;
@@ -397,7 +380,6 @@
 		};
 		97C147031CF9000F007C117D /* Debug */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				CLANG_ANALYZER_NONNULL = YES;
@@ -453,7 +435,6 @@
 		};
 		97C147041CF9000F007C117D /* Release */ = {
 			isa = XCBuildConfiguration;
-			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
 			buildSettings = {
 				ALWAYS_SEARCH_USER_PATHS = NO;
 				CLANG_ANALYZER_NONNULL = YES;
@@ -497,7 +478,8 @@
 				MTL_ENABLE_DEBUG_INFO = NO;
 				SDKROOT = iphoneos;
 				SUPPORTED_PLATFORMS = iphoneos;
-				SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
+				SWIFT_COMPILATION_MODE = wholemodule;
+				SWIFT_OPTIMIZATION_LEVEL = "-O";
 				TARGETED_DEVICE_FAMILY = "1,2";
 				VALIDATE_PRODUCT = YES;
 			};
@@ -517,13 +499,15 @@
 					"$(PROJECT_DIR)/Flutter",
 				);
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 				);
-				ONLY_ACTIVE_ARCH = NO;
-				PRODUCT_BUNDLE_IDENTIFIER = cn.i2edu.dubbingLibExample;
+				PRODUCT_BUNDLE_IDENTIFIER = cn.i2edu.example;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
 				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -546,12 +530,15 @@
 					"$(PROJECT_DIR)/Flutter",
 				);
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
 				LIBRARY_SEARCH_PATHS = (
 					"$(inherited)",
 					"$(PROJECT_DIR)/Flutter",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = cn.i2edu.dubbingLibExample;
+				PRODUCT_BUNDLE_IDENTIFIER = cn.i2edu.example;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
 				SWIFT_VERSION = 5.0;

+ 8 - 0
example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>

+ 8 - 0
example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>PreviewsEnabled</key>
+	<false/>
+</dict>
+</plist>

+ 8 - 0
example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>

+ 8 - 0
example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>PreviewsEnabled</key>
+	<false/>
+</dict>
+</plist>

+ 1 - 12
example/ios/Runner/Info.plist

@@ -11,7 +11,7 @@
 	<key>CFBundleInfoDictionaryVersion</key>
 	<string>6.0</string>
 	<key>CFBundleName</key>
-	<string>dubbing_lib_example</string>
+	<string>example</string>
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
@@ -41,16 +41,5 @@
 	</array>
 	<key>UIViewControllerBasedStatusBarAppearance</key>
 	<false/>
-    <key>NSAppTransportSecurity</key>
-    <dict>
-      <key>NSAllowsArbitraryLoads</key>
-      <true/>
-    </dict>
-    <key>NSMicrophoneUsageDescription</key>
-    <string></string>
-    <key>NSLocationUsageDescription</key>
-    <string></string>
-    <key>NSLocationAlwaysUsageDescription</key>
-    <string></string>
 </dict>
 </plist>

+ 0 - 1
example/ios/Runner/Runner-Bridging-Header.h

@@ -1,2 +1 @@
 #import "GeneratedPluginRegistrant.h"
-

+ 29 - 107
example/pubspec.lock

@@ -1,69 +1,48 @@
 # Generated by pub
 # See https://dart.dev/tools/pub/glossary#lockfile
 packages:
-  archive:
-    dependency: transitive
-    description:
-      name: archive
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "2.0.10"
-  args:
-    dependency: transitive
-    description:
-      name: args
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "1.5.2"
   async:
     dependency: transitive
     description:
       name: async
       url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "2.3.0"
+    version: "2.4.2"
   boolean_selector:
     dependency: transitive
     description:
       name: boolean_selector
       url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "1.0.5"
-  charcode:
+    version: "2.0.0"
+  characters:
     dependency: transitive
     description:
-      name: charcode
+      name: characters
       url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "1.1.2"
-  collection:
+    version: "1.0.0"
+  charcode:
     dependency: transitive
     description:
-      name: collection
+      name: charcode
       url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "1.14.11"
-  convert:
+    version: "1.1.3"
+  clock:
     dependency: transitive
     description:
-      name: convert
+      name: clock
       url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "2.1.1"
-  crypto:
+    version: "1.0.1"
+  collection:
     dependency: transitive
     description:
-      name: crypto
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "2.1.3"
-  cupertino_icons:
-    dependency: "direct main"
-    description:
-      name: cupertino_icons
+      name: collection
       url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "0.1.2"
+    version: "1.14.13"
   dubbing_lib:
     dependency: "direct dev"
     description:
@@ -71,86 +50,44 @@ packages:
       relative: true
     source: path
     version: "0.0.1"
+  fake_async:
+    dependency: transitive
+    description:
+      name: fake_async
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.1.0"
   flutter:
     dependency: "direct main"
     description: flutter
     source: sdk
     version: "0.0.0"
-  flutter_alert:
-    dependency: "direct main"
-    description:
-      name: flutter_alert
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.4.0"
   flutter_test:
     dependency: "direct dev"
     description: flutter
     source: sdk
     version: "0.0.0"
-  image:
-    dependency: transitive
-    description:
-      name: image
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "2.1.4"
   matcher:
     dependency: transitive
     description:
       name: matcher
       url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "0.12.5"
+    version: "0.12.8"
   meta:
     dependency: transitive
     description:
       name: meta
       url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "1.1.7"
-  open_iconic_flutter:
-    dependency: "direct main"
-    description:
-      name: open_iconic_flutter
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.3.0"
+    version: "1.1.8"
   path:
     dependency: transitive
     description:
       name: path
       url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "1.6.4"
-  pedantic:
-    dependency: transitive
-    description:
-      name: pedantic
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "1.8.0+1"
-  petitparser:
-    dependency: transitive
-    description:
-      name: petitparser
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "2.4.0"
-  quiver:
-    dependency: transitive
-    description:
-      name: quiver
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "2.0.5"
-  screen:
-    dependency: "direct main"
-    description:
-      name: screen
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.0.5"
+    version: "1.7.0"
   sky_engine:
     dependency: transitive
     description: flutter
@@ -162,14 +99,14 @@ packages:
       name: source_span
       url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "1.5.5"
+    version: "1.7.0"
   stack_trace:
     dependency: transitive
     description:
       name: stack_trace
       url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "1.9.3"
+    version: "1.9.5"
   stream_channel:
     dependency: transitive
     description:
@@ -197,14 +134,14 @@ packages:
       name: test_api
       url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "0.2.5"
+    version: "0.2.17"
   typed_data:
     dependency: transitive
     description:
       name: typed_data
       url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "1.1.6"
+    version: "1.2.0"
   vector_math:
     dependency: transitive
     description:
@@ -212,20 +149,5 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "2.0.8"
-  video_player:
-    dependency: "direct main"
-    description:
-      name: video_player
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "0.9.0"
-  xml:
-    dependency: transitive
-    description:
-      name: xml
-      url: "https://pub.flutter-io.cn"
-    source: hosted
-    version: "3.5.0"
 sdks:
-  dart: ">=2.4.0 <3.0.0"
-  flutter: ">=0.2.5 <2.0.0"
+  dart: ">=2.9.0-14.0.dev <3.0.0"

+ 1 - 5
example/pubspec.yaml

@@ -11,11 +11,7 @@ dependencies:
 
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
-  cupertino_icons: ^0.1.2
-  flutter_alert: 0.4.0
-  video_player: 0.9.0
-  screen: ">=0.0.4 <0.1.0"
-  open_iconic_flutter: ">=0.3.0 <0.4.0"
+#  cupertino_icons: ^0.1.2
 
 dev_dependencies:
   flutter_test:

+ 10 - 31
ios/Classes/DubbingComposer.swift

@@ -11,14 +11,12 @@ import AVFoundation
 
 class DubbingComposer {
     var timeline: [Double]
-    var videoUrl: URL?
     var musicUrl: URL
     var recordsUrl: [String]
     var preTime: Double = 0
     
-    init(timeline: [Double], videoUrl: URL?, musicUrl: URL, recordsUrl: [String]){
+    init(timeline: [Double], musicUrl: URL, recordsUrl: [String]){
         self.timeline = timeline
-        self.videoUrl = videoUrl
         self.musicUrl = musicUrl
         self.recordsUrl = recordsUrl
     }
@@ -28,29 +26,10 @@ class DubbingComposer {
         //初始化合成器
         let composition = AVMutableComposition()
         
-        var videoTimeRange:CMTimeRange?
-        
-        //为合成器添加视频轨道
-        if((videoUrl) != nil){
-            let videoTrack = composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)
-            let videoAsset = AVURLAsset(url: videoUrl!, options: nil)
-            videoTimeRange = CMTimeRangeMake(start: CMTime.zero, duration: videoAsset.duration)
-            do {
-                try videoTrack?.insertTimeRange(videoTimeRange!, of: videoAsset.tracks(withMediaType: AVMediaType.video).first!, at: CMTime.zero)
-            }
-            catch {
-                DispatchQueue.main.async(execute: {
-                    failBlock?("Fail on load video")
-                })
-                return
-            }
-        }
-            
-        
         //添加音乐轨道
         let musicTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)
         let musicAsset = AVURLAsset(url: musicUrl, options: nil)
-        let musicTimeRange = videoTimeRange ?? CMTimeRangeMake(start: CMTime.zero, duration: musicAsset.duration)
+        let musicTimeRange = CMTimeRangeMake(start: CMTime.zero, duration: musicAsset.duration)
         do {
             try musicTrack?.insertTimeRange(musicTimeRange, of: musicAsset.tracks(withMediaType: AVMediaType.audio).first!, at: CMTime.zero)
             print("创建音乐音轨成功")
@@ -83,14 +62,14 @@ class DubbingComposer {
             }
         }
         
-        let manager = FileManager.default
-        do {
-            try manager.removeItem(at: output)
-            print("删除旧输出成功")
-        }
-        catch {
-            print("删除旧输出失败")
-        }
+//        let manager = FileManager.default
+//        do {
+//            try manager.removeItem(at: output)
+//            print("删除旧输出成功")
+//        }
+//        catch {
+//            print("删除旧输出失败")
+//        }
         
         //输出到文件
         if let assetExport = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetMediumQuality) {

+ 27 - 175
ios/Classes/SwiftDubbingLibPlugin.swift

@@ -14,18 +14,7 @@ public class SwiftDubbingLibPlugin: NSObject, FlutterPlugin {
     
     var registrar: FlutterPluginRegistrar!
     var channel: FlutterMethodChannel!
-    
-    var result: FlutterResult? = nil
-    
-    /// 当前界面配音状态
-    var status: DubbingStatus = .stop
-    var isRecording = false
-    
-    var audioRecorder: AVAudioRecorder?
-    var audioPlayer: AVAudioPlayer?
-    
-    var initFlag: Bool = false
-    
+            
     public static func register(with registrar: FlutterPluginRegistrar) {
         let channel = FlutterMethodChannel(name: "dubbing_lib", binaryMessenger: registrar.messenger())
         let instance = SwiftDubbingLibPlugin()
@@ -36,51 +25,24 @@ public class SwiftDubbingLibPlugin: NSObject, FlutterPlugin {
     
     public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
         let args = call.arguments as? [String: Any];
-        self.result = result;
         switch call.method {
         case "getPlatformVersion":
             result("iOS " + UIDevice.current.systemVersion)
             break
-        case "startRecord":
-            let duration = args!["duration"] as! Int;
-            let fileName = args!["fileName"] as! String;
-            let index = args!["index"] as! Int;
-            let pathAudio = args!["pathAudio"] as! String;
-            startRecord(pathAudio: pathAudio, index: index, duration: duration, fileName: fileName, result: result)
-            break
-        case "playRecordAudio":
-            let filePath = args!["fileName"] as! String;
-            playRecord(filePath: filePath, result: result)
-            break
-        case "pauseRecordAudio":
-            audioPlayer?.pause()
-            result(true)
-            break
-        case "startMixinAudio":
-            let videoId = args!["videoId"] as! String;
+        case "mixinVideoAndAudio":
             let videoPath = args!["videoPath"] as! String;
             let bgmPath = args!["bgmPath"] as! String;
-            let audioPathList = args!["audioDecodePaths"] as! [String];
-            let startTimeList = args!["startTimeList"] as! [Double];
-            let pathVideoMixinDir = args!["pathVideoMixin"] as! String;
-            let outPath = pathVideoMixinDir + args!["mixinName"] as! String;
-            startMixinAudio(videoPath: videoPath, bgmPath: bgmPath, audioPathList: audioPathList, startTimeList: startTimeList, outPath: outPath, result: result)
+            let resultPath = args!["resultPath"] as! String;
+            
+            mixinVideo(bgmPath: bgmPath, videoPath: videoPath, outPath: resultPath, result: result)
             break
-        case "startMixinPaintedAudio":
+        case "mixinAudio":
             let bgmPath = args!["bgmPath"] as! String;
             let audioPathList = args!["audioPaths"] as! [String];
             let startTimeList = args!["startTimeList"] as! [Double];
-            let pathVideoMixinDir = args!["encodePath"] as! String;
-            startMixinAudio(videoPath: nil, bgmPath: bgmPath, audioPathList: audioPathList, startTimeList: startTimeList, outPath: pathVideoMixinDir, result: result)
+            let pathVideoMixinDir = args!["resultPath"] as! String;
+            mixinAudio(bgmPath: bgmPath, audioPathList: audioPathList, startTimeList: startTimeList, outPath: pathVideoMixinDir, result: result)
             break;
-        case "getIsMediaPlayPause":
-            result(audioPlayer != nil && audioPlayer!.isPlaying)
-            break
-        case "cleanAudioData":
-            break
-        case "findIsExistCacheVideo":
-            result("")
-            break
         case "setExtraFullScreen":
             result("")
             break
@@ -89,153 +51,43 @@ public class SwiftDubbingLibPlugin: NSObject, FlutterPlugin {
         }
     }
     
-    func initAudioSession(){
-        do {
-//            if (!initFlag) {
-           if (true) {
-                try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, options: .defaultToSpeaker)
-                try AVAudioSession.sharedInstance().setActive(true)
-                initFlag = true
-            }
-        } catch {
-            initFlag = false
-        }
-    }
-    
-    func resultError(code: String, message: String) -> Void {
-        self.result!(FlutterError(code: code, message: message, details: nil))
-    }
-    
-    /// 合成
-    func startMixinAudio(videoPath: String?, bgmPath: String, audioPathList: [String], startTimeList: [Double], outPath: String, result: @escaping FlutterResult) {
-        var videoUrl:URL?=nil
-        if(!(videoPath?.isBlank ?? true)){
-            videoUrl = URL(fileURLWithPath: videoPath!);
-        }
+    /// 合成音频
+    func mixinAudio(bgmPath: String, audioPathList: [String], startTimeList: [Double], outPath: String, result: @escaping FlutterResult) {
         let musicUrl = URL(fileURLWithPath: bgmPath)
-        let composer = DubbingComposer(timeline: startTimeList, videoUrl: videoUrl, musicUrl: musicUrl, recordsUrl: audioPathList)
+        let composer = DubbingComposer(timeline: startTimeList, musicUrl: musicUrl, recordsUrl: audioPathList)
         
         composer.preTime = preLag
         let outputUrl = URL(fileURLWithPath: outPath)
         DispatchQueue.global().async {
             composer.compose(outputUrl, onSuccess: {
-                self.result!(outPath)
+                result(outPath)
             }) { (message) in
                 print("合成失败", message)
-                self.resultError(code: "1005", message: "mix video and audio failed")
+                result(FlutterError(code: "1005", message: "mix audio failed", details: nil))
             }
         }
     }
-}
-
-
-// MARK: - 录音控制
-extension SwiftDubbingLibPlugin {
     
-    @objc func startRecord(pathAudio: String, index: Int, duration: Int, fileName: String, result: @escaping FlutterResult) {
-        initAudioSession()
-        endPlay()
-        status = .record
-        let filePath = pathAudio + fileName + ".aac";
-        do {
-            let settings: [String:Any] = [
-                AVNumberOfChannelsKey : 1, //设置通道
-                AVFormatIDKey : kAudioFormatMPEG4AAC, //设置录音格式
-                //AVSampleRateKey : 16000, //设置录音采样率
-                //AVLinearPCMBitDepthKey : 16, //每个采样点位数,分为8、16、24、32
-                //AVLinearPCMIsFloatKey : true, //是否使用浮点数采样
-            ]
-            let url = URL(fileURLWithPath: filePath)
-            
-            audioRecorder = try AVAudioRecorder(url: url, settings: settings)
-            audioRecorder?.prepareToRecord()
-            audioRecorder?.record()
-            isRecording = true
-        } catch {
-            stopRecord()
-            resultError(code: "1002", message: "start record failed")
-        }
-        if(isRecording){
-            var piecePosition = 0
-            let queue:DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
-            let _timer:DispatchSource = DispatchSource.makeTimerSource(flags: [], queue: queue) as! DispatchSource
-            _timer.schedule(deadline: DispatchTime.now(), repeating: .milliseconds(5))
-            _timer.setEventHandler(handler: {() -> Void in
-                self.isRecording = true
-                piecePosition += 1
-                self.channel.invokeMethod("recordProgress", arguments: ["progress": piecePosition * 5])
-                if(piecePosition * 5 >= duration) {
-                    _timer.cancel()
-                    self.result!(filePath)
-                    self.stopRecord()
-                }
-            })
-            _timer.resume()
-        } else {
-            resultError(code: "1002", message: "start record failed")
-        }
-    }
-    
-    @objc func playRecord(filePath: String, result: @escaping FlutterResult) {
-        status = .playRecord
-        initAudioSession()
-        do {
-            let url = URL(fileURLWithPath: filePath)
-            audioPlayer = try AVAudioPlayer(contentsOf: url)
-            audioPlayer?.prepareToPlay()
-            audioPlayer?.volume = 1
-            audioPlayer?.delegate = self
-            DispatchQueue.global().async {
-                self.audioPlayer?.play()
+    /// 合成视频
+    func mixinVideo(bgmPath: String, videoPath: String, outPath: String, result: @escaping FlutterResult) {
+        let musicUrl = URL(fileURLWithPath: bgmPath)
+        let videoUrl = URL(fileURLWithPath: videoPath)
+
+        let composer = VideoComposer(videoUrl: videoUrl, musicUrl: musicUrl)
+        
+        let outputUrl = URL(fileURLWithPath: outPath)
+        DispatchQueue.global().async {
+            composer.compose(outputUrl, onSuccess: {
+                result(outPath)
+            }) { (message) in
+                print("合成失败", message)
+                result(FlutterError(code: "1006", message: "mix video failed", details: nil))
             }
-        } catch {
-            stopPlayRecord()
-        }
-    }
-    
-    @objc func endPlay() {
-        switch status {
-        case .stop:
-            break
-        case .record:
-            stopRecord()
-        case .playRecord:
-            stopPlayRecord()
-        }
-    }
-    
-    // 停止录音
-    @objc func stopRecord() {
-        status = .stop
-        audioRecorder?.stop()
-        audioRecorder = nil
-        isRecording = false
-    }
-    
-    // 停止播放录音
-    func stopPlayRecord() {
-        status = .stop
-        if audioPlayer?.isPlaying == true {
-            audioPlayer?.stop()
         }
-        audioPlayer = nil
-    }
-}
-
-extension SwiftDubbingLibPlugin: AVAudioPlayerDelegate {
-    public func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
-        stopPlayRecord()
-        self.result!(flag)
-    }
-    
-    public func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
-        stopPlayRecord()
-        //        self.result!(false)
     }
 }
 
 extension String{
-    
     /// check string cellection is whiteSpace
     var isBlank : Bool{
         return allSatisfy({$0.isWhitespace})

+ 87 - 0
ios/Classes/VideoComposer.swift

@@ -0,0 +1,87 @@
+//
+//  VideoComposer.swift
+//  dubbing_lib
+//
+//  Created by weihong huang on 2020/9/18.
+//
+
+import Foundation
+import AVFoundation
+
+class VideoComposer {
+    var videoUrl: URL
+    var musicUrl: URL
+    
+    init(videoUrl: URL, musicUrl: URL){
+        self.videoUrl = videoUrl
+        self.musicUrl = musicUrl
+    }
+    
+    func compose(_ output: URL, onSuccess successBlock: (()->())?, onFail failBlock:((_ message: String)->())? ) {
+        
+        //初始化合成器
+        let composition = AVMutableComposition()
+        
+        var videoTimeRange:CMTimeRange?
+        
+        //为合成器添加视频轨道
+        let videoTrack = composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)
+        let videoAsset = AVURLAsset(url: videoUrl, options: nil)
+        videoTimeRange = CMTimeRangeMake(start: CMTime.zero, duration: videoAsset.duration)
+        do {
+            try videoTrack?.insertTimeRange(videoTimeRange!, of: videoAsset.tracks(withMediaType: AVMediaType.video).first!, at: CMTime.zero)
+        }
+        catch {
+            DispatchQueue.main.async(execute: {
+                failBlock?("Fail on load video")
+            })
+            return
+        }
+            
+        
+        //添加音乐轨道
+        let musicTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)
+        let musicAsset = AVURLAsset(url: musicUrl, options: nil)
+        let musicTimeRange = videoTimeRange ?? CMTimeRangeMake(start: CMTime.zero, duration: musicAsset.duration)
+        do {
+            try musicTrack?.insertTimeRange(musicTimeRange, of: musicAsset.tracks(withMediaType: AVMediaType.audio).first!, at: CMTime.zero)
+            print("创建音乐音轨成功")
+        }
+        catch {
+            print("创建音乐音轨失败")
+        }
+        
+        let manager = FileManager.default
+        do {
+            try manager.removeItem(at: output)
+            print("删除旧输出成功")
+        }
+        catch {
+            print("删除旧输出失败")
+        }
+        
+        //输出到文件
+        if let assetExport = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetMediumQuality) {
+            assetExport.outputFileType = AVFileType.mp4
+            assetExport.outputURL = output
+            assetExport.shouldOptimizeForNetworkUse = true
+            assetExport.exportAsynchronously(completionHandler: {
+                if((assetExport.error) != nil){
+                    print(assetExport.error!)
+                    DispatchQueue.main.async(execute: {
+                        failBlock?("Something wrong on composition")
+                    })
+                }else{
+                    successBlock?()
+                }
+                return
+            })
+
+        }
+        else {
+            DispatchQueue.main.async(execute: {
+                failBlock?("Something wrong on composition")
+            })
+        }
+    }
+}

+ 24 - 126
lib/dubbing_lib.dart

@@ -12,142 +12,40 @@ class DubbingLib {
   static const MethodChannel _channel =
   const MethodChannel('dubbing_lib');
 
-  StreamController _onProgressChange;
-  StreamController _onRecordProgressChange;
-  StreamController _onDecodeResultChange;
-
-  Stream<int> get recordProgressChange => _onRecordProgressChange.stream;
-
-  Stream<Map> get decodeResultChange => _onDecodeResultChange.stream;
-
-  /// 路径文件夹地址
-  String videoPath;
-  String recordPath;
-  String recordDecodePath;
-  String bgmPath;
-  String bgmDecodePath;
-  String audioSyncPath;
-  String audioSyncDecodePath;
-  String videoMixInPath;
-
   static Future<String> get platformVersion async {
     final String version = await _channel.invokeMethod('getPlatformVersion');
     return version;
   }
 
-  Future<void> init() {
-    return _channel.invokeMethod("initSpeechSdk");
-  }
-
   Future<void> setExtraFullScreen() {
     //todo 暂时视频播放时全屏StatusBar bug
     return _channel.invokeMethod("setExtraFullScreen");
   }
 
-  Future<String> downLoadVideo(String videoUrl) {
-    return _channel.invokeMethod("downLoadVideo", {"url": videoUrl});
-  }
-
-  Future<void> pauseRecordAudio() {
-    return _channel.invokeMethod("pauseRecordAudio");
-  }
-
-  /// 返回录音文件地址
-  Future<String> startRecord(int index, int duration, String fileName) {
-    return _channel.invokeMethod("startRecord",
+  Future<String> mixinAudio(
+      {List<String> audioPaths, String bgmPath,
+        List<int> startTimeList, List<int> durationList, List<int> endTimeList,
+        String decodeDirPath, String mixinFilePath, String resultPath}) {
+    return _channel.invokeMethod("mixinAudio",
         {
-          "duration": duration,
-          "fileName": fileName,
-          "index": index,
-          "pathAudio": recordPath,
-          "pathAudioDecode": recordDecodePath
-        }
-    );
-  }
-
-  Future<void> playRecordAudio(String filePath) {
-    return _channel.invokeMethod("playRecordAudio", {"fileName": filePath});
-  }
-
-  Future<String> startMixinAudio(String videoId, String bgmUrl, String bgmPath,
-      List<int> endTimeList,
-      List<String> decodeAudioPathList,
-      List<int> durationList, String localVideoPath,
-      List<double> startTimeList, String mixinName) {
-    return _channel.invokeMethod(
-        "startMixinAudio", {
-      "videoId": videoId,
-      "bgmUrl": bgmUrl,
-      "endTimeList": endTimeList,
-      "audioDecodePaths": decodeAudioPathList,
-      "durationList": durationList,
-      "videoPath": localVideoPath,
-      "bgmPath": bgmPath,
-      "pathBgmDecode": bgmDecodePath,
-      "pathBgmRecordSync": audioSyncPath,
-      "pathBgmRecordDecodeSync": audioSyncDecodePath,
-      "pathVideoMixin": videoMixInPath,
-      "startTimeList": startTimeList,
-      "mixinName": mixinName
-    });
-  }
-
-  Future<void> cleanAudioData(String videoId) {
-    return _channel.invokeMethod("cleanAudioData", {
-      "videoId": videoId,
-      "pathAudio": recordPath,
-      "pathAudioDecode": recordDecodePath
-    });
-  }
-
-  Future<String> startMixinPaintedAudio(List<String> audioPaths, String bgmPath, List<int> durationList, List<int> endTimeList,
-      String audioDecodePath, String mixinFilePath, String encodePath,List<int> startTimeList) {
-    return _channel.invokeMethod("startMixinPaintedAudio",
-        {"audioPaths": audioPaths, "bgmPath": bgmPath, "durationList": durationList, "endTimeList": endTimeList,
-          "audioDecodePath": audioDecodePath, "mixinFilePath": mixinFilePath, "encodePath": encodePath,"startTimeList": startTimeList});
-  }
-
-  /// 初始化路径
-  void initPath(
-      {@required String videoPath, @required String recordPath, @required String recordDecodePath,
-        @required String bgmPath, @required String bgmDecodePath, @required String audioSyncPath, @required String audioSyncDecodePath,
-        @required String videoMixInPath}) {
-    this.videoPath = videoPath;
-    this.recordPath = recordPath;
-    this.recordDecodePath = recordDecodePath;
-    this.bgmPath = bgmPath;
-    this.bgmDecodePath = bgmDecodePath;
-    this.audioSyncPath = audioSyncPath;
-    this.audioSyncDecodePath = audioSyncDecodePath;
-    this.videoMixInPath = videoMixInPath;
-  }
-
-  /// 初始化listener 进入录音页面时调用
-  void initListener() {
-    _onProgressChange = new StreamController<int>.broadcast();
-    _onRecordProgressChange = new StreamController<int>.broadcast();
-    _onDecodeResultChange = new StreamController<Map>.broadcast();
-    _channel.setMethodCallHandler(_onMethodCallHandler);
-  }
-
-  /// 1.录音进度 2.解码录音回调 
-  Future _onMethodCallHandler(MethodCall call) {
-    switch (call.method) {
-      case "recordProgress":
-        _onRecordProgressChange.add(call.arguments['progress']);
-        break;
-      case "decodeResult":
-        _onDecodeResultChange.add(call.arguments);
-        break;
-    }
-    return null;
-  }
-
-  /// 关闭stream
-  void closeStream() {
-    _channel.setMethodCallHandler(null);
-    _onProgressChange.close();
-    _onRecordProgressChange.close();
-    _onDecodeResultChange.close();
+          "audioPaths": audioPaths,
+          "bgmPath": bgmPath,
+          "durationList": durationList, // Android used only
+          "endTimeList": endTimeList, // Android used only
+          "decodeDirPath": decodeDirPath, // Android used only
+          "mixinFilePath": mixinFilePath, // Android used only
+          "resultPath": resultPath,
+          "startTimeList": startTimeList, // iOS used only
+        });
+  }
+
+  Future<String> mixinVideoAndAudio(
+      {String bgmPath, String videoPath, String resultPath}) {
+    return _channel.invokeMethod("mixinVideoAndAudio",
+        {
+          "bgmPath": bgmPath,
+          "videoPath": videoPath,
+          "resultPath": resultPath,
+        });
   }
 }

+ 43 - 85
pubspec.lock

@@ -1,62 +1,55 @@
 # Generated by pub
 # See https://dart.dev/tools/pub/glossary#lockfile
 packages:
-  archive:
-    dependency: transitive
-    description:
-      name: archive
-      url: "https://pub.dev"
-    source: hosted
-    version: "2.0.11"
-  args:
-    dependency: transitive
-    description:
-      name: args
-      url: "https://pub.dev"
-    source: hosted
-    version: "1.5.2"
   async:
     dependency: transitive
     description:
       name: async
-      url: "https://pub.dev"
+      url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "2.4.0"
+    version: "2.4.2"
   boolean_selector:
     dependency: transitive
     description:
       name: boolean_selector
-      url: "https://pub.dev"
+      url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "1.0.5"
+    version: "2.0.0"
+  characters:
+    dependency: transitive
+    description:
+      name: characters
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.0"
   charcode:
     dependency: transitive
     description:
       name: charcode
-      url: "https://pub.dev"
+      url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "1.1.2"
-  collection:
+    version: "1.1.3"
+  clock:
     dependency: transitive
     description:
-      name: collection
-      url: "https://pub.dev"
+      name: clock
+      url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "1.14.11"
-  convert:
+    version: "1.0.1"
+  collection:
     dependency: transitive
     description:
-      name: convert
-      url: "https://pub.dev"
+      name: collection
+      url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "2.1.1"
-  crypto:
+    version: "1.14.13"
+  fake_async:
     dependency: transitive
     description:
-      name: crypto
-      url: "https://pub.dev"
+      name: fake_async
+      url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "2.1.3"
+    version: "1.1.0"
   flutter:
     dependency: "direct main"
     description: flutter
@@ -67,55 +60,27 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
-  image:
-    dependency: transitive
-    description:
-      name: image
-      url: "https://pub.dev"
-    source: hosted
-    version: "2.1.4"
   matcher:
     dependency: transitive
     description:
       name: matcher
-      url: "https://pub.dev"
+      url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "0.12.6"
+    version: "0.12.8"
   meta:
     dependency: transitive
     description:
       name: meta
-      url: "https://pub.dev"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.1.8"
   path:
     dependency: transitive
     description:
       name: path
-      url: "https://pub.dev"
-    source: hosted
-    version: "1.6.4"
-  pedantic:
-    dependency: transitive
-    description:
-      name: pedantic
-      url: "https://pub.dev"
+      url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "1.8.0+1"
-  petitparser:
-    dependency: transitive
-    description:
-      name: petitparser
-      url: "https://pub.dev"
-    source: hosted
-    version: "2.4.0"
-  quiver:
-    dependency: transitive
-    description:
-      name: quiver
-      url: "https://pub.dev"
-    source: hosted
-    version: "2.0.5"
+    version: "1.7.0"
   sky_engine:
     dependency: transitive
     description: flutter
@@ -125,64 +90,57 @@ packages:
     dependency: transitive
     description:
       name: source_span
-      url: "https://pub.dev"
+      url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "1.5.5"
+    version: "1.7.0"
   stack_trace:
     dependency: transitive
     description:
       name: stack_trace
-      url: "https://pub.dev"
+      url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "1.9.3"
+    version: "1.9.5"
   stream_channel:
     dependency: transitive
     description:
       name: stream_channel
-      url: "https://pub.dev"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     version: "2.0.0"
   string_scanner:
     dependency: transitive
     description:
       name: string_scanner
-      url: "https://pub.dev"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.0.5"
   term_glyph:
     dependency: transitive
     description:
       name: term_glyph
-      url: "https://pub.dev"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.1.0"
   test_api:
     dependency: transitive
     description:
       name: test_api
-      url: "https://pub.dev"
+      url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "0.2.11"
+    version: "0.2.17"
   typed_data:
     dependency: transitive
     description:
       name: typed_data
-      url: "https://pub.dev"
+      url: "https://pub.flutter-io.cn"
     source: hosted
-    version: "1.1.6"
+    version: "1.2.0"
   vector_math:
     dependency: transitive
     description:
       name: vector_math
-      url: "https://pub.dev"
+      url: "https://pub.flutter-io.cn"
     source: hosted
     version: "2.0.8"
-  xml:
-    dependency: transitive
-    description:
-      name: xml
-      url: "https://pub.dev"
-    source: hosted
-    version: "3.5.0"
 sdks:
-  dart: ">=2.4.0 <3.0.0"
+  dart: ">=2.9.0-14.0.dev <3.0.0"