jun7572 5 éve
szülő
commit
98a833c72c

+ 1 - 1
example/android/app/build.gradle

@@ -34,7 +34,7 @@ android {
     defaultConfig {
         // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
         applicationId "com.june.fffmpeg_example"
-        minSdkVersion 24
+        minSdkVersion 23
         targetSdkVersion 28
         versionCode flutterVersionCode.toInteger()
         versionName flutterVersionName

+ 80 - 39
example/ios/Runner/Info.plist

@@ -2,44 +2,85 @@
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
-	<key>CFBundleDevelopmentRegion</key>
-	<string>$(DEVELOPMENT_LANGUAGE)</string>
-	<key>CFBundleExecutable</key>
-	<string>$(EXECUTABLE_NAME)</string>
-	<key>CFBundleIdentifier</key>
-	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
-	<key>CFBundleInfoDictionaryVersion</key>
-	<string>6.0</string>
-	<key>CFBundleName</key>
-	<string>fffmpeg_example</string>
-	<key>CFBundlePackageType</key>
-	<string>APPL</string>
-	<key>CFBundleShortVersionString</key>
-	<string>$(FLUTTER_BUILD_NAME)</string>
-	<key>CFBundleSignature</key>
-	<string>????</string>
-	<key>CFBundleVersion</key>
-	<string>$(FLUTTER_BUILD_NUMBER)</string>
-	<key>LSRequiresIPhoneOS</key>
-	<true/>
-	<key>UILaunchStoryboardName</key>
-	<string>LaunchScreen</string>
-	<key>UIMainStoryboardFile</key>
-	<string>Main</string>
-	<key>UISupportedInterfaceOrientations</key>
-	<array>
-		<string>UIInterfaceOrientationPortrait</string>
-		<string>UIInterfaceOrientationLandscapeLeft</string>
-		<string>UIInterfaceOrientationLandscapeRight</string>
-	</array>
-	<key>UISupportedInterfaceOrientations~ipad</key>
-	<array>
-		<string>UIInterfaceOrientationPortrait</string>
-		<string>UIInterfaceOrientationPortraitUpsideDown</string>
-		<string>UIInterfaceOrientationLandscapeLeft</string>
-		<string>UIInterfaceOrientationLandscapeRight</string>
-	</array>
-	<key>UIViewControllerBasedStatusBarAppearance</key>
-	<false/>
+    <key>BGTaskSchedulerPermittedIdentifiers</key>
+    <array>
+        <string>com.i2.School.refresh</string>
+        <string>com.i2.School.cleaning_db</string>
+    </array>
+    <key>CFBundleDevelopmentRegion</key>
+    <string>$(DEVELOPMENT_LANGUAGE)</string>
+    <key>CFBundleExecutable</key>
+    <string>$(EXECUTABLE_NAME)</string>
+    <key>CFBundleIdentifier</key>
+    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+    <key>CFBundleInfoDictionaryVersion</key>
+    <string>6.0</string>
+    <key>CFBundleName</key>
+    <string>fffmpeg_example</string>
+    <key>CFBundlePackageType</key>
+    <string>APPL</string>
+    <key>CFBundleShortVersionString</key>
+    <string>$(MARKETING_VERSION)</string>
+    <key>CFBundleSignature</key>
+    <string>????</string>
+    <key>CFBundleVersion</key>
+    <string>$(CURRENT_PROJECT_VERSION)</string>
+    <key>FDMaximumConcurrentTasks</key>
+    <integer>5</integer>
+    <key>LSRequiresIPhoneOS</key>
+    <true/>
+    <key>NSAppTransportSecurity</key>
+    <dict>
+        <key>NSAllowsArbitraryLoads</key>
+        <true/>
+    </dict>
+    <key>NSAppleMusicUsageDescription</key>
+    <string>App需要您的同意,才能读取媒体资料库</string>
+    <key>NSCalendarsUsageDescription</key>
+    <string>App需要您的同意,才能使用日历</string>
+    <key>NSCameraUsageDescription</key>
+    <string>App需要您的同意,才能访问相机</string>
+    <key>NSContactsUsageDescription</key>
+    <string>App需要您的同意,才能访问通讯录</string>
+    <key>NSLocationAlwaysUsageDescription</key>
+    <string>App需要您的同意,才能始终访问位置</string>
+    <key>NSLocationUsageDescription</key>
+    <string>App需要您的同意,才能访问位置</string>
+    <key>NSLocationWhenInUseUsageDescription</key>
+    <string>App需要您的同意,才能在使用期间访问位置</string>
+    <key>NSMicrophoneUsageDescription</key>
+    <string>App需要您的同意,才能访问麦克风</string>
+    <key>NSMotionUsageDescription</key>
+    <string>App需要您的同意,才能访问运动与健身</string>
+    <key>NSPhotoLibraryAddUsageDescription</key>
+    <string>App需要您的同意,才能访问相册</string>
+    <key>NSPhotoLibraryUsageDescription</key>
+    <string>App需要您的同意,才能访问相册</string>
+    <key>NSSpeechRecognitionUsageDescription</key>
+    <string>App需要您的同意,才能使用语音识别</string>
+    <key>UILaunchStoryboardName</key>
+    <string>LaunchScreen</string>
+    <key>UIMainStoryboardFile</key>
+    <string>Main</string>
+    <key>UISupportedInterfaceOrientations</key>
+    <array>
+        <string>UIInterfaceOrientationPortrait</string>
+        <string>UIInterfaceOrientationLandscapeLeft</string>
+        <string>UIInterfaceOrientationLandscapeRight</string>
+    </array>
+    <key>UISupportedInterfaceOrientations~ipad</key>
+    <array>
+        <string>UIInterfaceOrientationPortrait</string>
+        <string>UIInterfaceOrientationPortraitUpsideDown</string>
+        <string>UIInterfaceOrientationLandscapeLeft</string>
+        <string>UIInterfaceOrientationLandscapeRight</string>
+    </array>
+    <key>UIViewControllerBasedStatusBarAppearance</key>
+    <false/>
+    <key>io.flutter.embedded_views_preview</key>
+    <true/>
+    <key>kTCCServiceMediaLibrary</key>
+    <string>是否允许此App使用音乐?</string>
 </dict>
+
 </plist>

+ 43 - 4
example/lib/main.dart

@@ -1,8 +1,12 @@
+import 'dart:io';
+
+import 'package:dio/dio.dart';
 import 'package:flutter/material.dart';
 import 'dart:async';
 
 import 'package:flutter/services.dart';
 import 'package:fffmpeg/fffmpeg.dart';
+import 'package:path_provider/path_provider.dart';
 
 void main() {
   runApp(MyApp());
@@ -20,6 +24,7 @@ class _MyAppState extends State<MyApp> {
   void initState() {
     super.initState();
     initPlatformState();
+    initss();
   }
 
   // Platform messages are asynchronous, so we initialize in an async method.
@@ -48,8 +53,29 @@ class _MyAppState extends State<MyApp> {
 
       home: Scaffold(
         floatingActionButton: FloatingActionButton(
-          onPressed: (){
-            addWaterMask(input, output).then((value) => print("========"+value.toString()));
+          onPressed: ()async{
+              String http="http://139.199.153.108:8080/test.mp4";
+              String img="http://139.199.153.108:8080/tomcat.png";
+              Directory temporaryDirectory =await getTemporaryDirectory();
+              String sss= temporaryDirectory.path+"/test.mp4";
+              String sss_img= temporaryDirectory.path+"/sss_img.png";
+              if(!await File(sss).exists()){
+                await Dio().download(http, sss,onReceiveProgress: (int2,int1){
+                  print("int===="+int2.toString()+"===="+int1.toString());
+                });
+              }
+              if(!await File(sss_img).exists()){
+                await Dio().download(img, sss_img);
+              }
+              input=sss;
+              output= temporaryDirectory.path+"/test_water4.mp4";
+              logo=sss_img;
+              print("ok");
+
+              String ss=input+","+output;
+            // addWaterMask(input, output).then((value) => print("========"+value.toString()));
+            Fffmpeg.addWatermarkToVedio(input, output, logo, WaterMarkPosition.LeftBottom);
+            // Fffmpeg.exeCommand(ss);
           },
         ),
         appBar: AppBar(
@@ -64,13 +90,26 @@ class _MyAppState extends State<MyApp> {
   static String logo="/storage/emulated/0/aa_flutter/logo.jpeg";
   static String input="/storage/emulated/0/aa_flutter/test.mp4";
   static String output="/storage/emulated/0/aa_flutter/test_water.mp4";
+  //传个图片,能设定宽高和四个角落
   static Future addWaterMask(String inputPath,String outputPath)async{
 //    await _addLogoTodisk();
 //       String s = await _getWaterMaskPath();
 //       await File(outputPath).create();
-      String command="-i "+input+" -i "+logo+" -filter_complex 'overlay=main_w-overlay_w-10:main_h-overlay_h-10' "+output;
+
       //处理完返回路径吧
-    return  await Fffmpeg.exeCommand(command);
+    return  await Fffmpeg.addWatermarkToVedio(input,output,logo,WaterMarkPosition.LeftBottom);
 
   }
+  initss()async{
+    if(Platform.isIOS){
+      Directory temporaryDirectory =await getTemporaryDirectory();
+      String sss= temporaryDirectory.path+"/test.mp4";
+      String sss_img= temporaryDirectory.path+"/sss_img.png";
+
+      input=sss;
+      output= temporaryDirectory.path+"/test_water.mp4";
+      logo=sss_img;
+      print("init_ok");
+    }
+  }
 }

+ 85 - 1
example/pubspec.lock

@@ -64,6 +64,13 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "0.1.3"
+  dio:
+    dependency: "direct main"
+    description:
+      name: dio
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.0.10"
   fffmpeg:
     dependency: "direct main"
     description:
@@ -71,6 +78,13 @@ packages:
       relative: true
     source: path
     version: "0.0.1"
+  file:
+    dependency: transitive
+    description:
+      name: file
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "5.2.1"
   flutter:
     dependency: "direct main"
     description: flutter
@@ -81,6 +95,13 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.0"
+  http_parser:
+    dependency: transitive
+    description:
+      name: http_parser
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.1.4"
   image:
     dependency: transitive
     description:
@@ -88,6 +109,13 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "2.1.12"
+  intl:
+    dependency: transitive
+    description:
+      name: intl
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.16.1"
   matcher:
     dependency: transitive
     description:
@@ -109,6 +137,34 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "1.6.4"
+  path_provider:
+    dependency: "direct main"
+    description:
+      name: path_provider
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.6.14"
+  path_provider_linux:
+    dependency: transitive
+    description:
+      name: path_provider_linux
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.0.1+2"
+  path_provider_macos:
+    dependency: transitive
+    description:
+      name: path_provider_macos
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.0.4+3"
+  path_provider_platform_interface:
+    dependency: transitive
+    description:
+      name: path_provider_platform_interface
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.3"
   petitparser:
     dependency: transitive
     description:
@@ -116,6 +172,27 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "2.4.0"
+  platform:
+    dependency: transitive
+    description:
+      name: platform
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.2.1"
+  plugin_platform_interface:
+    dependency: transitive
+    description:
+      name: plugin_platform_interface
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.0.2"
+  process:
+    dependency: transitive
+    description:
+      name: process
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.0.13"
   quiver:
     dependency: transitive
     description:
@@ -184,6 +261,13 @@ packages:
       url: "https://pub.flutter-io.cn"
     source: hosted
     version: "2.0.8"
+  xdg_directories:
+    dependency: transitive
+    description:
+      name: xdg_directories
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.1.0"
   xml:
     dependency: transitive
     description:
@@ -193,4 +277,4 @@ packages:
     version: "3.6.1"
 sdks:
   dart: ">=2.7.0 <3.0.0"
-  flutter: ">=1.10.0"
+  flutter: ">=1.12.13+hotfix.5 <2.0.0"

+ 2 - 0
example/pubspec.yaml

@@ -23,6 +23,8 @@ dependencies:
   # The following adds the Cupertino Icons font to your application.
   # Use with the CupertinoIcons class for iOS style icons.
   cupertino_icons: ^0.1.3
+  dio:
+  path_provider:
 
 dev_dependencies:
   flutter_test:

+ 59 - 22
ios/Classes/FffmpegPlugin.m

@@ -18,26 +18,30 @@
        NSDictionary* arguments = call.arguments[@"arguments"];
        NSString *input=arguments[@"inputPath"];
        NSString *output=arguments[@"outputPath"];
-       NSLog(@"=======input=======",input);
-        NSLog(@"========output======",output);
-       [self addWaterPicWithVideoPath:input outPath:output result:result];
-//      int rc = [MobileFFmpeg executeWithArguments:arguments];
-//
-//                  NSLog(@"FFmpeg exited with rc: %d\n", rc);
-//
-               
-      
-    
-
-      
-  } else {
+       NSLog(input);
+        NSLog(output);
+       //test
+//       [self addWaterPicWithVideoPath1:input outPath:output result:result];
+
+   } else if([@"addwaterMark" isEqualToString:call.method]){
+        NSDictionary* arguments = call.arguments[@"arguments"];
+         NSString *input=arguments[@"inputPath"];
+         NSString *output=arguments[@"outputPath"];
+            NSLog(input);
+            NSLog(output);
+           NSString *watermarkPath=arguments[@"watermarkPath"];
+       //LeftTop  RightTop  RightBottom  LeftBottom
+           NSString *position=arguments[@"position"];
+       [self addWaterPicWithVideoPath:input outPath:output result:result watermarkPath:watermarkPath position:position];
+       
+   }else {
     result(FlutterMethodNotImplemented);
   }
 }
 
 
 
-- (void)addWaterPicWithVideoPath:(NSString*)path outPath:(NSString*)outPath result:(FlutterResult)result
+- (void)addWaterPicWithVideoPath:(NSString*)path outPath:(NSString*)outPath result:(FlutterResult)result watermarkPath:(NSString*)watermarkPath position:(NSString*)position
 {
     //1 创建AVAsset实例
     AVURLAsset* videoAsset = [AVURLAsset assetWithURL:[NSURL fileURLWithPath:path]];
@@ -95,7 +99,7 @@
     mainCompositionInst.renderSize = CGSizeMake(renderWidth, renderHeight);
     mainCompositionInst.instructions = [NSArray arrayWithObject:mainInstruction];
     mainCompositionInst.frameDuration = CMTimeMake(1, 30);
-    [self applyVideoEffectsToComposition:mainCompositionInst size:naturalSize];
+    [self applyVideoEffectsToComposition:mainCompositionInst size:naturalSize watermarkPath:watermarkPath position:position];
 
     //    // 4 - 输出路径
     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
@@ -116,13 +120,18 @@
         dispatch_async(dispatch_get_main_queue(), ^{
 
             if( exporter.status == AVAssetExportSessionStatusCompleted ){
-
+ NSLog(@"ios-addmark-ok");
                 UISaveVideoAtPathToSavedPhotosAlbum(myPathDocs, nil, nil, nil);
+                
                         result(outPath);
+               
             }else if( exporter.status == AVAssetExportSessionStatusFailed )
             {
+                NSLog(@"not-ok");
+                NSLog(exporter.error.localizedDescription);
                    result(@"notok");
-                NSLog(@"failed");
+                
+               
             }
 
         });
@@ -136,7 +145,7 @@
  @param composition 视频的结构
  @param size 视频的尺寸
  */
-- (void)applyVideoEffectsToComposition:(AVMutableVideoComposition *)composition size:(CGSize)size
+- (void)applyVideoEffectsToComposition:(AVMutableVideoComposition *)composition size:(CGSize)size watermarkPath:(NSString*)watermarkPath position:(NSString*)position
 {
     // 文字
 //    CATextLayer *subtitle1Text = [[CATextLayer alloc] init];
@@ -146,11 +155,35 @@
 //    [subtitle1Text setString:@"ZHIMABAOBAO"];
 //    //    [subtitle1Text setAlignmentMode:kCAAlignmentCenter];
 //    [subtitle1Text setForegroundColor:[[UIColor whiteColor] CGColor]];
-
-    //图片
+    
+    NSData *imageData = [NSData dataWithContentsOfFile:watermarkPath];
+      UIImage *image2 = [UIImage imageWithData:imageData];
+//    //图片
     CALayer*picLayer = [CALayer layer];
-    picLayer.contents = (id)[UIImage imageNamed:@"watermarklogo"].CGImage;
-    picLayer.frame = CGRectMake(20, size.height-120, 100, 102);
+////    UIImage *ui11=[UIImage imageNamed:@"watermarklogo"];
+//
+    picLayer.contents = (id)image2.CGImage;
+     
+    
+//    CALayer*picLayer = [CALayer layer];
+//    picLayer.contents = (id)[UIImage imageNamed:@"watermarklogo"].CGImage;
+//    picLayer.frame = CGRectMake(20, size.height-120, 100, 102);
+
+     picLayer.contents = (id)image2.CGImage;
+     NSUInteger width= image2.size.width;
+     NSUInteger height= image2.size.height;
+    //看到时候传不传进来
+    NSInteger logoPadding=20;
+    //LeftTop  RightTop  RightBottom  LeftBottom
+    if([@"LeftTop" isEqualToString:position]){
+        picLayer.frame = CGRectMake(logoPadding, size.height-height-logoPadding, width, height);
+    }else if([@"RightTop" isEqualToString:position]){
+        picLayer.frame = CGRectMake(size.width-width-logoPadding, size.height-height-logoPadding, width, height);
+    }else if([@"RightBottom" isEqualToString:position]){
+picLayer.frame = CGRectMake(size.width-width-logoPadding, logoPadding, width, height);
+    }else if([@"LeftBottom" isEqualToString:position]){
+ picLayer.frame = CGRectMake(logoPadding, logoPadding, width, height);
+    }
     
     // 2 - The usual overlay
     CALayer *overlayLayer = [CALayer layer];
@@ -171,4 +204,8 @@
 }
 
 
+
+
+
+
 @end

+ 81 - 2
lib/fffmpeg.dart

@@ -1,9 +1,19 @@
 import 'dart:async';
 import 'dart:io';
+import 'dart:typed_data';
 
+import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
-
+import 'dart:ui' as UI;
+import 'dart:ui';
+enum WaterMarkPosition{
+  LeftTop,
+  RightTop,
+  LeftBottom,
+  RightBottom
+}
 class Fffmpeg {
+
   static const MethodChannel _channel =
       const MethodChannel('fffmpeg');
 
@@ -25,6 +35,30 @@ class Fffmpeg {
     return version;
   }
 
+  static Future<String> addWatermarkToVedio(String inputPath,String outputPath,String watermarkPath,WaterMarkPosition waterMarkPosition)async{
+    print("input=="+inputPath);
+    print("output=="+outputPath);
+    String s="";
+    String position="";
+    if(Platform.isIOS){
+      if(waterMarkPosition==WaterMarkPosition.LeftTop){
+        position="LeftTop";
+      }else if(waterMarkPosition==WaterMarkPosition.RightTop){
+        position="RightTop";
+      }else if(waterMarkPosition==WaterMarkPosition.RightBottom){
+        position="RightBottom";
+      }else if(waterMarkPosition==WaterMarkPosition.LeftBottom){
+        position="LeftBottom";
+      }
+      s = await _channel.invokeMethod('addwaterMark',{'arguments': {"inputPath":inputPath,"outputPath":outputPath,"watermarkPath":watermarkPath,"position":position}});
+      return s;
+    }else if(Platform.isAndroid){
+     s= await executeWithArguments(getCommand(inputPath, outputPath, watermarkPath, waterMarkPosition));
+      return s;
+    }
+
+  }
+
   static Future<String> executeWithArguments(List<String> arguments) async {
     if(Platform.isIOS){
       return "Platform.isIOS!!error";
@@ -38,7 +72,7 @@ class Fffmpeg {
       return "error";
     }
   }
-
+  //解析输入的命令
   static List<String> parseArguments(String command) {
     List<String> argumentList = new List();
     StringBuffer currentArgument = new StringBuffer();
@@ -91,4 +125,49 @@ class Fffmpeg {
 
     return argumentList;
   }
+
+  static List<String> getCommand(String inputPath,String outputPath,String imagesPath,WaterMarkPosition waterMarkPosition){
+    List<String> commands=List<String>(26);
+    if(waterMarkPosition==WaterMarkPosition.LeftTop){
+      commands[5] = "overlay=10:10";
+    }else if(waterMarkPosition==WaterMarkPosition.RightTop){
+     commands[5] = "overlay= main_w-overlay_w:0";
+    }else if(waterMarkPosition==WaterMarkPosition.RightBottom){
+      commands[5] = "overlay= main_w-overlay_w:main_h-overlay_h";
+    }else if(waterMarkPosition==WaterMarkPosition.LeftBottom){
+      commands[5] = "overlay=0: main_h-overlay_h";
+    }
+    commands[0] = "-i";
+    commands[1] = inputPath;
+    commands[2] = "-i";
+    commands[3] = imagesPath;
+    commands[4] = "-filter_complex";
+
+    commands[6] = "-y";
+    commands[7] = "-strict";
+    commands[8] = "-2";
+    commands[9] = "-vcodec";
+    commands[10] = "libx264";
+    commands[11] = "-preset";
+    commands[12] = "ultrafast";
+    //-crf  用于指定输出视频的质量,取值范围是0-51,默认值为23,数字越小输出视频的质量越高。
+    // 这个选项会直接影响到输出视频的码率。一般来说,压制480p我会用20左右,压制720p我会用16-18
+    commands[13] = "-crf";
+    commands[14] = "29";
+    commands[15] = "-threads";
+    commands[16] = "2";
+    commands[17] = "-acodec";
+    commands[18] = "aac";
+    commands[19] = "-ar";
+    commands[20] = "44100";
+    commands[21] = "-ac";
+    commands[22] = "2";
+    commands[23] = "-b:a";
+    commands[24] = "32k";
+    //下面两行用于设置视频大小
+//        commands[23] = "-s";
+//        commands[24] = "480x480";
+    commands[25] = outputPath;
+    return commands;
+  }
 }