Ben 7 éve
szülő
commit
dccc64004c
28 módosított fájl, 3476 hozzáadás és 206 törlés
  1. BIN
      .vs/miaomiao/v14/.suo
  2. BIN
      miaomiao/bin/Android/Debug/android-debug.apk
  3. 102 102
      miaomiao/config.xml
  4. 88 87
      miaomiao/package.json
  5. 3 0
      miaomiao/plugins/android.json
  6. 51 0
      miaomiao/plugins/cordova-plugin-video-editor/CHANGELOG.md
  7. 13 0
      miaomiao/plugins/cordova-plugin-video-editor/LICENSE.md
  8. 290 0
      miaomiao/plugins/cordova-plugin-video-editor/README.md
  9. 107 0
      miaomiao/plugins/cordova-plugin-video-editor/package.json
  10. 72 0
      miaomiao/plugins/cordova-plugin-video-editor/plugin.xml
  11. 92 0
      miaomiao/plugins/cordova-plugin-video-editor/src/android/CustomAndroidFormatStrategy.java
  12. 633 0
      miaomiao/plugins/cordova-plugin-video-editor/src/android/VideoEditor.java
  13. 16 0
      miaomiao/plugins/cordova-plugin-video-editor/src/android/build.gradle
  14. 194 0
      miaomiao/plugins/cordova-plugin-video-editor/src/ios/SDAVAssetExportSession.h
  15. 445 0
      miaomiao/plugins/cordova-plugin-video-editor/src/ios/SDAVAssetExportSession.m
  16. 31 0
      miaomiao/plugins/cordova-plugin-video-editor/src/ios/VideoEditor.h
  17. 577 0
      miaomiao/plugins/cordova-plugin-video-editor/src/ios/VideoEditor.m
  18. 206 0
      miaomiao/plugins/cordova-plugin-video-editor/src/windows/VideoEditorProxy.js
  19. 206 0
      miaomiao/plugins/cordova-plugin-video-editor/src/windows8/VideoEditorProxy.js
  20. 169 0
      miaomiao/plugins/cordova-plugin-video-editor/typings/VideoEditor.d.ts
  21. 61 0
      miaomiao/plugins/cordova-plugin-video-editor/www/VideoEditor.js
  22. 21 0
      miaomiao/plugins/cordova-plugin-video-editor/www/VideoEditorOptions.js
  23. 8 0
      miaomiao/plugins/fetch.json
  24. 3 0
      miaomiao/plugins/ios.json
  25. 2 2
      miaomiao/www/js/config/config.js
  26. 21 2
      miaomiao/www/js/controllers/account.js
  27. 64 13
      miaomiao/www/js/services/commonservice.js
  28. 1 0
      miaomiao/www/templates/account/login.html

BIN
.vs/miaomiao/v14/.suo


BIN
miaomiao/bin/Android/Debug/android-debug.apk


+ 102 - 102
miaomiao/config.xml

xqd
@@ -1,107 +1,107 @@
-<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<?xml version='1.0' encoding='utf-8'?>
 <widget id="com.miaomiao.app" version="0.1.0" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
-  <name>瞄喵</name>
-  <description>
+    <name>瞄喵</name>
+    <description>
         An Ionic Framework and Cordova project.
     </description>
-  <author email="you@example.com" href="http://example.com.com/">
+    <author email="you@example.com" href="http://example.com.com/">
       Mike
   </author>
-  <content src="index.html"/>
-  <access origin="*"/>
-  <allow-intent href="*"/>
-  <allow-navigation href="*"/>
-  <preference name="webviewbounce" value="false"/>
-  <preference name="UIWebViewBounce" value="false"/>
-  <preference name="DisallowOverscroll" value="true"/>
-  <preference name="android-minSdkVersion" value="16"/>
-  <preference name="BackupWebStorage" value="none"/>
-  <preference name="KeepRunning" value="True"/>
-  <preference name="ShowTitle" value="True"/>
-  <preference name="InAppBrowserStorageEnabled" value="True"/>
-  <preference name="SuppressesIncrementalRendering" value="True"/>
-  <preference name="windows-target-version" value="10.0"/>
-  <preference name="SplashScreen" value="screen"/>
-  <preference name="KeyboardDisplayRequiresUserAction" value="false"/>
-  <preference name="SplashScreenDelay" value="3000"/>
-  <feature name="StatusBar">
-    <param name="ios-package" onload="true" value="CDVStatusBar"/>
-  </feature>
-  <platform name="ios">
-    <icon height="57" src="resources/ios/icon/icon.png" width="57"/>
-    <icon height="114" src="resources/ios/icon/icon@2x.png" width="114"/>
-    <icon height="40" src="resources/ios/icon/icon-40.png" width="40"/>
-    <icon height="80" src="resources/ios/icon/icon-40@2x.png" width="80"/>
-    <icon height="120" src="resources/ios/icon/icon-40@3x.png" width="120"/>
-    <icon height="50" src="resources/ios/icon/icon-50.png" width="50"/>
-    <icon height="100" src="resources/ios/icon/icon-50@2x.png" width="100"/>
-    <icon height="60" src="resources/ios/icon/icon-60.png" width="60"/>
-    <icon height="120" src="resources/ios/icon/icon-60@2x.png" width="120"/>
-    <icon height="180" src="resources/ios/icon/icon-60@3x.png" width="180"/>
-    <icon height="72" src="resources/ios/icon/icon-72.png" width="72"/>
-    <icon height="144" src="resources/ios/icon/icon-72@2x.png" width="144"/>
-    <icon height="76" src="resources/ios/icon/icon-76.png" width="76"/>
-    <icon height="152" src="resources/ios/icon/icon-76@2x.png" width="152"/>
-    <icon height="29" src="resources/ios/icon/icon-small.png" width="29"/>
-    <icon height="58" src="resources/ios/icon/icon-small@2x.png" width="58"/>
-    <icon height="87" src="resources/ios/icon/icon-small@3x.png" width="87"/>
-    <splash height="1136" src="resources/ios/splash/Default-568h@2x~iphone.png" width="640"/>
-    <splash height="1334" src="resources/ios/splash/Default-667h.png" width="750"/>
-    <splash height="2208" src="resources/ios/splash/Default-736h.png" width="1242"/>
-    <splash height="1242" src="resources/ios/splash/Default-Landscape-736h.png" width="2208"/>
-    <splash height="1536" src="resources/ios/splash/Default-Landscape@2x~ipad.png" width="2048"/>
-    <splash height="2048" src="resources/ios/splash/Default-Landscape@~ipadpro.png" width="2732"/>
-    <splash height="768" src="resources/ios/splash/Default-Landscape~ipad.png" width="1024"/>
-    <splash height="2048" src="resources/ios/splash/Default-Portrait@2x~ipad.png" width="1536"/>
-    <splash height="2732" src="resources/ios/splash/Default-Portrait@~ipadpro.png" width="2048"/>
-    <splash height="1024" src="resources/ios/splash/Default-Portrait~ipad.png" width="768"/>
-    <splash height="960" src="resources/ios/splash/Default@2x~iphone.png" width="640"/>
-    <splash height="480" src="resources/ios/splash/Default~iphone.png" width="320"/>
-  </platform>
-  <platform name="android">
-    <icon density="ldpi" src="resources/android/icon/drawable-ldpi-icon.png"/>
-    <icon density="mdpi" src="resources/android/icon/drawable-mdpi-icon.png"/>
-    <icon density="hdpi" src="resources/android/icon/drawable-hdpi-icon.png"/>
-    <icon density="xhdpi" src="resources/android/icon/drawable-xhdpi-icon.png"/>
-    <icon density="xxhdpi" src="resources/android/icon/drawable-xxhdpi-icon.png"/>
-    <icon density="xxxhdpi" src="resources/android/icon/drawable-xxxhdpi-icon.png"/>
-    <splash density="land-ldpi" src="resources/android/splash/drawable-land-ldpi-screen.png"/>
-    <splash density="land-mdpi" src="resources/android/splash/drawable-land-mdpi-screen.png"/>
-    <splash density="land-hdpi" src="resources/android/splash/drawable-land-hdpi-screen.png"/>
-    <splash density="land-xhdpi" src="resources/android/splash/drawable-land-xhdpi-screen.png"/>
-    <splash density="land-xxhdpi" src="resources/android/splash/drawable-land-xxhdpi-screen.png"/>
-    <splash density="land-xxxhdpi" src="resources/android/splash/drawable-land-xxxhdpi-screen.png"/>
-    <splash density="port-ldpi" src="resources/android/splash/drawable-port-ldpi-screen.png"/>
-    <splash density="port-mdpi" src="resources/android/splash/drawable-port-mdpi-screen.png"/>
-    <splash density="port-hdpi" src="resources/android/splash/drawable-port-hdpi-screen.png"/>
-    <splash density="port-xhdpi" src="resources/android/splash/drawable-port-xhdpi-screen.png"/>
-    <splash density="port-xxhdpi" src="resources/android/splash/drawable-port-xxhdpi-screen.png"/>
-    <splash density="port-xxxhdpi" src="resources/android/splash/drawable-port-xxxhdpi-screen.png"/>
-  </platform>
-  <engine name="android" spec="^6.2.3"/>
-  <plugin name="cordova-hot-code-push-plugin" spec="^1.5.3"/>
-  <plugin name="cordova-plugin-actionsheet" spec="2.3.3"/>
-  <plugin name="cordova-plugin-camera" spec="file:node_modules\cordova-plugin-camera"/>
-  <plugin name="cordova-plugin-compat" spec="1.1.0"/>
-  <plugin name="cordova-plugin-console" spec="~1.0.2"/>
-  <plugin name="cordova-plugin-device" spec="~1.1.1"/>
-  <plugin name="cordova-plugin-file" spec="4.3.2"/>
-  <plugin name="cordova-plugin-file-transfer" spec="1.6.2"/>
-  <plugin name="cordova-plugin-media-capture" spec="1.4.3"/>
-  <plugin name="cordova-plugin-offbye-alipay" spec="git+https://github.com/offbye/cordova-plugin-alipay.git">
-    <variable name="PARTNER_ID" value="2088721135315822"/>
-  </plugin>
-  <plugin name="cordova-plugin-splashscreen" spec="4.0.3"/>
-  <plugin name="cordova-plugin-statusbar" spec="~2.1.0"/>
-  <plugin name="cordova-plugin-wechat" spec="^2.0.0">
-    <variable name="WECHATAPPID" value="wxc5181c0d406023e6"/>
-  </plugin>
-  <plugin name="cordova-plugin-whitelist" spec="^1.3.2"/>
-  <plugin name="ionic-plugin-keyboard" spec="~1.0.9"/>
-  <plugin name="jpush-phonegap-plugin" spec="^3.2.3">
-    <variable name="APP_KEY" value="69838317211448192366f9d8"/>
-  </plugin>
-  <plugin name="phonegap-plugin-barcodescanner" spec="git+https://github.com/phonegap/phonegap-plugin-barcodescanner.git">
-    <variable name="CAMERA_USAGE_DESCRIPTION" value="请摄像头对准条码"/>
-  </plugin>
-</widget>
+    <content src="index.html" />
+    <access origin="*" />
+    <allow-intent href="*" />
+    <allow-navigation href="*" />
+    <preference name="webviewbounce" value="false" />
+    <preference name="UIWebViewBounce" value="false" />
+    <preference name="DisallowOverscroll" value="true" />
+    <preference name="android-minSdkVersion" value="18" />
+    <preference name="BackupWebStorage" value="none" />
+    <preference name="KeepRunning" value="True" />
+    <preference name="ShowTitle" value="True" />
+    <preference name="InAppBrowserStorageEnabled" value="True" />
+    <preference name="SuppressesIncrementalRendering" value="True" />
+    <preference name="windows-target-version" value="10.0" />
+    <preference name="SplashScreen" value="screen" />
+    <preference name="KeyboardDisplayRequiresUserAction" value="false" />
+    <preference name="SplashScreenDelay" value="3000" />
+    <feature name="StatusBar">
+        <param name="ios-package" onload="true" value="CDVStatusBar" />
+    </feature>
+    <platform name="ios">
+        <icon height="57" src="resources/ios/icon/icon.png" width="57" />
+        <icon height="114" src="resources/ios/icon/icon@2x.png" width="114" />
+        <icon height="40" src="resources/ios/icon/icon-40.png" width="40" />
+        <icon height="80" src="resources/ios/icon/icon-40@2x.png" width="80" />
+        <icon height="120" src="resources/ios/icon/icon-40@3x.png" width="120" />
+        <icon height="50" src="resources/ios/icon/icon-50.png" width="50" />
+        <icon height="100" src="resources/ios/icon/icon-50@2x.png" width="100" />
+        <icon height="60" src="resources/ios/icon/icon-60.png" width="60" />
+        <icon height="120" src="resources/ios/icon/icon-60@2x.png" width="120" />
+        <icon height="180" src="resources/ios/icon/icon-60@3x.png" width="180" />
+        <icon height="72" src="resources/ios/icon/icon-72.png" width="72" />
+        <icon height="144" src="resources/ios/icon/icon-72@2x.png" width="144" />
+        <icon height="76" src="resources/ios/icon/icon-76.png" width="76" />
+        <icon height="152" src="resources/ios/icon/icon-76@2x.png" width="152" />
+        <icon height="29" src="resources/ios/icon/icon-small.png" width="29" />
+        <icon height="58" src="resources/ios/icon/icon-small@2x.png" width="58" />
+        <icon height="87" src="resources/ios/icon/icon-small@3x.png" width="87" />
+        <splash height="1136" src="resources/ios/splash/Default-568h@2x~iphone.png" width="640" />
+        <splash height="1334" src="resources/ios/splash/Default-667h.png" width="750" />
+        <splash height="2208" src="resources/ios/splash/Default-736h.png" width="1242" />
+        <splash height="1242" src="resources/ios/splash/Default-Landscape-736h.png" width="2208" />
+        <splash height="1536" src="resources/ios/splash/Default-Landscape@2x~ipad.png" width="2048" />
+        <splash height="2048" src="resources/ios/splash/Default-Landscape@~ipadpro.png" width="2732" />
+        <splash height="768" src="resources/ios/splash/Default-Landscape~ipad.png" width="1024" />
+        <splash height="2048" src="resources/ios/splash/Default-Portrait@2x~ipad.png" width="1536" />
+        <splash height="2732" src="resources/ios/splash/Default-Portrait@~ipadpro.png" width="2048" />
+        <splash height="1024" src="resources/ios/splash/Default-Portrait~ipad.png" width="768" />
+        <splash height="960" src="resources/ios/splash/Default@2x~iphone.png" width="640" />
+        <splash height="480" src="resources/ios/splash/Default~iphone.png" width="320" />
+    </platform>
+    <platform name="android">
+        <icon density="ldpi" src="resources/android/icon/drawable-ldpi-icon.png" />
+        <icon density="mdpi" src="resources/android/icon/drawable-mdpi-icon.png" />
+        <icon density="hdpi" src="resources/android/icon/drawable-hdpi-icon.png" />
+        <icon density="xhdpi" src="resources/android/icon/drawable-xhdpi-icon.png" />
+        <icon density="xxhdpi" src="resources/android/icon/drawable-xxhdpi-icon.png" />
+        <icon density="xxxhdpi" src="resources/android/icon/drawable-xxxhdpi-icon.png" />
+        <splash density="land-ldpi" src="resources/android/splash/drawable-land-ldpi-screen.png" />
+        <splash density="land-mdpi" src="resources/android/splash/drawable-land-mdpi-screen.png" />
+        <splash density="land-hdpi" src="resources/android/splash/drawable-land-hdpi-screen.png" />
+        <splash density="land-xhdpi" src="resources/android/splash/drawable-land-xhdpi-screen.png" />
+        <splash density="land-xxhdpi" src="resources/android/splash/drawable-land-xxhdpi-screen.png" />
+        <splash density="land-xxxhdpi" src="resources/android/splash/drawable-land-xxxhdpi-screen.png" />
+        <splash density="port-ldpi" src="resources/android/splash/drawable-port-ldpi-screen.png" />
+        <splash density="port-mdpi" src="resources/android/splash/drawable-port-mdpi-screen.png" />
+        <splash density="port-hdpi" src="resources/android/splash/drawable-port-hdpi-screen.png" />
+        <splash density="port-xhdpi" src="resources/android/splash/drawable-port-xhdpi-screen.png" />
+        <splash density="port-xxhdpi" src="resources/android/splash/drawable-port-xxhdpi-screen.png" />
+        <splash density="port-xxxhdpi" src="resources/android/splash/drawable-port-xxxhdpi-screen.png" />
+    </platform>
+    <plugin name="cordova-hot-code-push-plugin" spec="^1.5.3" />
+    <plugin name="cordova-plugin-actionsheet" spec="2.3.3" />
+    <plugin name="cordova-plugin-camera" spec="file:node_modules\cordova-plugin-camera" />
+    <plugin name="cordova-plugin-compat" spec="1.1.0" />
+    <plugin name="cordova-plugin-console" spec="~1.0.2" />
+    <plugin name="cordova-plugin-device" spec="~1.1.1" />
+    <plugin name="cordova-plugin-file" spec="4.3.2" />
+    <plugin name="cordova-plugin-file-transfer" spec="1.6.2" />
+    <plugin name="cordova-plugin-media-capture" spec="1.4.3" />
+    <plugin name="cordova-plugin-offbye-alipay" spec="git+https://github.com/offbye/cordova-plugin-alipay.git">
+        <variable name="PARTNER_ID" value="2088721135315822" />
+    </plugin>
+    <plugin name="cordova-plugin-splashscreen" spec="4.0.3" />
+    <plugin name="cordova-plugin-statusbar" spec="~2.1.0" />
+    <plugin name="cordova-plugin-video-editor" spec="^1.1.3" />
+    <plugin name="cordova-plugin-wechat" spec="^2.0.0">
+        <variable name="WECHATAPPID" value="wxc5181c0d406023e6" />
+    </plugin>
+    <plugin name="cordova-plugin-whitelist" spec="^1.3.2" />
+    <plugin name="ionic-plugin-keyboard" spec="~1.0.9" />
+    <plugin name="jpush-phonegap-plugin" spec="^3.2.3">
+        <variable name="APP_KEY" value="69838317211448192366f9d8" />
+    </plugin>
+    <plugin name="phonegap-plugin-barcodescanner" spec="git+https://github.com/phonegap/phonegap-plugin-barcodescanner.git">
+        <variable name="CAMERA_USAGE_DESCRIPTION" value="请摄像头对准条码" />
+    </plugin>
+</widget>

+ 88 - 87
miaomiao/package.json

xqd
@@ -1,89 +1,90 @@
 {
-    "name": "ionic-tabs",
-    "version": "1.1.1",
-    "description": "An Ionic project",
-    "dependencies": {
-        "cordova-android": "^6.2.3",
-        "cordova-hot-code-push-plugin": "^1.5.3",
-        "cordova-ios": "^4.4.0",
-        "cordova-plugin-actionsheet": "2.3.3",
-        "cordova-plugin-camera": "file:node_modules\\cordova-plugin-camera",
-        "cordova-plugin-compat": "1.1.0",
-        "cordova-plugin-console": "~1.0.2",
-        "cordova-plugin-device": "~1.1.1",
-        "cordova-plugin-file": "4.3.2",
-        "cordova-plugin-file-transfer": "1.6.2",
-        "cordova-plugin-jcore": "^1.1.8",
-        "cordova-plugin-media-capture": "1.4.3",
-        "cordova-plugin-offbye-alipay": "git+https://github.com/offbye/cordova-plugin-alipay.git",
-        "cordova-plugin-splashscreen": "4.0.3",
-        "cordova-plugin-statusbar": "~2.1.0",
-        "cordova-plugin-wechat": "^2.0.0",
-        "cordova-plugin-whitelist": "^1.3.2",
-        "gulp": "^3.5.6",
-        "gulp-concat": "^2.2.0",
-        "gulp-minify-css": "^0.3.0",
-        "gulp-rename": "^1.2.0",
-        "gulp-sass": "^2.0.4",
-        "ionic-plugin-keyboard": "~1.0.9",
-        "jpush-phonegap-plugin": "^3.2.3",
-        "phonegap-plugin-barcodescanner": "git+https://github.com/phonegap/phonegap-plugin-barcodescanner.git"
+  "name": "ionic-tabs",
+  "version": "1.1.1",
+  "description": "An Ionic project",
+  "dependencies": {
+    "cordova-android": "^6.2.3",
+    "cordova-hot-code-push-plugin": "^1.5.3",
+    "cordova-ios": "^4.4.0",
+    "cordova-plugin-actionsheet": "2.3.3",
+    "cordova-plugin-camera": "file:node_modules\\cordova-plugin-camera",
+    "cordova-plugin-compat": "1.1.0",
+    "cordova-plugin-console": "~1.0.2",
+    "cordova-plugin-device": "~1.1.1",
+    "cordova-plugin-file": "4.3.2",
+    "cordova-plugin-file-transfer": "1.6.2",
+    "cordova-plugin-jcore": "^1.1.8",
+    "cordova-plugin-media-capture": "1.4.3",
+    "cordova-plugin-offbye-alipay": "git+https://github.com/offbye/cordova-plugin-alipay.git",
+    "cordova-plugin-splashscreen": "4.0.3",
+    "cordova-plugin-statusbar": "~2.1.0",
+    "cordova-plugin-video-editor": "^1.1.3",
+    "cordova-plugin-wechat": "^2.0.0",
+    "cordova-plugin-whitelist": "^1.3.2",
+    "gulp": "^3.5.6",
+    "gulp-concat": "^2.2.0",
+    "gulp-minify-css": "^0.3.0",
+    "gulp-rename": "^1.2.0",
+    "gulp-sass": "^2.0.4",
+    "ionic-plugin-keyboard": "~1.0.9",
+    "jpush-phonegap-plugin": "^3.2.3",
+    "phonegap-plugin-barcodescanner": "git+https://github.com/phonegap/phonegap-plugin-barcodescanner.git"
+  },
+  "devDependencies": {
+    "@ionic/cli-plugin-ionic1": "1.1.2",
+    "bower": "^1.3.3",
+    "gulp-sass": "^2.3.2",
+    "gulp-util": "^2.2.14",
+    "ionic": "3.7.0",
+    "shelljs": "^0.3.0"
+  },
+  "cordovaPlugins": [
+    "cordova-plugin-device",
+    "cordova-plugin-console",
+    "cordova-plugin-statusbar",
+    "ionic-plugin-keyboard",
+    "cordova-plugin-file-transfer",
+    "phonegap-plugin-barcodescanner",
+    "cordova-plugin-whitelist",
+    "cordova-hot-code-push-plugin",
+    "cordova-plugin-camera",
+    "jpush-phonegap-plugin",
+    "cordova-plugin-wechat",
+    "cordova-plugin-video-editor"
+  ],
+  "cordovaPlatforms": [
+    "android",
+    "ios"
+  ],
+  "cordova": {
+    "plugins": {
+      "cordova-plugin-actionsheet": {},
+      "cordova-plugin-compat": {},
+      "cordova-plugin-console": {},
+      "cordova-plugin-device": {},
+      "cordova-plugin-file": {},
+      "cordova-plugin-file-transfer": {},
+      "cordova-plugin-media-capture": {},
+      "cordova-plugin-splashscreen": {},
+      "cordova-plugin-statusbar": {},
+      "ionic-plugin-keyboard": {},
+      "phonegap-plugin-barcodescanner": {
+        "CAMERA_USAGE_DESCRIPTION": "请摄像头对准条码"
+      },
+      "cordova-plugin-offbye-alipay": {
+        "PARTNER_ID": "2088721135315822"
+      },
+      "cordova-plugin-whitelist": {},
+      "cordova-hot-code-push-plugin": {},
+      "cordova-plugin-camera": {},
+      "jpush-phonegap-plugin": {
+        "APP_KEY": "69838317211448192366f9d8"
+      },
+      "cordova-plugin-wechat": {
+        "WECHATAPPID": "wxc5181c0d406023e6"
+      },
+      "cordova-plugin-video-editor": {}
     },
-    "devDependencies": {
-        "@ionic/cli-plugin-ionic1": "1.1.2",
-        "bower": "^1.3.3",
-        "gulp-sass": "^2.3.2",
-        "gulp-util": "^2.2.14",
-        "ionic": "3.7.0",
-        "shelljs": "^0.3.0"
-    },
-    "cordovaPlugins": [
-        "cordova-plugin-device",
-        "cordova-plugin-console",
-        "cordova-plugin-statusbar",
-        "ionic-plugin-keyboard",
-        "cordova-plugin-file-transfer",
-        "phonegap-plugin-barcodescanner",
-        "cordova-plugin-whitelist",
-        "cordova-hot-code-push-plugin",
-        "cordova-plugin-camera",
-        "jpush-phonegap-plugin",
-        "cordova-plugin-wechat"
-    ],
-    "cordovaPlatforms": [
-        "android",
-        "ios"
-    ],
-    "cordova": {
-        "plugins": {
-            "cordova-plugin-actionsheet": {},
-            "cordova-plugin-compat": {},
-            "cordova-plugin-console": {},
-            "cordova-plugin-device": {},
-            "cordova-plugin-file": {},
-            "cordova-plugin-file-transfer": {},
-            "cordova-plugin-media-capture": {},
-            "cordova-plugin-splashscreen": {},
-            "cordova-plugin-statusbar": {},
-            "ionic-plugin-keyboard": {},
-            "phonegap-plugin-barcodescanner": {
-                "CAMERA_USAGE_DESCRIPTION": "请摄像头对准条码"
-            },
-            "cordova-plugin-offbye-alipay": {
-                "PARTNER_ID": "2088721135315822"
-            },
-            "cordova-plugin-whitelist": {},
-            "cordova-hot-code-push-plugin": {},
-            "cordova-plugin-camera": {},
-            "jpush-phonegap-plugin": {
-                "APP_KEY": "69838317211448192366f9d8"
-            },
-            "cordova-plugin-wechat": {
-                "WECHATAPPID": "wxc5181c0d406023e6"
-            }
-        },
-        "platforms": [
-            "android"
-        ]
-    }
-}
+    "platforms": []
+  }
+}

+ 3 - 0
miaomiao/plugins/android.json

xqd
@@ -37,6 +37,9 @@
         },
         "cordova-plugin-statusbar": {
             "PACKAGE_NAME": "com.miaomiao.app"
+        },
+        "cordova-plugin-video-editor": {
+            "PACKAGE_NAME": "com.miaomiao.app"
         }
     },
     "dependent_plugins": {

+ 51 - 0
miaomiao/plugins/cordova-plugin-video-editor/CHANGELOG.md

xqd
@@ -0,0 +1,51 @@
+# Change Log
+
+## [1.0.0](https://github.com/jbavari/cordova-plugin-video-editor/tree/1.0.0) (2015-11-11)
+[Full Changelog](https://github.com/jbavari/cordova-plugin-video-editor/compare/0.0.6...1.0.0)
+
+**Implemented enhancements:**
+
+- trim start point [\#16](https://github.com/jbavari/cordova-plugin-video-editor/issues/16)
+
+**Closed issues:**
+
+- VideoEditor reference error [\#31](https://github.com/jbavari/cordova-plugin-video-editor/issues/31)
+- Setting trim start [\#28](https://github.com/jbavari/cordova-plugin-video-editor/issues/28)
+- Stretched and rotated video when in portrait mode [\#9](https://github.com/jbavari/cordova-plugin-video-editor/issues/9)
+
+## [0.0.6](https://github.com/jbavari/cordova-plugin-video-editor/tree/0.0.6) (2015-11-11)
+[Full Changelog](https://github.com/jbavari/cordova-plugin-video-editor/compare/0.0.5...0.0.6)
+
+## [0.0.5](https://github.com/jbavari/cordova-plugin-video-editor/tree/0.0.5) (2015-11-10)
+[Full Changelog](https://github.com/jbavari/cordova-plugin-video-editor/compare/0.0.4...0.0.5)
+
+**Closed issues:**
+
+- Selecting Videos from Gallery for transcoding [\#33](https://github.com/jbavari/cordova-plugin-video-editor/issues/33)
+- is trimming working any android version? [\#27](https://github.com/jbavari/cordova-plugin-video-editor/issues/27)
+- Input files are remove by default after encoding [\#23](https://github.com/jbavari/cordova-plugin-video-editor/issues/23)
+- Example project installation or installation instructions? [\#22](https://github.com/jbavari/cordova-plugin-video-editor/issues/22)
+- Transcoding not working in Jelly Bean [\#15](https://github.com/jbavari/cordova-plugin-video-editor/issues/15)
+- Not valid import com.netcompss.loader.LoadJNI; [\#8](https://github.com/jbavari/cordova-plugin-video-editor/issues/8)
+
+## [0.0.4](https://github.com/jbavari/cordova-plugin-video-editor/tree/0.0.4) (2015-11-09)
+**Closed issues:**
+
+- Plugin won't compile with Cordova 5 [\#26](https://github.com/jbavari/cordova-plugin-video-editor/issues/26)
+- Error generating manifest, icon value also present in... [\#24](https://github.com/jbavari/cordova-plugin-video-editor/issues/24)
+- Consider switching from ffmpeg4android library as it costs $400 [\#20](https://github.com/jbavari/cordova-plugin-video-editor/issues/20)
+- File not Found but ... file exists [\#11](https://github.com/jbavari/cordova-plugin-video-editor/issues/11)
+- Any Documentation? [\#1](https://github.com/jbavari/cordova-plugin-video-editor/issues/1)
+
+**Merged pull requests:**
+
+- Abandon ffmpeg4android\_lib in favor of android-ffmpeg-java [\#13](https://github.com/jbavari/cordova-plugin-video-editor/pull/13) ([rossmartin](https://github.com/rossmartin))
+- Add option to save to gallery \(android\). [\#12](https://github.com/jbavari/cordova-plugin-video-editor/pull/12) ([rossmartin](https://github.com/rossmartin))
+- Add createThumbnail support [\#5](https://github.com/jbavari/cordova-plugin-video-editor/pull/5) ([rossmartin](https://github.com/rossmartin))
+- Updated readme \(VideoEditorPlugin --\> VideoEditor\) [\#4](https://github.com/jbavari/cordova-plugin-video-editor/pull/4) ([rossmartin](https://github.com/rossmartin))
+- Added transcoding support for android and video trimming. [\#3](https://github.com/jbavari/cordova-plugin-video-editor/pull/3) ([rossmartin](https://github.com/rossmartin))
+- Fixed JS errors in plugin files and updated transcoding [\#2](https://github.com/jbavari/cordova-plugin-video-editor/pull/2) ([rossmartin](https://github.com/rossmartin))
+
+
+
+\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*

+ 13 - 0
miaomiao/plugins/cordova-plugin-video-editor/LICENSE.md

xqd
@@ -0,0 +1,13 @@
+# Licenses
+
+## Android version
+
+Apache 2.0
+
+## iOS version
+
+MIT
+
+## Windows version
+
+Apache 2.0

+ 290 - 0
miaomiao/plugins/cordova-plugin-video-editor/README.md

xqd
@@ -0,0 +1,290 @@
+[![npm version](https://badge.fury.io/js/cordova-plugin-video-editor.svg)](https://badge.fury.io/js/cordova-plugin-video-editor)
+
+This is a cordova plugin to assist in several video editing tasks such as:
+
+* Transcoding
+* Trimming
+* Creating thumbnails from a video file (now at a specific time in the video)
+* Getting info on a video - width, height, orientation, duration, size, & bitrate.
+
+After looking at an article on [How Vine Satisfied Its Need for Speed](http://www.technologyreview.com/view/510511/how-vine-satisfies-its-need-for-speed/), it was clear Cordova/Phonegap needed a way to modify videos to be faster for app's that need that speed.
+
+This plugin will address those concerns, hopefully.
+
+## Installation
+```
+cordova plugin add cordova-plugin-video-editor
+```
+`VideoEditor` and `VideoEditorOptions` will be available in the window after deviceready.
+
+## Usage
+
+### Transcode a video
+
+```javascript
+// parameters passed to transcodeVideo
+VideoEditor.transcodeVideo(
+    success, // success cb
+    error, // error cb
+    {
+        fileUri: 'file-uri-here', // the path to the video on the device
+        outputFileName: 'output-name', // the file name for the transcoded video
+        outputFileType: VideoEditorOptions.OutputFileType.MPEG4, // android is always mp4
+        optimizeForNetworkUse: VideoEditorOptions.OptimizeForNetworkUse.YES, // ios only
+        saveToLibrary: true, // optional, defaults to true
+        deleteInputFile: false, // optional (android only), defaults to false
+        maintainAspectRatio: true, // optional (ios only), defaults to true
+        width: 640, // optional, see note below on width and height
+        height: 640, // optional, see notes below on width and height
+        videoBitrate: 1000000, // optional, bitrate in bits, defaults to 1 megabit (1000000)
+        fps: 24, // optional (android only), defaults to 24
+        audioChannels: 2, // optional (ios only), number of audio channels, defaults to 2
+        audioSampleRate: 44100, // optional (ios only), sample rate for the audio, defaults to 44100
+        audioBitrate: 128000, // optional (ios only), audio bitrate for the video in bits, defaults to 128 kilobits (128000)
+        progress: function(info) {} // info will be a number from 0 to 100
+    }
+);
+```
+#### A note on width and height used by transcodeVideo
+I recommend setting `maintainAspectRatio` to true.  When this option is true you can provide any width/height and the height provided will be used to calculate the new width for the output video.  If you set `maintainAspectRatio` false there is a good chance you'll end up with videos that are stretched and/or distorted.  Here is the simplified formula used on iOS when `maintainAspectRatio` is true -
+```objective-c
+aspectRatio = videoWidth / videoHeight;
+outputWidth = height * aspectRatio;
+outputHeight = outputWidth / aspectRatio;
+```
+
+Android will always use the aspect ratio of the input video to calculate the scaled output width and height.  Setting `maintainAspectRatio` on android will make not make a difference.
+
+If you don't provide width and height to `transcodeVideo` the output video will have the same dimensions as the input video.
+
+#### transcodeVideo example -
+```javascript
+// options used with transcodeVideo function
+// VideoEditorOptions is global, no need to declare it
+var VideoEditorOptions = {
+    OptimizeForNetworkUse: {
+        NO: 0,
+        YES: 1
+    },
+    OutputFileType: {
+        M4V: 0,
+        MPEG4: 1,
+        M4A: 2,
+        QUICK_TIME: 3
+    }
+};
+```
+```javascript
+// this example uses the cordova media capture plugin
+navigator.device.capture.captureVideo(
+    videoCaptureSuccess,
+    videoCaptureError,
+    {
+        limit: 1,
+        duration: 20
+    }
+);
+
+function videoCaptureSuccess(mediaFiles) {
+    // Wrap this below in a ~100 ms timeout on Android if
+    // you just recorded the video using the capture plugin.
+    // For some reason it is not available immediately in the file system.
+
+    var file = mediaFiles[0];
+    var videoFileName = 'video-name-here'; // I suggest a uuid
+
+    VideoEditor.transcodeVideo(
+        videoTranscodeSuccess,
+        videoTranscodeError,
+        {
+            fileUri: file.fullPath,
+            outputFileName: videoFileName,
+            outputFileType: VideoEditorOptions.OutputFileType.MPEG4,
+            optimizeForNetworkUse: VideoEditorOptions.OptimizeForNetworkUse.YES,
+            saveToLibrary: true,
+            maintainAspectRatio: true,
+            width: 640,
+            height: 640,
+            videoBitrate: 1000000, // 1 megabit
+            audioChannels: 2,
+            audioSampleRate: 44100,
+            audioBitrate: 128000, // 128 kilobits
+            progress: function(info) {
+                console.log('transcodeVideo progress callback, info: ' + info);
+            }
+        }
+    );
+}
+
+function videoTranscodeSuccess(result) {
+	// result is the path to the transcoded video on the device
+    console.log('videoTranscodeSuccess, result: ' + result);
+}
+
+function videoTranscodeError(err) {
+	console.log('videoTranscodeError, err: ' + err);
+}
+```
+
+#### Windows Quirks
+Windows does not support any of the optional parameters at this time. Specifying them will not cause an error but, there is no functionality behind them.
+
+### Trim a Video (iOS only)
+```javascript
+VideoEditor.trim(
+    trimSuccess,
+    trimFail,
+    {
+        fileUri: 'file-uri-here', // path to input video
+        trimStart: 5, // time to start trimming in seconds
+        trimEnd: 15, // time to end trimming in seconds
+        outputFileName: 'output-name', // output file name
+        progress: function(info) {} // optional, see docs on progress
+    }
+);
+
+function trimSuccess(result) {
+    // result is the path to the trimmed video on the device
+    console.log('trimSuccess, result: ' + result);
+}
+
+function trimFail(err) {
+    console.log('trimFail, err: ' + err);
+}
+```
+
+### Create a JPEG thumbnail from a video
+```javascript
+VideoEditor.createThumbnail(
+    success, // success cb
+    error, // error cb
+    {
+        fileUri: 'file-uri-here', // the path to the video on the device
+        outputFileName: 'output-name', // the file name for the JPEG image
+        atTime: 2, // optional, location in the video to create the thumbnail (in seconds)
+        width: 320, // optional, width of the thumbnail
+        height: 480, // optional, height of the thumbnail
+        quality: 100 // optional, quality of the thumbnail (between 1 and 100)
+    }
+);
+// atTime will default to 0 if not provided
+// width and height will be the same as the video input if they are not provided
+// quality will default to 100 if not provided
+```
+
+```javascript
+// this example uses the cordova media capture plugin
+navigator.device.capture.captureVideo(
+    videoCaptureSuccess,
+    videoCaptureError,
+    {
+        limit: 1,
+        duration: 20
+    }
+);
+
+function videoCaptureSuccess(mediaFiles) {
+    // Wrap this below in a ~100 ms timeout on Android if
+    // you just recorded the video using the capture plugin.
+    // For some reason it is not available immediately in the file system.
+
+    var file = mediaFiles[0];
+    var videoFileName = 'video-name-here'; // I suggest a uuid
+
+    VideoEditor.createThumbnail(
+        createThumbnailSuccess,
+        createThumbnailError,
+        {
+            fileUri: file.fullPath,
+            outputFileName: videoFileName,
+            atTime: 2,
+            width: 320,
+            height: 480,
+            quality: 100
+        }
+    );
+}
+
+function createThumbnailSuccess(result) {
+    // result is the path to the jpeg image on the device
+    console.log('createThumbnailSuccess, result: ' + result);
+}
+```
+
+#### A note on width and height used by createThumbnail
+The aspect ratio of the thumbnail created will match that of the video input.  This means you may not get exactly the width and height dimensions you give to `createThumbnail` for the jpeg.  This for your convenience but let us know if it is a problem.  I am considering adding a `maintainAspectRatio` option to `createThumbnail` (and when this option is false you might have stretched, square thumbnails :laughing:).
+
+### Get info on a video (width, height, orientation, duration, size, & bitrate)
+```javascript
+VideoEditor.getVideoInfo(
+    success, // success cb
+    error, // error cb
+    {
+        fileUri: 'file-uri-here', // the path to the video on the device
+    }
+);
+```
+
+```javascript
+VideoEditor.getVideoInfo(
+    getVideoInfoSuccess,
+    getVideoInfoError,
+    {
+        fileUri: file.fullPath
+    }
+);
+
+function getVideoInfoSuccess(info) {
+    console.log('getVideoInfoSuccess, info: ' + JSON.stringify(info, null, 2));
+    // info is a JSON object with the following properties -
+    {
+        width: 1920,
+        height: 1080,
+        orientation: 'landscape', // will be portrait or landscape
+        duration: 3.541, // duration in seconds
+        size: 6830126, // size of the video in bytes
+        bitrate: 15429777 // bitrate of the video in bits per second
+    }
+}
+```
+
+## Android & FFmpeg
+FFmpeg has been removed from android for several reasons but mainly for performance.  If you still need the old functionality that FFmpeg provided  [V1.09](https://github.com/jbavari/cordova-plugin-video-editor/tree/1.0.9) is the last version that will use it.
+
+## On iOS
+
+[iOS Developer AVFoundation Documentation](https://developer.apple.com/library/ios/documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/01_UsingAssets.html#//apple_ref/doc/uid/TP40010188-CH7-SW8)
+
+[Video compression in AVFoundation](http://www.iphonedevsdk.com/forum/iphone-sdk-development/110246-video-compression-avassetwriter-in-avfoundation.html)
+
+[AVFoundation slides - tips/tricks](https://speakerdeck.com/bobmccune/composing-and-editing-media-with-av-foundation)
+
+[AVFoundation slides #2](http://www.slideshare.net/bobmccune/learning-avfoundation)
+
+[Bob McCune's AVFoundation Editor - ios app example](https://github.com/tapharmonic/AVFoundationEditor)
+
+[Saving videos after recording videos](http://stackoverflow.com/questions/20902234/save-video-to-library-after-capturing-video-using-phonegap-capturevideo)
+
+
+
+## On Android
+
+[Android Documentation](http://developer.android.com/guide/appendix/media-formats.html#recommendations)
+
+[Android Media Stores](http://developer.android.com/reference/android/provider/MediaStore.html#EXTRA_VIDEO_QUALITY)
+
+[How to Port ffmpeg (the Program) to Android–Ideas and Thoughts](http://www.roman10.net/how-to-port-ffmpeg-the-program-to-androidideas-and-thoughts/)
+
+[How to Build Android Applications Based on FFmpeg by An Example](http://www.roman10.net/how-to-build-android-applications-based-on-ffmpeg-by-an-example/)
+
+
+## On Windows
+
+
+## License
+
+Android: Apache 2.0
+
+iOS: MIT
+
+Windows: Apache 2.0

+ 107 - 0
miaomiao/plugins/cordova-plugin-video-editor/package.json

xqd
@@ -0,0 +1,107 @@
+{
+  "_args": [
+    [
+      {
+        "raw": "cordova-plugin-video-editor",
+        "scope": null,
+        "escapedName": "cordova-plugin-video-editor",
+        "name": "cordova-plugin-video-editor",
+        "rawSpec": "",
+        "spec": "latest",
+        "type": "tag"
+      },
+      "D:\\my\\miao\\miaomiao\\node_modules"
+    ]
+  ],
+  "_from": "cordova-plugin-video-editor@latest",
+  "_id": "cordova-plugin-video-editor@1.1.3",
+  "_inCache": true,
+  "_location": "/cordova-plugin-video-editor",
+  "_nodeVersion": "6.11.0",
+  "_npmOperationalInternal": {
+    "host": "s3://npm-registry-packages",
+    "tmp": "tmp/cordova-plugin-video-editor-1.1.3.tgz_1504664338878_0.16480375337414443"
+  },
+  "_npmUser": {
+    "name": "rossmartin",
+    "email": "rmartin311@gmail.com"
+  },
+  "_npmVersion": "3.10.10",
+  "_phantomChildren": {},
+  "_requested": {
+    "raw": "cordova-plugin-video-editor",
+    "scope": null,
+    "escapedName": "cordova-plugin-video-editor",
+    "name": "cordova-plugin-video-editor",
+    "rawSpec": "",
+    "spec": "latest",
+    "type": "tag"
+  },
+  "_requiredBy": [
+    "#USER",
+    "/"
+  ],
+  "_resolved": "https://registry.npmjs.org/cordova-plugin-video-editor/-/cordova-plugin-video-editor-1.1.3.tgz",
+  "_shasum": "a5fdd2548c1d7e20d33650ca258913ba18fbfcd6",
+  "_shrinkwrap": null,
+  "_spec": "cordova-plugin-video-editor",
+  "_where": "D:\\my\\miao\\miaomiao\\node_modules",
+  "author": {
+    "name": "Josh Bavari"
+  },
+  "bugs": {
+    "url": "https://github.com/jbavari/cordova-plugin-video-editor/issues"
+  },
+  "contributors": [
+    {
+      "name": "Josh Bavari",
+      "email": "jbavari@gmail.com",
+      "url": "https://twitter.com/jbavari"
+    },
+    {
+      "name": "Ross Martin",
+      "email": "rmartin311@gmail.com",
+      "url": "https://twitter.com/MountainDoofus"
+    }
+  ],
+  "cordova": {
+    "id": "cordova-plugin-video-editor",
+    "platforms": [
+      "android",
+      "ios"
+    ]
+  },
+  "dependencies": {},
+  "description": "Cordova Video Editor Plugin",
+  "devDependencies": {},
+  "directories": {},
+  "dist": {
+    "shasum": "a5fdd2548c1d7e20d33650ca258913ba18fbfcd6",
+    "tarball": "https://registry.npmjs.org/cordova-plugin-video-editor/-/cordova-plugin-video-editor-1.1.3.tgz"
+  },
+  "gitHead": "eb7d9224cef8dd99560719da309a076b80dba0d8",
+  "homepage": "https://github.com/jbavari/cordova-plugin-video-editor#readme",
+  "keywords": [],
+  "license": "MIT",
+  "maintainers": [
+    {
+      "name": "jbavari",
+      "email": "jbavari@gmail.com"
+    },
+    {
+      "name": "rossmartin",
+      "email": "rmartin311@gmail.com"
+    }
+  ],
+  "name": "cordova-plugin-video-editor",
+  "optionalDependencies": {},
+  "readme": "ERROR: No README data found!",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/jbavari/cordova-plugin-video-editor.git"
+  },
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "version": "1.1.3"
+}

+ 72 - 0
miaomiao/plugins/cordova-plugin-video-editor/plugin.xml

xqd
@@ -0,0 +1,72 @@
+<?xml version='1.0' encoding='utf-8'?>
+<plugin id="cordova-plugin-video-editor" version="1.1.3" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
+    <name>VideoEditor</name>
+    <description>A plugin to assist in video editing tasks</description>
+    <keywords>cordova,video,editing,transcoding,encoding</keywords>
+    <repo>https://github.com/jbavari/cordova-plugin-video-editor.git</repo>
+    <license>MIT for iOS, GPL for Android, Apache 2.0 for Windows</license>
+
+
+    <js-module name="VideoEditor" src="www/VideoEditor.js">
+        <clobbers target="VideoEditor" />
+    </js-module>
+
+    <js-module name="VideoEditorOptions" src="www/VideoEditorOptions.js">
+        <clobbers target="VideoEditorOptions" />
+    </js-module>
+
+    <engines>
+        <engine name="cordova" version=">=3.0.0" />
+    </engines>
+
+    <!-- android -->
+    <platform name="android">
+        <config-file target="config.xml" parent="/*">
+            <feature name="VideoEditor">
+                <param name="android-package" value="org.apache.cordova.videoeditor.VideoEditor" />
+            </feature>
+        </config-file>
+
+        <!-- add plugin class -->
+        <source-file src="src/android/VideoEditor.java" target-dir="src/org/apache/cordova/videoeditor" />
+        <source-file src="src/android/CustomAndroidFormatStrategy.java" target-dir="src/org/apache/cordova/videoeditor" />
+
+        <framework src="src/android/build.gradle" custom="true" type="gradleReference" />
+
+    </platform>
+
+    <!-- ios -->
+    <platform name="ios">
+        <config-file target="config.xml" parent="/*">
+            <feature name="VideoEditor">
+                <param name="ios-package" value="VideoEditor"/>
+            </feature>
+        </config-file>
+
+        <header-file src="src/ios/VideoEditor.h" />
+        <source-file src="src/ios/VideoEditor.m" />
+
+        <header-file src="src/ios/SDAVAssetExportSession.h" />
+        <source-file src="src/ios/SDAVAssetExportSession.m" />
+
+        <framework src="AssetsLibrary.framework" />
+        <framework src="AVFoundation.framework" />
+        <framework src="MediaPlayer.framework" />
+        <framework src="CoreVideo.framework" />
+    </platform>
+
+    <!-- Windows 8 -->
+    <platform name="windows8">
+        <js-module src="src/windows8/VideoEditorProxy.js" name="VideoEditorProxy">
+            <merges target="" />
+        </js-module>
+    </platform>
+
+    <!-- Windows -->
+    <platform name="windows">
+        <js-module src="src/windows/VideoEditorProxy.js" name="VideoEditorProxy">
+            <merges target="" />
+        </js-module>
+    </platform>
+
+</plugin>

+ 92 - 0
miaomiao/plugins/cordova-plugin-video-editor/src/android/CustomAndroidFormatStrategy.java

xqd
@@ -0,0 +1,92 @@
+package org.apache.cordova.videoeditor;
+
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.util.Log;
+import net.ypresto.androidtranscoder.format.MediaFormatStrategy;
+import net.ypresto.androidtranscoder.format.OutputFormatUnavailableException;
+
+/**
+ * Created by ehmm on 02.05.2016.
+ *
+ *
+ */
+public class CustomAndroidFormatStrategy implements MediaFormatStrategy {
+
+    private static final String TAG = "CustomFormatStrategy";
+    private static final int DEFAULT_BITRATE = 8000000;
+    private static final int DEFAULT_FRAMERATE = 30;
+    private static final int DEFAULT_WIDTH = 0;
+    private static final int DEFAULT_HEIGHT = 0;
+    private final int mBitRate;
+    private final int mFrameRate;
+    private final int width;
+    private final int height;
+
+    public CustomAndroidFormatStrategy() {
+        this.mBitRate = DEFAULT_BITRATE;
+        this.mFrameRate = DEFAULT_FRAMERATE;
+        this.width = DEFAULT_WIDTH;
+        this.height = DEFAULT_HEIGHT;
+    }
+
+    public CustomAndroidFormatStrategy(final int bitRate, final int frameRate, final int width, final int height) {
+        this.mBitRate = bitRate;
+        this.mFrameRate = frameRate;
+        this.width = width;
+        this.height = height;
+    }
+
+    public MediaFormat createVideoOutputFormat(MediaFormat inputFormat) {
+        int inWidth = inputFormat.getInteger(MediaFormat.KEY_WIDTH);
+        int inHeight = inputFormat.getInteger(MediaFormat.KEY_HEIGHT);
+        int inLonger, inShorter, outWidth, outHeight, outLonger;
+        double aspectRatio;
+
+        if (this.width >= this.height) {
+            outLonger = this.width;
+        } else {
+            outLonger = this.height;
+        }
+
+        if (inWidth >= inHeight) {
+            inLonger = inWidth;
+            inShorter = inHeight;
+
+        } else {
+            inLonger = inHeight;
+            inShorter = inWidth;
+
+        }
+
+        if (inLonger > outLonger && outLonger > 0) {
+            if (inWidth >= inHeight) {
+                aspectRatio = (double) inLonger / (double) inShorter;
+                outWidth = outLonger;
+                outHeight = Double.valueOf(outWidth / aspectRatio).intValue();
+
+            } else {
+                aspectRatio = (double) inLonger / (double) inShorter;
+                outHeight = outLonger;
+                outWidth = Double.valueOf(outHeight / aspectRatio).intValue();
+            }
+        } else {
+            outWidth = inWidth;
+            outHeight = inHeight;
+        }
+
+        MediaFormat format = MediaFormat.createVideoFormat("video/avc", outWidth, outHeight);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, mBitRate);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
+        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 3);
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+
+        return format;
+
+    }
+
+    public MediaFormat createAudioOutputFormat(MediaFormat inputFormat) {
+        return null;
+    }
+
+}

+ 633 - 0
miaomiao/plugins/cordova-plugin-video-editor/src/android/VideoEditor.java

xqd
@@ -0,0 +1,633 @@
+package org.apache.cordova.videoeditor;
+
+import java.io.*;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import android.graphics.Bitmap;
+import org.apache.cordova.CordovaPlugin;
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.PluginResult;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.database.Cursor;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+import android.util.Log;
+
+import net.ypresto.androidtranscoder.MediaTranscoder;
+
+/**
+ * VideoEditor plugin for Android
+ * Created by Ross Martin 2-2-15
+ */
+public class VideoEditor extends CordovaPlugin {
+
+    private static final String TAG = "VideoEditor";
+
+    private CallbackContext callback;
+
+    @Override
+    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
+        Log.d(TAG, "execute method starting");
+
+        this.callback = callbackContext;
+
+        if (action.equals("transcodeVideo")) {
+            try {
+                this.transcodeVideo(args);
+            } catch (IOException e) {
+                callback.error(e.toString());
+            }
+            return true;
+        } else if (action.equals("createThumbnail")) {
+            try {
+                this.createThumbnail(args);
+            } catch (IOException e) {
+                callback.error(e.toString());
+            }
+            return true;
+        } else if (action.equals("getVideoInfo")) {
+            try {
+                this.getVideoInfo(args);
+            } catch (IOException e) {
+                callback.error(e.toString());
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * transcodeVideo
+     *
+     * Transcodes a video
+     *
+     * ARGUMENTS
+     * =========
+     *
+     * fileUri              - path to input video
+     * outputFileName       - output file name
+     * saveToLibrary        - save to gallery
+     * deleteInputFile      - optionally remove input file
+     * width                - width for the output video
+     * height               - height for the output video
+     * fps                  - fps the video
+     * videoBitrate         - video bitrate for the output video in bits
+     * duration             - max video duration (in seconds?)
+     *
+     * RESPONSE
+     * ========
+     *
+     * outputFilePath - path to output file
+     *
+     * @param JSONArray args
+     * @return void
+     */
+    private void transcodeVideo(JSONArray args) throws JSONException, IOException {
+        Log.d(TAG, "transcodeVideo firing");
+
+        JSONObject options = args.optJSONObject(0);
+        Log.d(TAG, "options: " + options.toString());
+
+        final File inFile = this.resolveLocalFileSystemURI(options.getString("fileUri"));
+        if (!inFile.exists()) {
+            Log.d(TAG, "input file does not exist");
+            callback.error("input video does not exist.");
+            return;
+        }
+
+        final String videoSrcPath = inFile.getAbsolutePath();
+        final String outputFileName = options.optString(
+                "outputFileName",
+                new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.ENGLISH).format(new Date())
+        );
+
+        final boolean deleteInputFile = options.optBoolean("deleteInputFile", false);
+        final int width = options.optInt("width", 0);
+        final int height = options.optInt("height", 0);
+        final int fps = options.optInt("fps", 24);
+        final int videoBitrate = options.optInt("videoBitrate", 1000000); // default to 1 megabit
+        final long videoDuration = options.optLong("duration", 0) * 1000 * 1000;
+
+        Log.d(TAG, "videoSrcPath: " + videoSrcPath);
+
+        final String outputExtension = ".mp4";
+
+        final Context appContext = cordova.getActivity().getApplicationContext();
+        final PackageManager pm = appContext.getPackageManager();
+
+        ApplicationInfo ai;
+        try {
+            ai = pm.getApplicationInfo(cordova.getActivity().getPackageName(), 0);
+        } catch (final NameNotFoundException e) {
+            ai = null;
+        }
+        final String appName = (String) (ai != null ? pm.getApplicationLabel(ai) : "Unknown");
+
+        final boolean saveToLibrary = options.optBoolean("saveToLibrary", true);
+        File mediaStorageDir;
+
+        if (saveToLibrary) {
+            mediaStorageDir = new File(
+                    Environment.getExternalStorageDirectory() + "/Movies",
+                    appName
+            );
+        } else {
+            mediaStorageDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + cordova.getActivity().getPackageName() + "/files/files/videos");
+        }
+
+        if (!mediaStorageDir.exists()) {
+            if (!mediaStorageDir.mkdirs()) {
+                callback.error("Can't access or make Movies directory");
+                return;
+            }
+        }
+
+        final String outputFilePath = new File(
+                mediaStorageDir.getPath(),
+                outputFileName + outputExtension
+        ).getAbsolutePath();
+
+        Log.d(TAG, "outputFilePath: " + outputFilePath);
+
+        cordova.getThreadPool().execute(new Runnable() {
+            public void run() {
+
+                try {
+
+                    FileInputStream fin = new FileInputStream(inFile);
+
+                    MediaTranscoder.Listener listener = new MediaTranscoder.Listener() {
+                        @Override
+                        public void onTranscodeProgress(double progress) {
+                            Log.d(TAG, "transcode running " + progress);
+
+                            JSONObject jsonObj = new JSONObject();
+                            try {
+                                jsonObj.put("progress", progress);
+                            } catch (JSONException e) {
+                                e.printStackTrace();
+                            }
+
+                            PluginResult progressResult = new PluginResult(PluginResult.Status.OK, jsonObj);
+                            progressResult.setKeepCallback(true);
+                            callback.sendPluginResult(progressResult);
+                        }
+
+                        @Override
+                        public void onTranscodeCompleted() {
+
+                            File outFile = new File(outputFilePath);
+                            if (!outFile.exists()) {
+                                Log.d(TAG, "outputFile doesn't exist!");
+                                callback.error("an error ocurred during transcoding");
+                                return;
+                            }
+
+                            // make the gallery display the new file if saving to library
+                            if (saveToLibrary) {
+                                Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+                                scanIntent.setData(Uri.fromFile(inFile));
+                                scanIntent.setData(Uri.fromFile(outFile));
+                                appContext.sendBroadcast(scanIntent);
+                            }
+
+                            if (deleteInputFile) {
+                                inFile.delete();
+                            }
+
+                            callback.success(outputFilePath);
+                        }
+
+                        @Override
+                        public void onTranscodeCanceled() {
+                            callback.error("transcode canceled");
+                            Log.d(TAG, "transcode canceled");
+                        }
+
+                        @Override
+                        public void onTranscodeFailed(Exception exception) {
+                            callback.error(exception.toString());
+                            Log.d(TAG, "transcode exception", exception);
+                        }
+                    };
+
+                    MediaMetadataRetriever mmr = new MediaMetadataRetriever();
+                    mmr.setDataSource(videoSrcPath);
+
+                    String orientation;
+                    String mmrOrientation = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+                    Log.d(TAG, "mmrOrientation: " + mmrOrientation); // 0, 90, 180, or 270
+
+                    float videoWidth = Float.parseFloat(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
+                    float videoHeight = Float.parseFloat(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
+
+                    MediaTranscoder.getInstance().transcodeVideo(fin.getFD(), outputFilePath,
+                            new CustomAndroidFormatStrategy(videoBitrate, fps, width, height), listener, videoDuration);
+
+                } catch (Throwable e) {
+                    Log.d(TAG, "transcode exception ", e);
+                    callback.error(e.toString());
+                }
+
+            }
+        });
+    }
+
+    /**
+     * createThumbnail
+     *
+     * Creates a thumbnail from the start of a video.
+     *
+     * ARGUMENTS
+     * =========
+     * fileUri        - input file path
+     * outputFileName - output file name
+     * atTime         - location in the video to create the thumbnail (in seconds)
+     * width          - width for the thumbnail (optional)
+     * height         - height for the thumbnail (optional)
+     * quality        - quality of the thumbnail (optional, between 1 and 100)
+     *
+     * RESPONSE
+     * ========
+     *
+     * outputFilePath - path to output file
+     *
+     * @param JSONArray args
+     * @return void
+     */
+    private void createThumbnail(JSONArray args) throws JSONException, IOException {
+        Log.d(TAG, "createThumbnail firing");
+
+
+        JSONObject options = args.optJSONObject(0);
+        Log.d(TAG, "options: " + options.toString());
+
+        String fileUri = options.getString("fileUri");
+        if (!fileUri.startsWith("file:/")) {
+            fileUri = "file:/" + fileUri;
+        }
+
+        File inFile = this.resolveLocalFileSystemURI(fileUri);
+        if (!inFile.exists()) {
+            Log.d(TAG, "input file does not exist");
+            callback.error("input video does not exist.");
+            return;
+        }
+        final String srcVideoPath = inFile.getAbsolutePath();
+        String outputFileName = options.optString(
+                "outputFileName",
+                new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.ENGLISH).format(new Date())
+        );
+
+        final int quality = options.optInt("quality", 100);
+        final int width = options.optInt("width", 0);
+        final int height = options.optInt("height", 0);
+        long atTimeOpt = options.optLong("atTime", 0);
+        final long atTime = (atTimeOpt == 0) ? 0 : atTimeOpt * 1000000;
+
+        final Context appContext = cordova.getActivity().getApplicationContext();
+        PackageManager pm = appContext.getPackageManager();
+
+        ApplicationInfo ai;
+        try {
+            ai = pm.getApplicationInfo(cordova.getActivity().getPackageName(), 0);
+        } catch (final NameNotFoundException e) {
+            ai = null;
+        }
+        final String appName = (String) (ai != null ? pm.getApplicationLabel(ai) : "Unknown");
+
+        File externalFilesDir =  new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + cordova.getActivity().getPackageName() + "/files/files/videos");
+
+        if (!externalFilesDir.exists()) {
+            if (!externalFilesDir.mkdirs()) {
+                callback.error("Can't access or make Movies directory");
+                return;
+            }
+        }
+
+        final File outputFile =  new File(
+                externalFilesDir.getPath(),
+                outputFileName + ".jpg"
+        );
+        final String outputFilePath = outputFile.getAbsolutePath();
+
+        // start task
+        cordova.getThreadPool().execute(new Runnable() {
+            public void run() {
+
+                OutputStream outStream = null;
+
+                try {
+                    MediaMetadataRetriever mmr = new MediaMetadataRetriever();
+                    mmr.setDataSource(srcVideoPath);
+
+                    Bitmap bitmap = mmr.getFrameAtTime(atTime);
+
+                    if (width > 0 || height > 0) {
+                        int videoWidth = bitmap.getWidth();
+                        int videoHeight = bitmap.getHeight();
+                        double aspectRatio = (double) videoWidth / (double) videoHeight;
+
+                        Log.d(TAG, "videoWidth: " + videoWidth);
+                        Log.d(TAG, "videoHeight: " + videoHeight);
+
+                        int scaleWidth = Double.valueOf(height * aspectRatio).intValue();
+                        int scaleHeight = Double.valueOf(scaleWidth / aspectRatio).intValue();
+
+                        Log.d(TAG, "scaleWidth: " + scaleWidth);
+                        Log.d(TAG, "scaleHeight: " + scaleHeight);
+
+                        final Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, scaleWidth, scaleHeight, false);
+                        bitmap.recycle();
+                        bitmap = resizedBitmap;
+                    }
+
+                    outStream = new FileOutputStream(outputFile);
+                    bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outStream);
+
+                    callback.success(outputFilePath);
+
+                } catch (Throwable e) {
+                    if (outStream != null) {
+                        try {
+                            outStream.close();
+                        } catch (IOException e1) {
+                            e1.printStackTrace();
+                        }
+                    }
+
+                    Log.d(TAG, "exception on thumbnail creation", e);
+                    callback.error(e.toString());
+
+                }
+
+            }
+        });
+    }
+
+    /**
+     * getVideoInfo
+     *
+     * Gets info on a video
+     *
+     * ARGUMENTS
+     * =========
+     *
+     * fileUri:      - path to input video
+     *
+     * RESPONSE
+     * ========
+     *
+     * width         - width of the video
+     * height        - height of the video
+     * orientation   - orientation of the video
+     * duration      - duration of the video (in seconds)
+     * size          - size of the video (in bytes)
+     * bitrate       - bitrate of the video (in bits per second)
+     *
+     * @param JSONArray args
+     * @return void
+     */
+    private void getVideoInfo(JSONArray args) throws JSONException, IOException {
+        Log.d(TAG, "getVideoInfo firing");
+
+        JSONObject options = args.optJSONObject(0);
+        Log.d(TAG, "options: " + options.toString());
+
+        File inFile = this.resolveLocalFileSystemURI(options.getString("fileUri"));
+        if (!inFile.exists()) {
+            Log.d(TAG, "input file does not exist");
+            callback.error("input video does not exist.");
+            return;
+        }
+
+        String videoSrcPath = inFile.getAbsolutePath();
+        Log.d(TAG, "videoSrcPath: " + videoSrcPath);
+
+        MediaMetadataRetriever mmr = new MediaMetadataRetriever();
+        mmr.setDataSource(videoSrcPath);
+        float videoWidth = Float.parseFloat(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
+        float videoHeight = Float.parseFloat(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
+
+        String orientation;
+        if (Build.VERSION.SDK_INT >= 17) {
+            String mmrOrientation = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+            Log.d(TAG, "mmrOrientation: " + mmrOrientation); // 0, 90, 180, or 270
+
+            if (videoWidth < videoHeight) {
+                if (mmrOrientation.equals("0") || mmrOrientation.equals("180")) {
+                    orientation = "portrait";
+                } else {
+                    orientation = "landscape";
+                }
+            } else {
+                if (mmrOrientation.equals("0") || mmrOrientation.equals("180")) {
+                    orientation = "landscape";
+                } else {
+                    orientation = "portrait";
+                }
+            }
+        } else {
+            orientation = (videoWidth < videoHeight) ? "portrait" : "landscape";
+        }
+
+        double duration = Double.parseDouble(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)) / 1000.0;
+        long bitrate = Long.parseLong(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE));
+
+        JSONObject response = new JSONObject();
+        response.put("width", videoWidth);
+        response.put("height", videoHeight);
+        response.put("orientation", orientation);
+        response.put("duration", duration);
+        response.put("size", inFile.length());
+        response.put("bitrate", bitrate);
+
+        callback.success(response);
+    }
+
+
+    @SuppressWarnings("deprecation")
+    private File resolveLocalFileSystemURI(String url) throws IOException, JSONException {
+        String decoded = URLDecoder.decode(url, "UTF-8");
+
+        File fp = null;
+
+        // Handle the special case where you get an Android content:// uri.
+        if (decoded.startsWith("content:")) {
+            fp = new File(getPath(this.cordova.getActivity().getApplicationContext(), Uri.parse(decoded)));
+        } else {
+            // Test to see if this is a valid URL first
+            @SuppressWarnings("unused")
+            URL testUrl = new URL(decoded);
+
+            if (decoded.startsWith("file://")) {
+                int questionMark = decoded.indexOf("?");
+                if (questionMark < 0) {
+                    fp = new File(decoded.substring(7, decoded.length()));
+                } else {
+                    fp = new File(decoded.substring(7, questionMark));
+                }
+            } else if (decoded.startsWith("file:/")) {
+                fp = new File(decoded.substring(6, decoded.length()));
+            } else {
+                fp = new File(decoded);
+            }
+        }
+
+        if (!fp.exists()) {
+            throw new FileNotFoundException( "" + url + " -> " + fp.getCanonicalPath());
+        }
+        if (!fp.canRead()) {
+            throw new IOException("can't read file: " + url + " -> " + fp.getCanonicalPath());
+        }
+        return fp;
+    }
+
+    /**
+     * Get a file path from a Uri. This will get the the path for Storage Access
+     * Framework Documents, as well as the _data field for the MediaStore and
+     * other file-based ContentProviders.
+     *
+     * @param context The context.
+     * @param uri The Uri to query.
+     * @author paulburke
+     */
+    public static String getPath(final Context context, final Uri uri) {
+
+        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+
+        // DocumentProvider
+        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
+            // ExternalStorageProvider
+            if (isExternalStorageDocument(uri)) {
+                final String docId = DocumentsContract.getDocumentId(uri);
+                final String[] split = docId.split(":");
+                final String type = split[0];
+
+                if ("primary".equalsIgnoreCase(type)) {
+                    return Environment.getExternalStorageDirectory() + "/" + split[1];
+                }
+
+                // TODO handle non-primary volumes
+            }
+            // DownloadsProvider
+            else if (isDownloadsDocument(uri)) {
+
+                final String id = DocumentsContract.getDocumentId(uri);
+                final Uri contentUri = ContentUris.withAppendedId(
+                        Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
+
+                return getDataColumn(context, contentUri, null, null);
+            }
+            // MediaProvider
+            else if (isMediaDocument(uri)) {
+                final String docId = DocumentsContract.getDocumentId(uri);
+                final String[] split = docId.split(":");
+                final String type = split[0];
+
+                Uri contentUri = null;
+                if ("image".equals(type)) {
+                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+                } else if ("video".equals(type)) {
+                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+                } else if ("audio".equals(type)) {
+                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+                }
+
+                final String selection = "_id=?";
+                final String[] selectionArgs = new String[] {
+                        split[1]
+                };
+
+                return getDataColumn(context, contentUri, selection, selectionArgs);
+            }
+        }
+        // MediaStore (and general)
+        else if ("content".equalsIgnoreCase(uri.getScheme())) {
+            return getDataColumn(context, uri, null, null);
+        }
+        // File
+        else if ("file".equalsIgnoreCase(uri.getScheme())) {
+            return uri.getPath();
+        }
+
+        return null;
+    }
+
+    /**
+     * Get the value of the data column for this Uri. This is useful for
+     * MediaStore Uris, and other file-based ContentProviders.
+     *
+     * @param context The context.
+     * @param uri The Uri to query.
+     * @param selection (Optional) Filter used in the query.
+     * @param selectionArgs (Optional) Selection arguments used in the query.
+     * @return The value of the _data column, which is typically a file path.
+     */
+    public static String getDataColumn(Context context, Uri uri, String selection,
+                                       String[] selectionArgs) {
+
+        Cursor cursor = null;
+        final String column = "_data";
+        final String[] projection = {
+                column
+        };
+
+        try {
+            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
+                    null);
+            if (cursor != null && cursor.moveToFirst()) {
+                final int column_index = cursor.getColumnIndexOrThrow(column);
+                return cursor.getString(column_index);
+            }
+        } finally {
+            if (cursor != null)
+                cursor.close();
+        }
+        return null;
+    }
+
+
+    /**
+     * @param uri The Uri to check.
+     * @return Whether the Uri authority is ExternalStorageProvider.
+     */
+    public static boolean isExternalStorageDocument(Uri uri) {
+        return "com.android.externalstorage.documents".equals(uri.getAuthority());
+    }
+
+    /**
+     * @param uri The Uri to check.
+     * @return Whether the Uri authority is DownloadsProvider.
+     */
+    public static boolean isDownloadsDocument(Uri uri) {
+        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+    }
+
+    /**
+     * @param uri The Uri to check.
+     * @return Whether the Uri authority is MediaProvider.
+     */
+    public static boolean isMediaDocument(Uri uri) {
+        return "com.android.providers.media.documents".equals(uri.getAuthority());
+    }
+
+}

+ 16 - 0
miaomiao/plugins/cordova-plugin-video-editor/src/android/build.gradle

xqd
@@ -0,0 +1,16 @@
+
+repositories{
+    jcenter()
+    maven { url "https://jitpack.io" }
+}
+
+dependencies {
+    compile 'com.github.ergovia-mobile:android-transcoder:v0.1.10R_ergovia'
+}
+
+android {
+    packagingOptions {
+        exclude 'META-INF/NOTICE'
+        exclude 'META-INF/LICENSE'
+    }
+}

+ 194 - 0
miaomiao/plugins/cordova-plugin-video-editor/src/ios/SDAVAssetExportSession.h

xqd
@@ -0,0 +1,194 @@
+//
+//  SDAVAssetExportSession.h
+//
+// This file is part of the SDAVAssetExportSession package.
+//
+// Created by Olivier Poitrey <rs@dailymotion.com> on 13/03/13.
+// Copyright 2013 Olivier Poitrey. All rights servered.
+//
+// For the full copyright and license information, please view the LICENSE
+// file that was distributed with this source code.
+//
+
+#import <Foundation/Foundation.h>
+#import <AVFoundation/AVFoundation.h>
+
+@protocol SDAVAssetExportSessionDelegate;
+
+
+/**
+ * An `SDAVAssetExportSession` object transcodes the contents of an AVAsset source object to create an output
+ * of the form described by specified video and audio settings. It implements most of the API of Apple provided
+ * `AVAssetExportSession` but with the capability to provide you own video and audio settings instead of the
+ * limited set of Apple provided presets.
+ *
+ * After you have initialized an export session with the asset that contains the source media, video and audio
+ * settings, and the output file type (outputFileType), you can start the export running by invoking 
+ * `exportAsynchronouslyWithCompletionHandler:`. Because the export is performed asynchronously, this method
+ * returns immediately — you can observe progress to check on the progress.
+ *
+ * The completion handler you supply to `exportAsynchronouslyWithCompletionHandler:` is called whether the export
+ * fails, completes, or is cancelled. Upon completion, the status property indicates whether the export has
+ * completed successfully. If it has failed, the value of the error property supplies additional information
+ * about the reason for the failure.
+ */
+
+@interface SDAVAssetExportSession : NSObject
+
+@property (nonatomic, weak) id<SDAVAssetExportSessionDelegate> delegate;
+
+/**
+ * The asset with which the export session was initialized.
+ */
+@property (nonatomic, strong, readonly) AVAsset *asset;
+
+/**
+ * Indicates whether video composition is enabled for export, and supplies the instructions for video composition.
+ *
+ * You can observe this property using key-value observing.
+ */
+@property (nonatomic, copy) AVVideoComposition *videoComposition;
+
+/**
+ * Indicates whether non-default audio mixing is enabled for export, and supplies the parameters for audio mixing.
+ */
+@property (nonatomic, copy) AVAudioMix *audioMix;
+
+/**
+ * The type of file to be written by the session.
+ *
+ * The value is a UTI string corresponding to the file type to use when writing the asset.
+ * For a list of constants specifying UTIs for standard file types, see `AV Foundation Constants Reference`.
+ *
+ * You can observe this property using key-value observing.
+ */
+@property (nonatomic, copy) NSString *outputFileType;
+
+/**
+ * The URL of the export session’s output.
+ *
+ * You can observe this property using key-value observing.
+ */
+@property (nonatomic, copy) NSURL *outputURL;
+
+/**
+ * The settings used for input video track.
+ *
+ * The dictionary’s keys are from <CoreVideo/CVPixelBuffer.h>.
+ */
+@property (nonatomic, copy) NSDictionary *videoInputSettings;
+
+/**
+ * The settings used for encoding the video track.
+ *
+ * A value of nil specifies that appended output should not be re-encoded.
+ * The dictionary’s keys are from <AVFoundation/AVVideoSettings.h>.
+ */
+@property (nonatomic, copy) NSDictionary *videoSettings;
+
+/**
+ * The settings used for encoding the audio track.
+ *
+ * A value of nil specifies that appended output should not be re-encoded.
+ * The dictionary’s keys are from <CoreVideo/CVPixelBuffer.h>.
+ */
+@property (nonatomic, copy) NSDictionary *audioSettings;
+
+/**
+ * The time range to be exported from the source.
+ *
+ * The default time range of an export session is `kCMTimeZero` to `kCMTimePositiveInfinity`,
+ * meaning that (modulo a possible limit on file length) the full duration of the asset will be exported.
+ *
+ * You can observe this property using key-value observing.
+ *
+ */
+@property (nonatomic, assign) CMTimeRange timeRange;
+
+/**
+ * Indicates whether the movie should be optimized for network use.
+ *
+ * You can observe this property using key-value observing.
+ */
+@property (nonatomic, assign) BOOL shouldOptimizeForNetworkUse;
+
+/**
+ * The metadata to be written to the output file by the export session.
+ */
+@property (nonatomic, copy) NSArray *metadata;
+
+/**
+ * Describes the error that occurred if the export status is `AVAssetExportSessionStatusFailed`
+ * or `AVAssetExportSessionStatusCancelled`.
+ *
+ * If there is no error to report, the value of this property is nil.
+ */
+@property (nonatomic, strong, readonly) NSError *error;
+
+/**
+ * The progress of the export on a scale from 0 to 1.
+ *
+ *
+ * A value of 0 means the export has not yet begun, 1 means the export is complete.
+ *
+ * Unlike Apple provided `AVAssetExportSession`, this property can be observed using key-value observing.
+ */
+@property (nonatomic, assign, readonly) float progress;
+
+/**
+ * The status of the export session.
+ *
+ * For possible values, see “AVAssetExportSessionStatus.”
+ *
+ * You can observe this property using key-value observing. (TODO)
+ */
+@property (nonatomic, assign, readonly) AVAssetExportSessionStatus status;
+
+/**
+ * Returns an asset export session configured with a specified asset.
+ *
+ * @param asset The asset you want to export
+ * @return An asset export session initialized to export `asset`.
+ */
++ (id)exportSessionWithAsset:(AVAsset *)asset;
+
+/**
+ * Initializes an asset export session with a specified asset.
+ *
+ * @param asset The asset you want to export
+ * @return An asset export session initialized to export `asset`.
+ */
+- (id)initWithAsset:(AVAsset *)asset;
+
+/**
+ * Starts the asynchronous execution of an export session.
+ *
+ * This method starts an asynchronous export operation and returns immediately. status signals the terminal
+ * state of the export session, and if a failure occurs, error describes the problem.
+ *
+ * If internal preparation for export fails, handler is invoked synchronously. The handler may also be called
+ * asynchronously, after the method returns, in the following cases:
+ *
+ * 1. If a failure occurs during the export, including failures of loading, re-encoding, or writing media data to the output.
+ * 2. If cancelExport is invoked.
+ * 3. After the export session succeeds, having completely written its output to the outputURL.
+ *
+ * @param handler A block that is invoked when writing is complete or in the event of writing failure.
+ */
+- (void)exportAsynchronouslyWithCompletionHandler:(void (^)())handler;
+
+/**
+ * Cancels the execution of an export session.
+ *
+ * You can invoke this method when the export is running.
+ */
+- (void)cancelExport;
+
+@end
+
+
+@protocol SDAVAssetExportSessionDelegate <NSObject>
+
+- (void)exportSession:(SDAVAssetExportSession *)exportSession renderFrame:(CVPixelBufferRef)pixelBuffer withPresentationTime:(CMTime)presentationTime toBuffer:(CVPixelBufferRef)renderBuffer;
+
+@end

+ 445 - 0
miaomiao/plugins/cordova-plugin-video-editor/src/ios/SDAVAssetExportSession.m

xqd
@@ -0,0 +1,445 @@
+//
+//  SDAVAssetExportSession.m
+//
+// This file is part of the SDAVAssetExportSession package.
+//
+// Created by Olivier Poitrey <rs@dailymotion.com> on 13/03/13.
+// Copyright 2013 Olivier Poitrey. All rights servered.
+//
+// For the full copyright and license information, please view the LICENSE
+// file that was distributed with this source code.
+//
+
+
+#import "SDAVAssetExportSession.h"
+
+@interface SDAVAssetExportSession ()
+
+@property (nonatomic, assign, readwrite) float progress;
+
+@property (nonatomic, strong) AVAssetReader *reader;
+@property (nonatomic, strong) AVAssetReaderVideoCompositionOutput *videoOutput;
+@property (nonatomic, strong) AVAssetReaderAudioMixOutput *audioOutput;
+@property (nonatomic, strong) AVAssetWriter *writer;
+@property (nonatomic, strong) AVAssetWriterInput *videoInput;
+@property (nonatomic, strong) AVAssetWriterInputPixelBufferAdaptor *videoPixelBufferAdaptor;
+@property (nonatomic, strong) AVAssetWriterInput *audioInput;
+@property (nonatomic, strong) dispatch_queue_t inputQueue;
+@property (nonatomic, strong) void (^completionHandler)();
+
+@end
+
+@implementation SDAVAssetExportSession
+{
+    NSError *_error;
+    NSTimeInterval duration;
+    CMTime lastSamplePresentationTime;
+}
+
++ (id)exportSessionWithAsset:(AVAsset *)asset
+{
+    return [SDAVAssetExportSession.alloc initWithAsset:asset];
+}
+
+- (id)initWithAsset:(AVAsset *)asset
+{
+    if ((self = [super init]))
+    {
+        _asset = asset;
+        _timeRange = CMTimeRangeMake(kCMTimeZero, kCMTimePositiveInfinity);
+    }
+
+    return self;
+}
+
+- (void)exportAsynchronouslyWithCompletionHandler:(void (^)())handler
+{
+    NSParameterAssert(handler != nil);
+    [self cancelExport];
+    self.completionHandler = handler;
+
+    if (!self.outputURL)
+    {
+        _error = [NSError errorWithDomain:AVFoundationErrorDomain code:AVErrorExportFailed userInfo:@
+                  {
+                  NSLocalizedDescriptionKey: @"Output URL not set"
+                  }];
+        handler();
+        return;
+    }
+
+    NSError *readerError;
+    self.reader = [AVAssetReader.alloc initWithAsset:self.asset error:&readerError];
+    if (readerError)
+    {
+        _error = readerError;
+        handler();
+        return;
+    }
+
+    NSError *writerError;
+    self.writer = [AVAssetWriter assetWriterWithURL:self.outputURL fileType:self.outputFileType error:&writerError];
+    if (writerError)
+    {
+        _error = writerError;
+        handler();
+        return;
+    }
+
+    self.reader.timeRange = self.timeRange;
+    self.writer.shouldOptimizeForNetworkUse = self.shouldOptimizeForNetworkUse;
+    self.writer.metadata = self.metadata;
+
+    NSArray *videoTracks = [self.asset tracksWithMediaType:AVMediaTypeVideo];
+
+
+    if (CMTIME_IS_VALID(self.timeRange.duration) && !CMTIME_IS_POSITIVE_INFINITY(self.timeRange.duration))
+    {
+        duration = CMTimeGetSeconds(self.timeRange.duration);
+    }
+    else
+    {
+        duration = CMTimeGetSeconds(self.asset.duration);
+    }
+    //
+    // Video output
+    //
+    if (videoTracks.count > 0) {
+        self.videoOutput = [AVAssetReaderVideoCompositionOutput assetReaderVideoCompositionOutputWithVideoTracks:videoTracks videoSettings:self.videoInputSettings];
+        self.videoOutput.alwaysCopiesSampleData = NO;
+        if (self.videoComposition)
+        {
+            self.videoOutput.videoComposition = self.videoComposition;
+        }
+        else
+        {
+            self.videoOutput.videoComposition = [self buildDefaultVideoComposition];
+        }
+        if ([self.reader canAddOutput:self.videoOutput])
+        {
+            [self.reader addOutput:self.videoOutput];
+        }
+
+        //
+        // Video input
+        //
+        self.videoInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo outputSettings:self.videoSettings];
+        self.videoInput.expectsMediaDataInRealTime = NO;
+        if ([self.writer canAddInput:self.videoInput])
+        {
+            [self.writer addInput:self.videoInput];
+        }
+        NSDictionary *pixelBufferAttributes = @
+        {
+            (id)kCVPixelBufferPixelFormatTypeKey: @(kCVPixelFormatType_32BGRA),
+            (id)kCVPixelBufferWidthKey: @(self.videoOutput.videoComposition.renderSize.width),
+            (id)kCVPixelBufferHeightKey: @(self.videoOutput.videoComposition.renderSize.height),
+            @"IOSurfaceOpenGLESTextureCompatibility": @YES,
+            @"IOSurfaceOpenGLESFBOCompatibility": @YES,
+        };
+        self.videoPixelBufferAdaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:self.videoInput sourcePixelBufferAttributes:pixelBufferAttributes];
+    }
+
+    //
+    //Audio output
+    //
+    NSArray *audioTracks = [self.asset tracksWithMediaType:AVMediaTypeAudio];
+    if (audioTracks.count > 0) {
+        self.audioOutput = [AVAssetReaderAudioMixOutput assetReaderAudioMixOutputWithAudioTracks:audioTracks audioSettings:nil];
+        self.audioOutput.alwaysCopiesSampleData = NO;
+        self.audioOutput.audioMix = self.audioMix;
+        if ([self.reader canAddOutput:self.audioOutput])
+        {
+            [self.reader addOutput:self.audioOutput];
+        }
+    } else {
+        // Just in case this gets reused
+        self.audioOutput = nil;
+    }
+
+    //
+    // Audio input
+    //
+    if (self.audioOutput) {
+        self.audioInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio outputSettings:self.audioSettings];
+        self.audioInput.expectsMediaDataInRealTime = NO;
+        if ([self.writer canAddInput:self.audioInput])
+        {
+            [self.writer addInput:self.audioInput];
+        }
+    }
+
+    [self.writer startWriting];
+    [self.reader startReading];
+    [self.writer startSessionAtSourceTime:self.timeRange.start];
+
+    __block BOOL videoCompleted = NO;
+    __block BOOL audioCompleted = NO;
+    __weak SDAVAssetExportSession *wself = self;
+    self.inputQueue = dispatch_queue_create("VideoEncoderInputQueue", DISPATCH_QUEUE_SERIAL);
+    if (videoTracks.count > 0) {
+        [self.videoInput requestMediaDataWhenReadyOnQueue:self.inputQueue usingBlock:^
+         {
+             if (![wself encodeReadySamplesFromOutput:wself.videoOutput toInput:wself.videoInput])
+             {
+                 @synchronized(wself)
+                 {
+                     videoCompleted = YES;
+                     if (audioCompleted)
+                     {
+                         [wself finish];
+                     }
+                 }
+             }
+         }];
+    }
+    else {
+        videoCompleted = YES;
+    }
+
+    if (!self.audioOutput) {
+        audioCompleted = YES;
+    } else {
+        [self.audioInput requestMediaDataWhenReadyOnQueue:self.inputQueue usingBlock:^
+         {
+             if (![wself encodeReadySamplesFromOutput:wself.audioOutput toInput:wself.audioInput])
+             {
+                 @synchronized(wself)
+                 {
+                     audioCompleted = YES;
+                     if (videoCompleted)
+                     {
+                         [wself finish];
+                     }
+                 }
+             }
+         }];
+    }
+}
+
+- (BOOL)encodeReadySamplesFromOutput:(AVAssetReaderOutput *)output toInput:(AVAssetWriterInput *)input
+{
+    while (input.isReadyForMoreMediaData)
+    {
+        CMSampleBufferRef sampleBuffer = [output copyNextSampleBuffer];
+        if (sampleBuffer)
+        {
+            BOOL handled = NO;
+            BOOL error = NO;
+
+            if (self.reader.status != AVAssetReaderStatusReading || self.writer.status != AVAssetWriterStatusWriting)
+            {
+                handled = YES;
+                error = YES;
+            }
+
+            if (!handled && self.videoOutput == output)
+            {
+                // update the video progress
+                lastSamplePresentationTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);
+                lastSamplePresentationTime = CMTimeSubtract(lastSamplePresentationTime, self.timeRange.start);
+                self.progress = duration == 0 ? 1 : CMTimeGetSeconds(lastSamplePresentationTime) / duration;
+
+                if ([self.delegate respondsToSelector:@selector(exportSession:renderFrame:withPresentationTime:toBuffer:)])
+                {
+                    CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
+                    CVPixelBufferRef renderBuffer = NULL;
+                    CVPixelBufferPoolCreatePixelBuffer(NULL, self.videoPixelBufferAdaptor.pixelBufferPool, &renderBuffer);
+                    [self.delegate exportSession:self renderFrame:pixelBuffer withPresentationTime:lastSamplePresentationTime toBuffer:renderBuffer];
+                    if (![self.videoPixelBufferAdaptor appendPixelBuffer:renderBuffer withPresentationTime:lastSamplePresentationTime])
+                    {
+                        error = YES;
+                    }
+                    CVPixelBufferRelease(renderBuffer);
+                    handled = YES;
+                }
+            }
+            if (!handled && ![input appendSampleBuffer:sampleBuffer])
+            {
+                error = YES;
+            }
+            CFRelease(sampleBuffer);
+
+            if (error)
+            {
+                return NO;
+            }
+        }
+        else
+        {
+            [input markAsFinished];
+            return NO;
+        }
+    }
+
+    return YES;
+}
+
+- (AVMutableVideoComposition *)buildDefaultVideoComposition
+{
+    AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
+    AVAssetTrack *videoTrack = [[self.asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
+
+    // get the frame rate from videoSettings, if not set then try to get it from the video track,
+    // if not set (mainly when asset is AVComposition) then use the default frame rate of 30
+    float trackFrameRate = 0;
+    if (self.videoSettings)
+    {
+        NSDictionary *videoCompressionProperties = [self.videoSettings objectForKey:AVVideoCompressionPropertiesKey];
+        if (videoCompressionProperties)
+        {
+            NSNumber *maxKeyFrameInterval = [videoCompressionProperties objectForKey:AVVideoMaxKeyFrameIntervalKey];
+            if (maxKeyFrameInterval)
+            {
+                trackFrameRate = maxKeyFrameInterval.floatValue;
+            }
+        }
+    }
+    else
+    {
+        trackFrameRate = [videoTrack nominalFrameRate];
+    }
+
+    if (trackFrameRate == 0)
+    {
+        trackFrameRate = 30;
+    }
+
+    videoComposition.frameDuration = CMTimeMake(1, trackFrameRate);
+    CGSize targetSize = CGSizeMake([self.videoSettings[AVVideoWidthKey] floatValue], [self.videoSettings[AVVideoHeightKey] floatValue]);
+    CGSize naturalSize = [videoTrack naturalSize];
+    CGAffineTransform transform = videoTrack.preferredTransform;
+    CGFloat videoAngleInDegree  = atan2(transform.b, transform.a) * 180 / M_PI;
+    if (videoAngleInDegree == 90 || videoAngleInDegree == -90) {
+        CGFloat width = naturalSize.width;
+        naturalSize.width = naturalSize.height;
+        naturalSize.height = width;
+    }
+    videoComposition.renderSize = naturalSize;
+    // center inside
+    {
+        float ratio;
+        float xratio = targetSize.width / naturalSize.width;
+        float yratio = targetSize.height / naturalSize.height;
+        ratio = MIN(xratio, yratio);
+
+        float postWidth = naturalSize.width * ratio;
+        float postHeight = naturalSize.height * ratio;
+        float transx = (targetSize.width - postWidth) / 2;
+        float transy = (targetSize.height - postHeight) / 2;
+
+        CGAffineTransform matrix = CGAffineTransformMakeTranslation(transx / xratio, transy / yratio);
+        matrix = CGAffineTransformScale(matrix, ratio / xratio, ratio / yratio);
+        transform = CGAffineTransformConcat(transform, matrix);
+    }
+
+    // Make a "pass through video track" video composition.
+    AVMutableVideoCompositionInstruction *passThroughInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
+    passThroughInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, self.asset.duration);
+
+    AVMutableVideoCompositionLayerInstruction *passThroughLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
+
+    [passThroughLayer setTransform:transform atTime:kCMTimeZero];
+
+    passThroughInstruction.layerInstructions = @[passThroughLayer];
+    videoComposition.instructions = @[passThroughInstruction];
+
+    return videoComposition;
+}
+
+- (void)finish
+{
+    // Synchronized block to ensure we never cancel the writer before calling finishWritingWithCompletionHandler
+    if (self.reader.status == AVAssetReaderStatusCancelled || self.writer.status == AVAssetWriterStatusCancelled)
+    {
+        return;
+    }
+
+    if (self.writer.status == AVAssetWriterStatusFailed)
+    {
+        [self complete];
+    }
+    else
+    {
+        [self.writer endSessionAtSourceTime:lastSamplePresentationTime];
+        [self.writer finishWritingWithCompletionHandler:^
+         {
+             [self complete];
+         }];
+    }
+}
+
+- (void)complete
+{
+    if (self.writer.status == AVAssetWriterStatusFailed || self.writer.status == AVAssetWriterStatusCancelled)
+    {
+        [NSFileManager.defaultManager removeItemAtURL:self.outputURL error:nil];
+    }
+
+    if (self.completionHandler)
+    {
+        self.completionHandler();
+        self.completionHandler = nil;
+    }
+}
+
+- (NSError *)error
+{
+    if (_error)
+    {
+        return _error;
+    }
+    else
+    {
+        return self.writer.error ? : self.reader.error;
+    }
+}
+
+- (AVAssetExportSessionStatus)status
+{
+    switch (self.writer.status)
+    {
+        default:
+        case AVAssetWriterStatusUnknown:
+            return AVAssetExportSessionStatusUnknown;
+        case AVAssetWriterStatusWriting:
+            return AVAssetExportSessionStatusExporting;
+        case AVAssetWriterStatusFailed:
+            return AVAssetExportSessionStatusFailed;
+        case AVAssetWriterStatusCompleted:
+            return AVAssetExportSessionStatusCompleted;
+        case AVAssetWriterStatusCancelled:
+            return AVAssetExportSessionStatusCancelled;
+    }
+}
+
+- (void)cancelExport
+{
+    if (self.inputQueue)
+    {
+        dispatch_async(self.inputQueue, ^
+                       {
+                           [self.writer cancelWriting];
+                           [self.reader cancelReading];
+                           [self complete];
+                           [self reset];
+                       });
+    }
+}
+
+- (void)reset
+{
+    _error = nil;
+    self.progress = 0;
+    self.reader = nil;
+    self.videoOutput = nil;
+    self.audioOutput = nil;
+    self.writer = nil;
+    self.videoInput = nil;
+    self.videoPixelBufferAdaptor = nil;
+    self.audioInput = nil;
+    self.inputQueue = nil;
+    self.completionHandler = nil;
+}
+
+@end

+ 31 - 0
miaomiao/plugins/cordova-plugin-video-editor/src/ios/VideoEditor.h

xqd
@@ -0,0 +1,31 @@
+//
+//  VideoEditor.h
+//
+//  Created by Josh Bavari on 01-14-2014
+//  Modified by Ross Martin on 01-29-2015
+//
+
+#import <Foundation/Foundation.h>
+#import <AVFoundation/AVFoundation.h>
+#import <AssetsLibrary/ALAssetsLibrary.h>
+#import <MediaPlayer/MediaPlayer.h>
+
+#import <Cordova/CDV.h>
+
+enum CDVOutputFileType {
+    M4V = 0,
+    MPEG4 = 1,
+    M4A = 2,
+    QUICK_TIME = 3
+};
+typedef NSUInteger CDVOutputFileType;
+
+@interface VideoEditor : CDVPlugin {
+}
+
+- (void)transcodeVideo:(CDVInvokedUrlCommand*)command;
+- (void) createThumbnail:(CDVInvokedUrlCommand*)command;
+- (void) getVideoInfo:(CDVInvokedUrlCommand*)command;
+- (void) trim:(CDVInvokedUrlCommand*)command;
+
+@end

+ 577 - 0
miaomiao/plugins/cordova-plugin-video-editor/src/ios/VideoEditor.m

xqd
@@ -0,0 +1,577 @@
+//
+//  VideoEditor.m
+//
+//  Created by Josh Bavari on 01-14-2014
+//  Modified by Ross Martin on 01-29-2015
+//
+
+#import <Cordova/CDV.h>
+#import "VideoEditor.h"
+#import "SDAVAssetExportSession.h"
+
+@interface VideoEditor ()
+
+@end
+
+@implementation VideoEditor
+
+/**
+ * transcodeVideo
+ *
+ * Transcodes a video
+ *
+ * ARGUMENTS
+ * =========
+ *
+ * fileUri              - path to input video
+ * outputFileName       - output file name
+ * quality              - transcode quality
+ * outputFileType       - output file type
+ * saveToLibrary        - save to gallery
+ * maintainAspectRatio  - make the output aspect ratio match the input video
+ * width                - width for the output video
+ * height               - height for the output video
+ * videoBitrate         - video bitrate for the output video in bits
+ * audioChannels        - number of audio channels for the output video
+ * audioSampleRate      - sample rate for the audio (samples per second)
+ * audioBitrate         - audio bitrate for the output video in bits
+ *
+ * RESPONSE
+ * ========
+ *
+ * outputFilePath - path to output file
+ *
+ * @param CDVInvokedUrlCommand command
+ * @return void
+ */
+- (void) transcodeVideo:(CDVInvokedUrlCommand*)command
+{
+    NSDictionary* options = [command.arguments objectAtIndex:0];
+
+    if ([options isKindOfClass:[NSNull class]]) {
+        options = [NSDictionary dictionary];
+    }
+
+    NSString *inputFilePath = [options objectForKey:@"fileUri"];
+    NSURL *inputFileURL = [self getURLFromFilePath:inputFilePath];
+    NSString *videoFileName = [options objectForKey:@"outputFileName"];
+    CDVOutputFileType outputFileType = ([options objectForKey:@"outputFileType"]) ? [[options objectForKey:@"outputFileType"] intValue] : MPEG4;
+    BOOL optimizeForNetworkUse = ([options objectForKey:@"optimizeForNetworkUse"]) ? [[options objectForKey:@"optimizeForNetworkUse"] intValue] : NO;
+    BOOL saveToPhotoAlbum = [options objectForKey:@"saveToLibrary"] ? [[options objectForKey:@"saveToLibrary"] boolValue] : YES;
+    //float videoDuration = [[options objectForKey:@"duration"] floatValue];
+    BOOL maintainAspectRatio = [options objectForKey:@"maintainAspectRatio"] ? [[options objectForKey:@"maintainAspectRatio"] boolValue] : YES;
+    float width = [[options objectForKey:@"width"] floatValue];
+    float height = [[options objectForKey:@"height"] floatValue];
+    int videoBitrate = ([options objectForKey:@"videoBitrate"]) ? [[options objectForKey:@"videoBitrate"] intValue] : 1000000; // default to 1 megabit
+    int audioChannels = ([options objectForKey:@"audioChannels"]) ? [[options objectForKey:@"audioChannels"] intValue] : 2;
+    int audioSampleRate = ([options objectForKey:@"audioSampleRate"]) ? [[options objectForKey:@"audioSampleRate"] intValue] : 44100;
+    int audioBitrate = ([options objectForKey:@"audioBitrate"]) ? [[options objectForKey:@"audioBitrate"] intValue] : 128000; // default to 128 kilobits
+
+    NSString *stringOutputFileType = Nil;
+    NSString *outputExtension = Nil;
+
+    switch (outputFileType) {
+        case QUICK_TIME:
+            stringOutputFileType = AVFileTypeQuickTimeMovie;
+            outputExtension = @".mov";
+            break;
+        case M4A:
+            stringOutputFileType = AVFileTypeAppleM4A;
+            outputExtension = @".m4a";
+            break;
+        case M4V:
+            stringOutputFileType = AVFileTypeAppleM4V;
+            outputExtension = @".m4v";
+            break;
+        case MPEG4:
+        default:
+            stringOutputFileType = AVFileTypeMPEG4;
+            outputExtension = @".mp4";
+            break;
+    }
+
+    // check if the video can be saved to photo album before going further
+    if (saveToPhotoAlbum && !UIVideoAtPathIsCompatibleWithSavedPhotosAlbum([inputFileURL path]))
+    {
+        NSString *error = @"Video cannot be saved to photo album";
+        [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error ] callbackId:command.callbackId];
+        return;
+    }
+
+    AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:inputFileURL options:nil];
+
+    NSString *cacheDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
+    NSString *outputPath = [NSString stringWithFormat:@"%@/%@%@", cacheDir, videoFileName, outputExtension];
+    NSURL *outputURL = [NSURL fileURLWithPath:outputPath];
+
+    NSArray *tracks = [avAsset tracksWithMediaType:AVMediaTypeVideo];
+    AVAssetTrack *track = [tracks objectAtIndex:0];
+    CGSize mediaSize = track.naturalSize;
+
+    float videoWidth = mediaSize.width;
+    float videoHeight = mediaSize.height;
+    int newWidth;
+    int newHeight;
+
+    if (maintainAspectRatio) {
+        float aspectRatio = videoWidth / videoHeight;
+
+        // for some portrait videos ios gives the wrong width and height, this fixes that
+        NSString *videoOrientation = [self getOrientationForTrack:avAsset];
+        if ([videoOrientation isEqual: @"portrait"]) {
+            if (videoWidth > videoHeight) {
+                videoWidth = mediaSize.height;
+                videoHeight = mediaSize.width;
+                aspectRatio = videoWidth / videoHeight;
+            }
+        }
+
+        newWidth = (width && height) ? height * aspectRatio : videoWidth;
+        newHeight = (width && height) ? newWidth / aspectRatio : videoHeight;
+    } else {
+        newWidth = (width && height) ? width : videoWidth;
+        newHeight = (width && height) ? height : videoHeight;
+    }
+
+    NSLog(@"input videoWidth: %f", videoWidth);
+    NSLog(@"input videoHeight: %f", videoHeight);
+    NSLog(@"output newWidth: %d", newWidth);
+    NSLog(@"output newHeight: %d", newHeight);
+
+    SDAVAssetExportSession *encoder = [SDAVAssetExportSession.alloc initWithAsset:avAsset];
+    encoder.outputFileType = stringOutputFileType;
+    encoder.outputURL = outputURL;
+    encoder.shouldOptimizeForNetworkUse = optimizeForNetworkUse;
+    encoder.videoSettings = @
+    {
+        AVVideoCodecKey: AVVideoCodecH264,
+        AVVideoWidthKey: [NSNumber numberWithInt: newWidth],
+        AVVideoHeightKey: [NSNumber numberWithInt: newHeight],
+        AVVideoCompressionPropertiesKey: @
+        {
+            AVVideoAverageBitRateKey: [NSNumber numberWithInt: videoBitrate],
+            AVVideoProfileLevelKey: AVVideoProfileLevelH264High40
+        }
+    };
+    encoder.audioSettings = @
+    {
+        AVFormatIDKey: @(kAudioFormatMPEG4AAC),
+        AVNumberOfChannelsKey: [NSNumber numberWithInt: audioChannels],
+        AVSampleRateKey: [NSNumber numberWithInt: audioSampleRate],
+        AVEncoderBitRateKey: [NSNumber numberWithInt: audioBitrate]
+    };
+
+    /* // setting timeRange is not possible due to a bug with SDAVAssetExportSession (https://github.com/rs/SDAVAssetExportSession/issues/28)
+     if (videoDuration) {
+     int32_t preferredTimeScale = 600;
+     CMTime startTime = CMTimeMakeWithSeconds(0, preferredTimeScale);
+     CMTime stopTime = CMTimeMakeWithSeconds(videoDuration, preferredTimeScale);
+     CMTimeRange exportTimeRange = CMTimeRangeFromTimeToTime(startTime, stopTime);
+     encoder.timeRange = exportTimeRange;
+     }
+     */
+
+    //  Set up a semaphore for the completion handler and progress timer
+    dispatch_semaphore_t sessionWaitSemaphore = dispatch_semaphore_create(0);
+
+    void (^completionHandler)(void) = ^(void)
+    {
+        dispatch_semaphore_signal(sessionWaitSemaphore);
+    };
+
+    // do it
+
+    [self.commandDelegate runInBackground:^{
+        [encoder exportAsynchronouslyWithCompletionHandler:completionHandler];
+
+        do {
+            dispatch_time_t dispatchTime = DISPATCH_TIME_FOREVER;  // if we dont want progress, we will wait until it finishes.
+            dispatchTime = getDispatchTimeFromSeconds((float)1.0);
+            double progress = [encoder progress] * 100;
+
+            NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
+            [dictionary setValue: [NSNumber numberWithDouble: progress] forKey: @"progress"];
+
+            CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: dictionary];
+
+            [result setKeepCallbackAsBool:YES];
+            [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+            dispatch_semaphore_wait(sessionWaitSemaphore, dispatchTime);
+        } while( [encoder status] < AVAssetExportSessionStatusCompleted );
+
+        // this is kinda odd but must be done
+        if ([encoder status] == AVAssetExportSessionStatusCompleted) {
+            NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
+            // AVAssetExportSessionStatusCompleted will not always mean progress is 100 so hard code it below
+            double progress = 100.00;
+            [dictionary setValue: [NSNumber numberWithDouble: progress] forKey: @"progress"];
+
+            CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: dictionary];
+
+            [result setKeepCallbackAsBool:YES];
+            [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+        }
+
+        if (encoder.status == AVAssetExportSessionStatusCompleted)
+        {
+            NSLog(@"Video export succeeded");
+            if (saveToPhotoAlbum) {
+                UISaveVideoAtPathToSavedPhotosAlbum(outputPath, self, nil, nil);
+            }
+            [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:outputPath] callbackId:command.callbackId];
+        }
+        else if (encoder.status == AVAssetExportSessionStatusCancelled)
+        {
+            NSLog(@"Video export cancelled");
+            [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Video export cancelled"] callbackId:command.callbackId];
+        }
+        else
+        {
+            NSString *error = [NSString stringWithFormat:@"Video export failed with error: %@ (%ld)", encoder.error.localizedDescription, (long)encoder.error.code];
+            [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:error] callbackId:command.callbackId];
+        }
+    }];
+}
+
+/**
+ * createThumbnail
+ *
+ * Creates a thumbnail from the start of a video.
+ *
+ * ARGUMENTS
+ * =========
+ * fileUri        - input file path
+ * outputFileName - output file name
+ * atTime         - location in the video to create the thumbnail (in seconds),
+ * width          - width of the thumbnail (optional)
+ * height         - height of the thumbnail (optional)
+ * quality        - quality of the thumbnail (between 1 and 100)
+ *
+ * RESPONSE
+ * ========
+ *
+ * outputFilePath - path to output file
+ *
+ * @param CDVInvokedUrlCommand command
+ * @return void
+ */
+- (void) createThumbnail:(CDVInvokedUrlCommand*)command
+{
+    NSLog(@"createThumbnail");
+    NSDictionary* options = [command.arguments objectAtIndex:0];
+
+    if ([options isKindOfClass:[NSNull class]]) {
+        options = [NSDictionary dictionary];
+    }
+
+    NSString* srcVideoPath = [options objectForKey:@"fileUri"];
+    NSString* outputFileName = [options objectForKey:@"outputFileName"];
+    float atTime = ([options objectForKey:@"atTime"]) ? [[options objectForKey:@"atTime"] floatValue] : 0;
+    float width = [[options objectForKey:@"width"] floatValue];
+    float height = [[options objectForKey:@"height"] floatValue];
+    float quality = ([options objectForKey:@"quality"]) ? [[options objectForKey:@"quality"] floatValue] : 100;
+    float thumbQuality = quality * 1.0 / 100;
+
+    int32_t preferredTimeScale = 600;
+    CMTime time = CMTimeMakeWithSeconds(atTime, preferredTimeScale);
+
+    UIImage* thumbnail = [self generateThumbnailImage:srcVideoPath atTime:time];
+
+    if (width && height) {
+        NSLog(@"got width and height, resizing image");
+        CGSize newSize = CGSizeMake(width, height);
+        thumbnail = [self scaleImage:thumbnail toSize:newSize];
+        NSLog(@"new size of thumbnail, width x height = %f x %f", thumbnail.size.width, thumbnail.size.height);
+    }
+
+    NSString *cacheDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
+    NSString *outputFilePath = [cacheDir stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", outputFileName, @"jpg"]];
+
+    // write out the thumbnail
+    if ([UIImageJPEGRepresentation(thumbnail, thumbQuality) writeToFile:outputFilePath atomically:YES])
+    {
+        NSLog(@"path to your video thumbnail: %@", outputFilePath);
+        [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:outputFilePath] callbackId:command.callbackId];
+    }
+    else
+    {
+        [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"failed to create thumbnail file"] callbackId:command.callbackId];
+    }
+}
+
+/**
+ * getVideoInfo
+ *
+ * Creates a thumbnail from the start of a video.
+ *
+ * ARGUMENTS
+ * =========
+ * fileUri       - input file path
+ *
+ * RESPONSE
+ * ========
+ *
+ * width         - width of the video
+ * height        - height of the video
+ * orientation   - orientation of the video
+ * duration      - duration of the video (in seconds)
+ * size          - size of the video (in bytes)
+ * bitrate       - bitrate of the video (in bits per second)
+ *
+ * @param CDVInvokedUrlCommand command
+ * @return void
+ */
+- (void) getVideoInfo:(CDVInvokedUrlCommand*)command
+{
+    NSLog(@"getVideoInfo");
+    NSDictionary* options = [command.arguments objectAtIndex:0];
+
+    if ([options isKindOfClass:[NSNull class]]) {
+        options = [NSDictionary dictionary];
+    }
+
+    NSString *filePath = [options objectForKey:@"fileUri"];
+    NSURL *fileURL = [self getURLFromFilePath:filePath];
+
+    unsigned long long size = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:nil].fileSize;
+
+    AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:fileURL options:nil];
+
+    NSArray *tracks = [avAsset tracksWithMediaType:AVMediaTypeVideo];
+    AVAssetTrack *track = [tracks objectAtIndex:0];
+    CGSize mediaSize = track.naturalSize;
+
+    float videoWidth = mediaSize.width;
+    float videoHeight = mediaSize.height;
+    float aspectRatio = videoWidth / videoHeight;
+
+    // for some portrait videos ios gives the wrong width and height, this fixes that
+    NSString *videoOrientation = [self getOrientationForTrack:avAsset];
+    if ([videoOrientation isEqual: @"portrait"]) {
+        if (videoWidth > videoHeight) {
+            videoWidth = mediaSize.height;
+            videoHeight = mediaSize.width;
+            aspectRatio = videoWidth / videoHeight;
+        }
+    }
+
+    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
+    [dict setObject:[NSNumber numberWithFloat:videoWidth] forKey:@"width"];
+    [dict setObject:[NSNumber numberWithFloat:videoHeight] forKey:@"height"];
+    [dict setValue:videoOrientation forKey:@"orientation"];
+    [dict setValue:[NSNumber numberWithFloat:track.timeRange.duration.value / 600.0] forKey:@"duration"];
+    [dict setObject:[NSNumber numberWithLongLong:size] forKey:@"size"];
+    [dict setObject:[NSNumber numberWithFloat:track.estimatedDataRate] forKey:@"bitrate"];
+
+    [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:dict] callbackId:command.callbackId];
+}
+
+/**
+ * trim
+ *
+ * Performs a trim operation on a clip, while encoding it.
+ *
+ * ARGUMENTS
+ * =========
+ * fileUri        - input file path
+ * trimStart      - time to start trimming
+ * trimEnd        - time to end trimming
+ * outputFileName - output file name
+ * progress:      - optional callback function that receives progress info
+ *
+ * RESPONSE
+ * ========
+ *
+ * outputFilePath - path to output file
+ *
+ * @param CDVInvokedUrlCommand command
+ * @return void
+ */
+- (void) trim:(CDVInvokedUrlCommand*)command {
+    NSLog(@"[Trim]: trim called");
+
+    // extract arguments
+    NSDictionary* options = [command.arguments objectAtIndex:0];
+    if ([options isKindOfClass:[NSNull class]]) {
+        options = [NSDictionary dictionary];
+    }
+    NSString *inputFilePath = [options objectForKey:@"fileUri"];
+    NSURL *inputFileURL = [self getURLFromFilePath:inputFilePath];
+    float trimStart = [[options objectForKey:@"trimStart"] floatValue];
+    float trimEnd = [[options objectForKey:@"trimEnd"] floatValue];
+    NSString *outputName = [options objectForKey:@"outputFileName"];
+
+    NSFileManager *fileMgr = [NSFileManager defaultManager];
+    NSString *cacheDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
+
+    // videoDir
+    NSString *videoDir = [cacheDir stringByAppendingPathComponent:@"mp4"];
+    if ([fileMgr createDirectoryAtPath:videoDir withIntermediateDirectories:YES attributes:nil error: NULL] == NO){
+        [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"failed to create video dir"] callbackId:command.callbackId];
+        return;
+    }
+    NSString *videoOutput = [videoDir stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", outputName, @"mp4"]];
+
+    NSLog(@"[Trim]: inputFilePath: %@", inputFilePath);
+    NSLog(@"[Trim]: outputPath: %@", videoOutput);
+
+    // run in background
+    [self.commandDelegate runInBackground:^{
+
+        AVURLAsset *avAsset = [AVURLAsset URLAssetWithURL:inputFileURL options:nil];
+
+        AVAssetExportSession *exportSession = [[AVAssetExportSession alloc]initWithAsset:avAsset presetName: AVAssetExportPresetHighestQuality];
+        exportSession.outputURL = [NSURL fileURLWithPath:videoOutput];
+        exportSession.outputFileType = AVFileTypeQuickTimeMovie;
+        exportSession.shouldOptimizeForNetworkUse = YES;
+
+        int32_t preferredTimeScale = 600;
+        CMTime startTime = CMTimeMakeWithSeconds(trimStart, preferredTimeScale);
+        CMTime stopTime = CMTimeMakeWithSeconds(trimEnd, preferredTimeScale);
+        CMTimeRange exportTimeRange = CMTimeRangeFromTimeToTime(startTime, stopTime);
+        exportSession.timeRange = exportTimeRange;
+
+        // debug timings
+        NSString *trimStart = (NSString *) CFBridgingRelease(CMTimeCopyDescription(NULL, startTime));
+        NSString *trimEnd = (NSString *) CFBridgingRelease(CMTimeCopyDescription(NULL, stopTime));
+        NSLog(@"[Trim]: duration: %lld, trimStart: %@, trimEnd: %@", avAsset.duration.value, trimStart, trimEnd);
+
+        //  Set up a semaphore for the completion handler and progress timer
+        dispatch_semaphore_t sessionWaitSemaphore = dispatch_semaphore_create(0);
+
+        void (^completionHandler)(void) = ^(void)
+        {
+            dispatch_semaphore_signal(sessionWaitSemaphore);
+        };
+
+        // do it
+        [exportSession exportAsynchronouslyWithCompletionHandler:completionHandler];
+
+        do {
+            dispatch_time_t dispatchTime = DISPATCH_TIME_FOREVER;  // if we dont want progress, we will wait until it finishes.
+            dispatchTime = getDispatchTimeFromSeconds((float)1.0);
+            double progress = [exportSession progress] * 100;
+
+            NSLog([NSString stringWithFormat:@"AVAssetExport running progress=%3.2f%%", progress]);
+
+            NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
+            [dictionary setValue: [NSNumber numberWithDouble: progress] forKey: @"progress"];
+
+            CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: dictionary];
+
+            [result setKeepCallbackAsBool:YES];
+            [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+            dispatch_semaphore_wait(sessionWaitSemaphore, dispatchTime);
+        } while( [exportSession status] < AVAssetExportSessionStatusCompleted );
+
+        // this is kinda odd but must be done
+        if ([exportSession status] == AVAssetExportSessionStatusCompleted) {
+            NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
+            // AVAssetExportSessionStatusCompleted will not always mean progress is 100 so hard code it below
+            double progress = 100.00;
+            [dictionary setValue: [NSNumber numberWithDouble: progress] forKey: @"progress"];
+
+            CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary: dictionary];
+
+            [result setKeepCallbackAsBool:YES];
+            [self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
+        }
+
+        switch ([exportSession status]) {
+            case AVAssetExportSessionStatusCompleted:
+                NSLog(@"[Trim]: Export Complete %d %@", exportSession.status, exportSession.error);
+                [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:videoOutput] callbackId:command.callbackId];
+                break;
+            case AVAssetExportSessionStatusFailed:
+                NSLog(@"[Trim]: Export failed: %@", [[exportSession error] localizedDescription]);
+                [self.commandDelegate sendPluginResult:[CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:[[exportSession error] localizedDescription]] callbackId:command.callbackId];
+                break;
+            case AVAssetExportSessionStatusCancelled:
+                NSLog(@"[Trim]: Export canceled");
+                break;
+            default:
+                NSLog(@"[Trim]: Export default in switch");
+                break;
+        }
+
+    }];
+}
+
+// modified version of http://stackoverflow.com/a/21230645/1673842
+- (UIImage *)generateThumbnailImage: (NSString *)srcVideoPath atTime:(CMTime)time
+{
+    NSURL *url = [NSURL fileURLWithPath:srcVideoPath];
+
+    if ([srcVideoPath rangeOfString:@"://"].location == NSNotFound)
+    {
+        url = [NSURL URLWithString:[[@"file://localhost" stringByAppendingString:srcVideoPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
+    }
+    else
+    {
+        url = [NSURL URLWithString:[srcVideoPath stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding]];
+    }
+
+    AVAsset *asset = [AVAsset assetWithURL:url];
+    AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];
+    imageGenerator.requestedTimeToleranceAfter = kCMTimeZero; // needed to get a precise time (http://stackoverflow.com/questions/5825990/i-cannot-get-a-precise-cmtime-for-generating-still-image-from-1-8-second-video)
+    imageGenerator.requestedTimeToleranceBefore = kCMTimeZero; // ^^
+    imageGenerator.appliesPreferredTrackTransform = YES; // crucial to have the right orientation for the image (http://stackoverflow.com/questions/9145968/getting-video-snapshot-for-thumbnail)
+    CGImageRef imageRef = [imageGenerator copyCGImageAtTime:time actualTime:NULL error:NULL];
+    UIImage *thumbnail = [UIImage imageWithCGImage:imageRef];
+    CGImageRelease(imageRef);  // CGImageRef won't be released by ARC
+
+    return thumbnail;
+}
+
+// to scale images without changing aspect ratio (http://stackoverflow.com/a/8224161/1673842)
+- (UIImage*)scaleImage:(UIImage*)image
+                toSize:(CGSize)newSize;
+{
+    float oldWidth = image.size.width;
+    float scaleFactor = newSize.width / oldWidth;
+
+    float newHeight = image.size.height * scaleFactor;
+    float newWidth = oldWidth * scaleFactor;
+
+    UIGraphicsBeginImageContext(CGSizeMake(newWidth, newHeight));
+    [image drawInRect:CGRectMake(0, 0, newWidth, newHeight)];
+    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
+    UIGraphicsEndImageContext();
+    return newImage;
+}
+
+// inspired by http://stackoverflow.com/a/6046421/1673842
+- (NSString*)getOrientationForTrack:(AVAsset *)asset
+{
+    AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
+    CGSize size = [videoTrack naturalSize];
+    CGAffineTransform txf = [videoTrack preferredTransform];
+
+    if (size.width == txf.tx && size.height == txf.ty)
+        return @"landscape";
+    else if (txf.tx == 0 && txf.ty == 0)
+        return @"landscape";
+    else if (txf.tx == 0 && txf.ty == size.width)
+        return @"portrait";
+    else
+        return @"portrait";
+}
+
+- (NSURL*)getURLFromFilePath:(NSString*)filePath
+{
+    if ([filePath containsString:@"assets-library://"]) {
+        return [NSURL URLWithString:[filePath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
+    } else if ([filePath containsString:@"file://"]) {
+        return [NSURL URLWithString:[filePath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
+    }
+
+    return [NSURL fileURLWithPath:[filePath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
+}
+
+static dispatch_time_t getDispatchTimeFromSeconds(float seconds) {
+    long long milliseconds = seconds * 1000.0;
+    dispatch_time_t waitTime = dispatch_time( DISPATCH_TIME_NOW, 1000000LL * milliseconds );
+    return waitTime;
+}
+
+@end

+ 206 - 0
miaomiao/plugins/cordova-plugin-video-editor/src/windows/VideoEditorProxy.js

xqd
@@ -0,0 +1,206 @@
+
+module.exports = {
+
+    trim: function (win, fail, args) {
+        //get args from cordova app
+        var options = args[0];
+        var comp, outboundFilePath;
+
+        //look up the video file
+        Windows.Storage.StorageFile.getFileFromApplicationUriAsync(new Windows.Foundation.Uri(options.fileUri)).then(function (file) {
+            //create a clip from the video
+            return Windows.Media.Editing.MediaClip.createFromFileAsync(file);
+        }).then(function (clip) {
+            //apply the trims, which are in milliseconds
+            clip.trimTimeFromStart = options.trimStart * 1000;
+            clip.trimTimeFromEnd = (options.trimEnd * 1000);
+
+            //setup a comp
+            comp = new Windows.Media.Editing.MediaComposition();
+            comp.clips.push(clip);
+
+            //create an outbound file location
+            return Windows.Storage.ApplicationData.current.localFolder.createFileAsync(options.outputFileName, Windows.Storage.CreationCollisionOption.replaceExisting);
+        }).then(function (outboundFile) {
+            outboundFilePath = outboundFile.path;
+            //render the trimmed video to file
+            return comp.renderToFileAsync(outboundFile);
+        }).done(function (file) {
+            //return the path to the success method
+            win(outboundFilePath);
+        });
+    },
+
+    createThumbnail: function (win, fail, args) {
+        //get args from cordova app
+        var options = args[0];
+        var comp, outputStream, writer, reader, thumbnailStream, outboundFilePath;
+
+        //look up the video file
+        Windows.Storage.StorageFile.getFileFromApplicationUriAsync(new Windows.Foundation.Uri(options.fileUri)).then(function (file) {
+            //create clip from video
+            return Windows.Media.Editing.MediaClip.createFromFileAsync(file);
+        }).then(function (clip) {
+            //setup a comp
+            comp = new Windows.Media.Editing.MediaComposition();
+            comp.clips.push(clip);
+
+            //create an outbound file location
+            return Windows.Storage.ApplicationData.current.localFolder.createFileAsync(options.outputFileName, Windows.Storage.CreationCollisionOption.replaceExisting);
+        }).then(function (outboundFile) {
+            //store outbound file location in a temp variable
+            outboundFilePath = outboundFile.path;
+            //open the file for writing
+            return outboundFile.openAsync(Windows.Storage.FileAccessMode.readWrite);
+        }).then(function (outboundStream) {
+            //prepare the output stream
+            outputStream = outboundStream.getOutputStreamAt(0);
+            //take a snip from the video
+            return comp.getThumbnailAsync(0, 1080, 920, Windows.Media.Editing.VideoFramePrecision.nearestFrame);
+        }).then(function (thumbnail) {
+            //keep reference so we can dispose later
+            thumbnailStream = thumbnail;
+            //keep reference so we can dispose later
+            reader = new Windows.Storage.Streams.DataReader(thumbnailStream.getInputStreamAt(0));
+
+            //load the thumbprint
+            return reader.loadAsync(thumbnailStream.size);
+        }).then(function () {
+            //keep reference so we can dispose later
+            writer = new Windows.Storage.Streams.DataWriter(outputStream);
+            //writer to the buffer
+            while (reader.unconsumedBufferLength > 0) {
+                writer.writeBuffer(reader.readBuffer(((reader.unconsumedBufferLength > 64) ? 64 : reader.unconsumedBufferLength)));
+            }
+            //transfer the buffer and write
+            return outputStream.writeAsync(writer.detachBuffer());
+        }).then(function (bytesWritten) {
+            console.log('Bytes written ' + bytesWritten);
+            //clear the stream
+            return outputStream.flushAsync();
+        }).done(function (outboundFile) {
+            //dispose all references
+            writer.close();
+            reader.close();
+            thumbnailStream.close();
+            //call win function for cordova
+            win(outboundFilePath);
+        });
+    },
+
+    transcodeVideo: function (win, fail, args) {
+        var videoQualities = Windows.Media.MediaProperties.VideoEncodingQuality;
+
+        //get args from cordova app
+        var options = args[0];
+        //easier way to access the quality
+        var qualities = [videoQualities.hd1080p, videoQualities.hd720p, videoQualities.wvga];
+        //chosen quality
+        var quality = qualities[options.quality];
+        var mediaProfile, sourceFile, destinationFile, duration = options.duration, optimize = options.optimizeForNetworkUse;
+
+        switch (options.outputFileType) {
+            //both m4v and mpeg4 transcode to mp4
+            case 0: 
+            case 1:{
+                mediaProfile = Windows.Media.MediaProperties.MediaEncodingProfile.createMp4(quality);
+                break;
+            }
+            //m4a transcoded with m4a
+            case 2:{
+                mediaProfile = Windows.Media.MediaProperties.MediaEncodingProfile.createM4a(quality);
+                break;
+            }
+            //we don't support anything more
+            default:{
+                throw 'output file type not supported on windows with this format';
+                break;
+            }
+        
+        }
+
+        //get the source file
+        Windows.Storage.StorageFile.getFileFromApplicationUriAsync(new Windows.Foundation.Uri(options.fileUri)).then(function (source) {
+            sourceFile = source;
+            //create the destination file
+            return Windows.Storage.ApplicationData.current.localFolder.createFileAsync(options.outputFileName, Windows.Storage.CreationCollisionOption.replaceExisting);
+        }).then(function (destination) {
+            destinationFile = destination;
+
+            var transcoder = new Windows.Media.Transcoding.MediaTranscoder();
+            //quality over speed and performance 
+            if (!optimize) {
+                transcoder.videoProcessingAlgorithm = Windows.Media.Transcoding.MediaVideoProcessingAlgorithm.mrfCrf444
+            }
+            //prepare transcoding
+            return transcoder.prepareFileTranscodeAsync(sourceFile, destinationFile, mediaProfile);
+        }).then(function (preparedTranscode) {
+            //perform the transcode
+            return preparedTranscode.transcodeAsync();
+        }).done(function () {
+            //return the destination file path
+            win(destinationFile.path);
+        }, function (details) {
+            //failed
+            fail(details);
+        });
+    },
+
+    /**
+     * getVideoInfo
+     *
+     * Get common video info for the uri passed in
+     *
+     * ARGUMENTS
+     * =========
+     * fileUri       - input file path
+     *
+     * RESPONSE
+     * ========
+     *
+     * width         - width of the video
+     * height        - height of the video
+     * orientation   - orientation of the video
+     * duration      - duration of the video (in seconds)
+     * size          - size of the video (in bytes)
+     * bitrate       - bitrate of the video (in bits per second)
+     *
+     * @param promise win
+     * @param promise fail
+     * @param object args
+     * @return void
+     */
+    getVideoInfo: function (win, fail, args) {
+        //get args from cordova app
+        var options = args[0];
+
+        var file, basicProps;
+
+        //look up the video file
+        Windows.Storage.StorageFile.getFileFromApplicationUriAsync(new Windows.Foundation.Uri(options.fileUri)).then(function (storageFile) {
+            //assign storage file to global variable
+            file = storageFile;
+            //get basic properties for size
+            return file.getBasicPropertiesAsync();
+        }).then(function (basicProperties) {
+            basicProps = basicProperties;
+            //get video properties for the rest of info
+            return file.properties.getVideoPropertiesAsync();
+        }).done(function (videoProps) {
+            //resolve the video info
+            win({
+                width: videoProps.width,    
+                height: videoProps.height,     
+                orientation: (videoProps.height > videoProps.width) ? 'portrait' :'landscape',
+                duration: videoProps.duration,
+                size: basicProps.size,
+                bitrate: videoProps.bitrate  
+            });
+        }, function (details) {
+            //failed
+            fail(details);
+        });
+    }
+}
+
+require("cordova/exec/proxy").add("VideoEditor", module.exports);

+ 206 - 0
miaomiao/plugins/cordova-plugin-video-editor/src/windows8/VideoEditorProxy.js

xqd
@@ -0,0 +1,206 @@
+
+module.exports = {
+
+    trim: function (win, fail, args) {
+        //get args from cordova app
+        var options = args[0];
+        var comp, outboundFilePath;
+
+        //look up the video file
+        Windows.Storage.StorageFile.getFileFromApplicationUriAsync(new Windows.Foundation.Uri(options.fileUri)).then(function (file) {
+            //create a clip from the video
+            return Windows.Media.Editing.MediaClip.createFromFileAsync(file);
+        }).then(function (clip) {
+            //apply the trims, which are in milliseconds
+            clip.trimTimeFromStart = options.trimStart * 1000;
+            clip.trimTimeFromEnd = (options.trimEnd * 1000);
+
+            //setup a comp
+            comp = new Windows.Media.Editing.MediaComposition();
+            comp.clips.push(clip);
+
+            //create an outbound file location
+            return Windows.Storage.ApplicationData.current.localFolder.createFileAsync(options.outputFileName, Windows.Storage.CreationCollisionOption.replaceExisting);
+        }).then(function (outboundFile) {
+            outboundFilePath = outboundFile.path;
+            //render the trimmed video to file
+            return comp.renderToFileAsync(outboundFile);
+        }).done(function (file) {
+            //return the path to the success method
+            win(outboundFilePath);
+        });
+    },
+
+    createThumbnail: function (win, fail, args) {
+        //get args from cordova app
+        var options = args[0];
+        var comp, outputStream, writer, reader, thumbnailStream, outboundFilePath;
+
+        //look up the video file
+        Windows.Storage.StorageFile.getFileFromApplicationUriAsync(new Windows.Foundation.Uri(options.fileUri)).then(function (file) {
+            //create clip from video
+            return Windows.Media.Editing.MediaClip.createFromFileAsync(file);
+        }).then(function (clip) {
+            //setup a comp
+            comp = new Windows.Media.Editing.MediaComposition();
+            comp.clips.push(clip);
+
+            //create an outbound file location
+            return Windows.Storage.ApplicationData.current.localFolder.createFileAsync(options.outputFileName, Windows.Storage.CreationCollisionOption.replaceExisting);
+        }).then(function (outboundFile) {
+            //store outbound file location in a temp variable
+            outboundFilePath = outboundFile.path;
+            //open the file for writing
+            return outboundFile.openAsync(Windows.Storage.FileAccessMode.readWrite);
+        }).then(function (outboundStream) {
+            //prepare the output stream
+            outputStream = outboundStream.getOutputStreamAt(0);
+            //take a snip from the video
+            return comp.getThumbnailAsync(0, 1080, 920, Windows.Media.Editing.VideoFramePrecision.nearestFrame);
+        }).then(function (thumbnail) {
+            //keep reference so we can dispose later
+            thumbnailStream = thumbnail;
+            //keep reference so we can dispose later
+            reader = new Windows.Storage.Streams.DataReader(thumbnailStream.getInputStreamAt(0));
+
+            //load the thumbprint
+            return reader.loadAsync(thumbnailStream.size);
+        }).then(function () {
+            //keep reference so we can dispose later
+            writer = new Windows.Storage.Streams.DataWriter(outputStream);
+            //writer to the buffer
+            while (reader.unconsumedBufferLength > 0) {
+                writer.writeBuffer(reader.readBuffer(((reader.unconsumedBufferLength > 64) ? 64 : reader.unconsumedBufferLength)));
+            }
+            //transfer the buffer and write
+            return outputStream.writeAsync(writer.detachBuffer());
+        }).then(function (bytesWritten) {
+            console.log('Bytes written ' + bytesWritten);
+            //clear the stream
+            return outputStream.flushAsync();
+        }).done(function (outboundFile) {
+            //dispose all references
+            writer.close();
+            reader.close();
+            thumbnailStream.close();
+            //call win function for cordova
+            win(outboundFilePath);
+        });
+    },
+
+    transcodeVideo: function (win, fail, args) {
+        var videoQualities = Windows.Media.MediaProperties.VideoEncodingQuality;
+
+        //get args from cordova app
+        var options = args[0];
+        //easier way to access the quality
+        var qualities = [videoQualities.hd1080p, videoQualities.hd720p, videoQualities.wvga];
+        //chosen quality
+        var quality = qualities[options.quality];
+        var mediaProfile, sourceFile, destinationFile, duration = options.duration, optimize = options.optimizeForNetworkUse;
+
+        switch (options.outputFileType) {
+            //both m4v and mpeg4 transcode to mp4
+            case 0: 
+            case 1:{
+                mediaProfile = Windows.Media.MediaProperties.MediaEncodingProfile.createMp4(quality);
+                break;
+            }
+            //m4a transcoded with m4a
+            case 2:{
+                mediaProfile = Windows.Media.MediaProperties.MediaEncodingProfile.createM4a(quality);
+                break;
+            }
+            //we don't support anything more
+            default:{
+                throw 'output file type not supported on windows with this format';
+                break;
+            }
+        
+        }
+
+        //get the source file
+        Windows.Storage.StorageFile.getFileFromApplicationUriAsync(new Windows.Foundation.Uri(options.fileUri)).then(function (source) {
+            sourceFile = source;
+            //create the destination file
+            return Windows.Storage.ApplicationData.current.localFolder.createFileAsync(options.outputFileName, Windows.Storage.CreationCollisionOption.replaceExisting);
+        }).then(function (destination) {
+            destinationFile = destination;
+
+            var transcoder = new Windows.Media.Transcoding.MediaTranscoder();
+            //quality over speed and performance 
+            if (!optimize) {
+                transcoder.videoProcessingAlgorithm = Windows.Media.Transcoding.MediaVideoProcessingAlgorithm.mrfCrf444
+            }
+            //prepare transcoding
+            return transcoder.prepareFileTranscodeAsync(sourceFile, destinationFile, mediaProfile);
+        }).then(function (preparedTranscode) {
+            //perform the transcode
+            return preparedTranscode.transcodeAsync();
+        }).done(function () {
+            //return the destination file path
+            win(destinationFile.path);
+        }, function (details) {
+            //failed
+            fail(details);
+        });
+    },
+
+    /**
+     * getVideoInfo
+     *
+     * Get common video info for the uri passed in
+     *
+     * ARGUMENTS
+     * =========
+     * fileUri       - input file path
+     *
+     * RESPONSE
+     * ========
+     *
+     * width         - width of the video
+     * height        - height of the video
+     * orientation   - orientation of the video
+     * duration      - duration of the video (in seconds)
+     * size          - size of the video (in bytes)
+     * bitrate       - bitrate of the video (in bits per second)
+     *
+     * @param promise win
+     * @param promise fail
+     * @param object args
+     * @return void
+     */
+    getVideoInfo: function (win, fail, args) {
+        //get args from cordova app
+        var options = args[0];
+
+        var file, basicProps;
+
+        //look up the video file
+        Windows.Storage.StorageFile.getFileFromApplicationUriAsync(new Windows.Foundation.Uri(options.fileUri)).then(function (storageFile) {
+            //assign storage file to global variable
+            file = storageFile;
+            //get basic properties for size
+            return file.getBasicPropertiesAsync();
+        }).then(function (basicProperties) {
+            basicProps = basicProperties;
+            //get video properties for the rest of info
+            return file.properties.getVideoPropertiesAsync();
+        }).done(function (videoProps) {
+            //resolve the video info
+            win({
+                width: videoProps.width,    
+                height: videoProps.height,     
+                orientation: (videoProps.height > videoProps.width) ? 'portrait' :'landscape',
+                duration: videoProps.duration,
+                size: basicProps.size,
+                bitrate: videoProps.bitrate  
+            });
+        }, function (details) {
+            //failed
+            fail(details);
+        });
+    }
+}
+
+require("cordova/exec/proxy").add("VideoEditor", module.exports);

+ 169 - 0
miaomiao/plugins/cordova-plugin-video-editor/typings/VideoEditor.d.ts

xqd
@@ -0,0 +1,169 @@
+/**
+ * Enumerations for transcoding
+ */
+declare module VideoEditorOptions {
+    //output quailty
+    enum Quality {
+        HIGH_QUALITY,
+        MEDIUM_QUALITY,
+        LOW_QUALITY
+    }
+    //speed over quailty, maybe should be a bool
+    enum OptimizeForNetworkUse {
+        NO,
+        YES
+    }
+    //type of encoding to do
+    enum OutputFileType {
+        M4V,
+        MPEG4,
+        M4A,
+        QUICK_TIME
+    }
+}
+
+/**
+ * Transcode options that are required to reencode or change the coding of the video.
+ */
+declare interface VideoEditorTranscodeProperties {
+        /** A well-known location where the editable video lives. */
+        fileUri: string,
+        /** A string that indicates what type of field this is, home for example. */
+        outputFileName: string,
+        /** The quality of the result. */
+        quality: VideoEditorOptions.Quality,
+        /** Instructions on how to encode the video. */
+        outputFileType: VideoEditorOptions.OutputFileType,
+        /** Should the video be processed with quailty or speed in mind */
+        optimizeForNetworkUse: VideoEditorOptions.OptimizeForNetworkUse,
+        /** Not supported in windows, the duration in seconds from the start of the video*/
+        duration?: number,
+        /** Not supported in windows, save into the device library*/
+        saveToLibrary?: boolean,
+        /** Not supported in windows, delete the orginal video*/
+        deleteInputFile?: boolean,
+        /** iOS only. Defaults to true */
+        maintainAspectRatio?: boolean,
+        /** Width of the result */
+        width?: number,
+        /** Height of the result */
+        height?: number,
+        /** Bitrate in bits. Defaults to 1 megabit (1000000). */
+        videoBitrate?: number,
+        /** Frames per second of the result. Android only. Defaults to 24. */
+        fps?: number,
+        /** Number of audio channels. iOS only. Defaults to 2. */
+        audioChannels?: number,
+        /** Sample rate for the audio. iOS only. Defaults to 4410. */
+        audioBitrate?: number,
+        /** Not supported in windows, progress on the transcode*/
+        progress?: (info: any) => void
+}
+
+/**
+ * Trim options that are required to locate, reduce start/ end and save the video.
+ */
+declare interface VideoEditorTrimProperties {
+        /** A well-known location where the editable video lives. */
+        fileUri: string,
+        /** A number of seconds to trim the front of the video. */
+        trimStart: number,
+        /** A number of seconds to trim the front of the video. */
+        trimEnd: number,
+        /** A string that indicates what type of field this is, home for example. */
+        outputFileName: string,
+        /** Progress on transcode. */
+        progress?: (info: any) => void
+}
+
+/**
+ * Trim options that are required to locate, reduce start/ end and save the video.
+ */
+declare interface VideoEditorThumbnailProperties {
+        /** A well-known location where the editable video lives. */
+        fileUri: string,
+        /** A string that indicates what type of field this is, home for example. */
+        outputFileName: string,
+        /** Location in video to create the thumbnail (in seconds). */
+        atTime?: number,
+        /** Width of the thumbnail. */
+        width?: number,
+        /** Height of the thumbnail. */
+        height?: number,
+        /** Quality of the thumbnail (between 1 and 100). */
+        quality?: number
+}
+
+declare interface VideoEditorVideoInfoOptions {
+        /** Path to the video on the device. */
+        fileUri: string
+}
+
+declare interface VideoEditorVideoInfoDetails {
+        /** Width of the video. */
+        width: number,
+        /** Height of the video. */
+        height: number,
+        /** Orientation of the video. Will be either portrait or landscape. */
+        orientation: 'portrait' | 'landscape',
+        /** Duration of the video in seconds. */
+        duration: number,
+        /** Size of the video in bytes. */
+        size: number,
+        /** Bitrate of the video in bits per second. */
+        bitrate: number
+}
+
+/**
+ * The VideoEditor object represents a tool for editing videos. Videos can only be trimmed, so far.
+ */
+interface VideoEditor {
+    /**
+    * The VideoEditor.transcode method executes asynchronously, encoding a video at a location
+    * and returning the full path. Options can be set to change how the video is encoded. The resulting string 
+    * is passed to the onSuccess callback function specified by the onSuccess parameter.
+    * @param onSuccess Success callback function invoked with the full path of the video returned from successly saving the video
+    * @param onError Error callback function, invoked when an error occurs.
+    * @param transcodeOptions Transcode options that are required to reencode or change the coding of the video.
+    */
+    transcodeVideo(onSuccess: (path: string) => void,
+        onError: (error: any) => void,
+        options: VideoEditorTranscodeProperties): void;
+
+    /**
+     * The VideoEditor.trim method executes asynchronously, taking a video location and trimming the beginning and end of the video
+     * and returning the full path of the trimmed video. The resulting string is passed to the onSuccess
+     * callback function specified by the onSuccess parameter.
+     * @param onSuccess Success callback function invoked with the full path of the video returned from successly saving the video
+     * @param onError Error callback function, invoked when an error occurs.
+     * @param trimOptions Trim options that are required to locate, reduce start/end and save the video.
+     */
+    trim(onSuccess: (path: string) => void,
+        onError: (error: any) => void,
+        trimOptions: VideoEditorTrimProperties): void;
+
+    /**
+    * The VideoEditor.trim method executes asynchronously, taking a video location and trimming the beginning and end of the video
+    * and returning the full path of the trimmed video. The resulting string is passed to the onSuccess
+    * callback function specified by the onSuccess parameter.
+    * @param onSuccess Success callback function invoked with the full path of the video returned from successly saving the video
+    * @param onError Error callback function, invoked when an error occurs.
+    * @param trimOptions Trim options that are required to locate, reduce start/end and save the video.
+    */
+    createThumbnail(onSuccess: (path: string) => void,
+        onError: (error: any) => void,
+        options: VideoEditorThumbnailProperties): void;
+
+    /**
+     * The VideoEditor.getVideoInfo method executes asynchronously, taking a video location and returning the details of the video.
+     * The resulting info object is passed to the onSuccess callback function specified by the onSuccess parameter.
+     * @param onSuccess Success callback function invoked with the details of the video.
+     * @param onError Error callback function, invoked when an error occurs.
+     * @param infoOptions Info options that are required to locate the video.
+     */
+    getVideoInfo(onSuccess: (info: VideoEditorVideoInfoDetails) => void,
+        onError: (error: any) => void,
+        options: VideoEditorVideoInfoOptions): void;
+}
+
+declare var VideoEditor: VideoEditor;

+ 61 - 0
miaomiao/plugins/cordova-plugin-video-editor/www/VideoEditor.js

xqd
@@ -0,0 +1,61 @@
+//
+//  VideoEditor.js
+//
+//  Created by Josh Bavari on 01-14-2014
+//  Modified by Ross Martin on 01-29-15
+//
+
+var exec = require('cordova/exec');
+var pluginName = 'VideoEditor';
+
+function VideoEditor() {}
+
+VideoEditor.prototype.transcodeVideo = function(success, error, options) {
+  var self = this;
+  var win = function(result) {
+    if (typeof result.progress !== 'undefined') {
+      if (typeof options.progress === 'function') {
+        options.progress(result.progress);
+      }
+    } else {
+      success(result);
+    }
+  };
+  exec(win, error, pluginName, 'transcodeVideo', [options]);
+};
+
+VideoEditor.prototype.trim = function(success, error, options) {
+  var self = this;
+  var win = function(result) {
+    if (typeof result.progress !== 'undefined') {
+      if (typeof options.progress === 'function') {
+        options.progress(result.progress);
+      }
+    } else {
+      success(result);
+    }
+  };
+  exec(win, error, pluginName, 'trim', [options]);
+};
+
+VideoEditor.prototype.createThumbnail = function(success, error, options) {
+  exec(success, error, pluginName, 'createThumbnail', [options]);
+};
+
+VideoEditor.prototype.getVideoInfo = function(success, error, options) {
+  exec(success, error, pluginName, 'getVideoInfo', [options]);
+};
+
+VideoEditor.prototype.execFFMPEG = function(success, error, options) {
+  var msg = 'execFFMPEG has been removed as of v1.1.0';
+  console.log(msg);
+  error(msg);
+};
+
+VideoEditor.prototype.execFFPROBE = function(success, error, options) {
+  var msg = 'ffprobe has been removed as of v1.0.9';
+  console.log(msg);
+  error(msg);
+};
+
+module.exports = new VideoEditor();

+ 21 - 0
miaomiao/plugins/cordova-plugin-video-editor/www/VideoEditorOptions.js

xqd
@@ -0,0 +1,21 @@
+//
+//  VideoEditorOptions.js
+//
+//  Created by Josh Bavari on 01-14-2014
+//  Modified by Ross Martin on 01-29-15
+//
+
+var VideoEditorOptions = {
+    OptimizeForNetworkUse: {
+        NO: 0,
+        YES: 1
+    },
+    OutputFileType: {
+        M4V: 0,
+        MPEG4: 1,
+        M4A: 2,
+        QUICK_TIME: 3
+    }
+};
+
+module.exports = VideoEditorOptions;

+ 8 - 0
miaomiao/plugins/fetch.json

xqd
@@ -150,5 +150,13 @@
         },
         "is_top_level": true,
         "variables": {}
+    },
+    "cordova-plugin-video-editor": {
+        "source": {
+            "type": "registry",
+            "id": "cordova-plugin-video-editor"
+        },
+        "is_top_level": true,
+        "variables": {}
     }
 }

+ 3 - 0
miaomiao/plugins/ios.json

xqd
@@ -38,6 +38,9 @@
         },
         "cordova-plugin-statusbar": {
             "PACKAGE_NAME": "com.miaomiao.app"
+        },
+        "cordova-plugin-video-editor": {
+            "PACKAGE_NAME": "com.miaomiao.app"
         }
     },
     "dependent_plugins": {

+ 2 - 2
miaomiao/www/js/config/config.js

xqd
@@ -1,11 +1,11 @@
 (function (app) {
     //全局配置 
     app.constant("config", {
-         // server: 'http://q8.9026.com/',
+           server: 'http://q8.9026.com/',
           // imgServer: 'http://q8.9026.com/attachment/'
         //server: 'http://localhost:8092/',
         //imgServer: 'http://localhost:8092/attachment/',
-        server: 'http://miao.beiyuesi.com/',
+       // server: 'http://miao.beiyuesi.com/',
         imgServer: 'http://miao.beiyuesi.com/attachment/'
     });
 })(angular.module('app'));

+ 21 - 2
miaomiao/www/js/controllers/account.js

xqd
@@ -1,7 +1,26 @@
 (function (app) {
-    app.controller('wechatLoginCtrl', ["$scope","userService","$ionicNavBarDelegate", "storage", "$state", "msg", "$http", "util",
-        function ($scope,userService,$ionicNavBarDelegate, storage, $state, msg, $http, util) {
+    app.controller('wechatLoginCtrl', ["$scope","userService","$ionicNavBarDelegate", "storage", "$state", "msg", "$http", "util","common",
+    function ($scope,userService,$ionicNavBarDelegate, storage, $state, msg, $http, util,common) {
             //$ionicNavBarDelegate.showBackButton(false);
+        $scope.addvideo = function () {
+                common.chooseVideo().then(function (file) {
+                    msg.loading();
+                    common.uploadFiles(file, 2).then(function (result) {
+                        msg.hide();
+                        var response = JSON.parse(result.response);
+                        $scope.video.server = response.data.file;
+                        var file = config.imgServer + response.data.file;
+                        $scope.video.isOK = true;
+                        $scope.video.file = response.data.file;
+                        $scope.video.vpic = config.server + 'upload/vpic/' + response.data.file + '.jpg';
+                    }, function (error) {
+                        //msg.error('视频上传失败');
+                        msg.hide();
+                    });
+                }, function (erro) {
+                    console.log('选择视频失败');
+                });
+            };
             $scope.wechat_login = function(){
                 var scope = "snsapi_userinfo", state = "_" + (+new Date());
                  //Wechat.auth(scope, state, function (response) {

+ 64 - 13
miaomiao/www/js/services/commonservice.js

xqd xqd xqd xqd
@@ -64,6 +64,7 @@
                     addCancelButtonWithLabel: '取消',
                     androidEnableCancelButton: true
                 };
+           
                 window.plugins.actionsheet.show(sheetOptions, function (index) {
                     switch (index) {
                         case 1: //拍摄视频
@@ -71,6 +72,7 @@
                             navigator.device.capture.captureVideo(function (videos) {
                                 if (videos[0]) {
                                     deferred.resolve(videos[0].fullPath);
+                                  
                                 } else {
                                     deferred.reject("视频未找到");
                                 }
@@ -103,15 +105,69 @@
             },
             uploadFiles: function (imageUri,type) {//通用上传,type:1图片,2视频
                 var deferred = $q.defer();
+                function guid() {
+                    function s4() {
+                        return Math.floor((1 + Math.random()) * 0x10000)
+                          .toString(16)
+                          .substring(1);
+                    }
+                    return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
+                      s4() + '-' + s4() + s4() + s4();
+                }
                 if (imageUri) {
                     var uploadOptions = new FileUploadOptions();
+                    uploadOptions.httpMethod = 'post';
+                    uploadOptions.chunkedMode = true;
+                    var token = 'Bearer ' + localStorage['token'];
+                    uploadOptions.headers = { "Authorization": token };
+                    var ft = new FileTransfer();
                     if (type==1) {//图片
                         uploadOptions.fileKey = "picture";
                         uploadOptions.fileName = "picture.jpg";
                         uploadOptions.mimeType = "image/jpeg";
                         uploadOptions.params = { tag: "dream" };
+                        ft.upload(imageUri, encodeURI(config.server + "api/attachment/upload"), function (sucess) {
+                            deferred.resolve(sucess);
+                        }, function (fail) {
+                            deferred.reject(fail);
+                        }, uploadOptions);
                     }
-                    if (type==2) {//视频
+                    if (type == 2) {//视频
+                        var videoFileName = guid();
+                        if (imageUri.indexOf('file://')==-1) {
+                            imageUri='file:/' + imageUri;
+                        }
+                        VideoEditor.transcodeVideo(
+                                  function (res) {
+                                      ft.upload(res, encodeURI(config.server + "api/attachment/upload"), function (sucess) {
+                                          deferred.resolve(sucess);
+                                          //  alert('上传成功' + JSON.stringify(sucess));
+                                      }, function (fail) {
+                                          deferred.reject(fail);
+                                          //alert('上传失败' + JSON.stringify(fail));
+                                      }, uploadOptions);
+                                  },
+                                  function (erro) {
+                                      deferred.reject(erro);
+                                  },
+                                  {
+                                      fileUri: imageUri,
+                                      outputFileName: videoFileName,
+                                      outputFileType: VideoEditorOptions.OutputFileType.MPEG4,
+                                      optimizeForNetworkUse: VideoEditorOptions.OptimizeForNetworkUse.YES,
+                                      saveToLibrary: true,
+                                      maintainAspectRatio: true,
+                                      width: 640,
+                                      height: 640,
+                                      videoBitrate: 1000000, // 1 megabit
+                                      audioChannels: 2,
+                                      audioSampleRate: 44100,
+                                      audioBitrate: 128000, // 128 kilobits
+                                      progress: function (info) {
+                                         
+                                      }
+                                  }
+                              );
                         uploadOptions.fileKey = "video";
                         uploadOptions.fileName = "video.mp4";
                         uploadOptions.mimeType = "video/mpeg";
@@ -122,19 +178,14 @@
                         uploadOptions.fileName = "avatar.jpg";
                         uploadOptions.mimeType = "image/jpeg";
                         uploadOptions.params = { tag: "avatar" };
+                        ft.upload(imageUri, encodeURI(config.server + "api/attachment/upload"), function (sucess) {
+                            deferred.resolve(sucess);
+                        }, function (fail) {
+                            deferred.reject(fail);
+                        }, uploadOptions);
                     }
-                    uploadOptions.httpMethod = 'post';
-                    uploadOptions.chunkedMode = true;
-                    var token = 'Bearer ' + localStorage['token'];
-                    uploadOptions.headers = { "Authorization": token };
-                    var ft = new FileTransfer();
-                    ft.upload(imageUri, encodeURI(config.server + "api/attachment/upload"), function (sucess) {
-                        deferred.resolve(sucess);
-                        //  alert('上传成功' + JSON.stringify(sucess));
-                    }, function (fail) {
-                        deferred.reject(fail);
-                        //alert('上传失败' + JSON.stringify(fail));
-                    }, uploadOptions);
+                  
+                  
                 } else {
                     deferred.reject('图片未找到');
                 }

+ 1 - 0
miaomiao/www/templates/account/login.html

xqd
@@ -7,5 +7,6 @@
                 <img ng-click="wechat_login()" ng-src="img/icon_wechat.svg"/>
             </div>
         </div>
+        <button ng-click="addvideo()">视频测试</button>
     </ion-content>
 </ion-view>