|
|
@@ -1,10 +1,102 @@
|
|
|
import 'dart:async';
|
|
|
+import 'dart:io';
|
|
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
+import 'package:flutter/services.dart';
|
|
|
import 'package:flutter_ffmpeg/flutter_ffmpeg.dart';
|
|
|
import 'package:flutter_ffmpeg/log_level.dart';
|
|
|
-import 'package:flutter_ffmpeg_example/flutter_ffmpeg_test_app.dart';
|
|
|
-import 'package:flutter_ffmpeg_example/video_util.dart';
|
|
|
+import 'package:path/path.dart';
|
|
|
+import 'package:path_provider/path_provider.dart';
|
|
|
+
|
|
|
+void main() => runApp(FlutterFFmpegTestApp());
|
|
|
+
|
|
|
+class FlutterFFmpegTestApp extends StatelessWidget {
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ return MaterialApp(
|
|
|
+ theme: ThemeData(
|
|
|
+ primaryColor: Color(0xFFF46842),
|
|
|
+ ),
|
|
|
+ home: MainPage(),
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class MainPage extends StatefulWidget {
|
|
|
+ @override
|
|
|
+ FlutterFFmpegTestAppState createState() => new FlutterFFmpegTestAppState();
|
|
|
+}
|
|
|
+
|
|
|
+class DecoratedTabBar extends StatelessWidget implements PreferredSizeWidget {
|
|
|
+ DecoratedTabBar({@required this.tabBar, @required this.decoration});
|
|
|
+
|
|
|
+ final TabBar tabBar;
|
|
|
+ final BoxDecoration decoration;
|
|
|
+
|
|
|
+ @override
|
|
|
+ Size get preferredSize => tabBar.preferredSize;
|
|
|
+
|
|
|
+ @override
|
|
|
+ Widget build(BuildContext context) {
|
|
|
+ return Stack(
|
|
|
+ children: [
|
|
|
+ Positioned.fill(child: Container(decoration: decoration)),
|
|
|
+ tabBar,
|
|
|
+ ],
|
|
|
+ );
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+class VideoUtil {
|
|
|
+ static Future<Directory> get tempDirectory async {
|
|
|
+ return await getTemporaryDirectory();
|
|
|
+ }
|
|
|
+
|
|
|
+ static Future<File> copyFileAssets(String assetName, String localName) async {
|
|
|
+ final ByteData assetByteData = await rootBundle.load(assetName);
|
|
|
+
|
|
|
+ final List<int> byteList = assetByteData.buffer.asUint8List(assetByteData.offsetInBytes, assetByteData.lengthInBytes);
|
|
|
+
|
|
|
+ final String fullTemporaryPath = join((await tempDirectory).path, localName);
|
|
|
+
|
|
|
+ return new File(fullTemporaryPath).writeAsBytes(byteList, mode: FileMode.writeOnly, flush: true);
|
|
|
+ }
|
|
|
+
|
|
|
+ static Future<String> assetPath(String assetName) async {
|
|
|
+ return join((await tempDirectory).path, assetName);
|
|
|
+ }
|
|
|
+
|
|
|
+ static String generateEncodeVideoScript(String image1Path, String image2Path, String image3Path, String videoFilePath, String videoCodec, String customOptions) {
|
|
|
+ return "-hide_banner -y -loop 1 -i " +
|
|
|
+ image1Path +
|
|
|
+ " " +
|
|
|
+ "-loop 1 -i " +
|
|
|
+ image2Path +
|
|
|
+ " " +
|
|
|
+ "-loop 1 -i " +
|
|
|
+ image3Path +
|
|
|
+ " " +
|
|
|
+ "-filter_complex " +
|
|
|
+ "[0:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream1out1][stream1out2];" +
|
|
|
+ "[1:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream2out1][stream2out2];" +
|
|
|
+ "[2:v]setpts=PTS-STARTPTS,scale=w=\'if(gte(iw/ih,640/427),min(iw,640),-1)\':h=\'if(gte(iw/ih,640/427),-1,min(ih,427))\',scale=trunc(iw/2)*2:trunc(ih/2)*2,setsar=sar=1/1,split=2[stream3out1][stream3out2];" +
|
|
|
+ "[stream1out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=3,select=lte(n\\,90)[stream1overlaid];" +
|
|
|
+ "[stream1out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30)[stream1ending];" +
|
|
|
+ "[stream2out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=2,select=lte(n\\,60)[stream2overlaid];" +
|
|
|
+ "[stream2out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30),split=2[stream2starting][stream2ending];" +
|
|
|
+ "[stream3out1]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=2,select=lte(n\\,60)[stream3overlaid];" +
|
|
|
+ "[stream3out2]pad=width=640:height=427:x=(640-iw)/2:y=(427-ih)/2:color=#00000000,trim=duration=1,select=lte(n\\,30)[stream3starting];" +
|
|
|
+ "[stream2starting][stream1ending]blend=all_expr=\'if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)\':shortest=1[stream2blended];" +
|
|
|
+ "[stream3starting][stream2ending]blend=all_expr=\'if(gte(X,(W/2)*T/1)*lte(X,W-(W/2)*T/1),B,A)\':shortest=1[stream3blended];" +
|
|
|
+ "[stream1overlaid][stream2blended][stream2overlaid][stream3blended][stream3overlaid]concat=n=5:v=1:a=0,scale=w=640:h=424,format=yuv420p[video]" +
|
|
|
+ " -map [video] -vsync 2 -async 1 " +
|
|
|
+ customOptions +
|
|
|
+ "-c:v " +
|
|
|
+ videoCodec +
|
|
|
+ " -r 30 " +
|
|
|
+ videoFilePath;
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
class FlutterFFmpegTestAppState extends State<MainPage> with TickerProviderStateMixin {
|
|
|
static const String ASSET_1 = "1.jpg";
|
|
|
@@ -62,8 +154,8 @@ class FlutterFFmpegTestAppState extends State<MainPage> with TickerProviderState
|
|
|
print("Testing COMMAND.");
|
|
|
|
|
|
// ENABLE LOG CALLBACK ON EACH CALL
|
|
|
- enableLogCallback(commandOutputLogCallback);
|
|
|
- enableStatisticsCallback(statisticsCallback);
|
|
|
+ _flutterFFmpeg.enableLogCallback(commandOutputLogCallback);
|
|
|
+ _flutterFFmpeg.enableStatisticsCallback(statisticsCallback);
|
|
|
|
|
|
// CLEAR OUTPUT ON EACH EXECUTION
|
|
|
_commandOutput = "";
|
|
|
@@ -95,8 +187,8 @@ class FlutterFFmpegTestAppState extends State<MainPage> with TickerProviderState
|
|
|
print("Testing Get Media Information.");
|
|
|
|
|
|
// ENABLE LOG CALLBACK ON EACH CALL
|
|
|
- enableLogCallback(commandOutputLogCallback);
|
|
|
- enableStatisticsCallback(null);
|
|
|
+ _flutterFFmpeg.enableLogCallback(commandOutputLogCallback);
|
|
|
+ _flutterFFmpeg.enableStatisticsCallback(null);
|
|
|
|
|
|
// CLEAR OUTPUT ON EACH EXECUTION
|
|
|
_commandOutput = "";
|
|
|
@@ -147,8 +239,8 @@ class FlutterFFmpegTestAppState extends State<MainPage> with TickerProviderState
|
|
|
print("Testing VIDEO.");
|
|
|
|
|
|
// ENABLE LOG CALLBACK ON EACH CALL
|
|
|
- enableLogCallback(encodeOutputLogCallback);
|
|
|
- enableStatisticsCallback(statisticsCallback);
|
|
|
+ _flutterFFmpeg.enableLogCallback(encodeOutputLogCallback);
|
|
|
+ _flutterFFmpeg.enableStatisticsCallback(statisticsCallback);
|
|
|
|
|
|
// CLEAR OUTPUT ON EACH EXECUTION
|
|
|
_encodeOutput = "";
|
|
|
@@ -267,14 +359,6 @@ class FlutterFFmpegTestAppState extends State<MainPage> with TickerProviderState
|
|
|
return await _flutterFFmpeg.disableStatistics();
|
|
|
}
|
|
|
|
|
|
- Future<void> enableLogCallback(Function(int level, String message) logCallback) async {
|
|
|
- await _flutterFFmpeg.enableLogCallback(logCallback);
|
|
|
- }
|
|
|
-
|
|
|
- Future<void> enableStatisticsCallback(Function(int time, int size, double bitrate, double speed, int videoFrameNumber, double videoQuality, double videoFps) statisticsCallback) async {
|
|
|
- await _flutterFFmpeg.enableStatisticsCallback(statisticsCallback);
|
|
|
- }
|
|
|
-
|
|
|
Future<Map<dynamic, dynamic>> getLastReceivedStatistics() async {
|
|
|
return await _flutterFFmpeg.getLastReceivedStatistics();
|
|
|
}
|