flutter_ffmpeg.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. /*
  2. * Copyright (c) 2019 Taner Sener
  3. *
  4. * This file is part of FlutterFFmpeg.
  5. *
  6. * FlutterFFmpeg is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Lesser General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * FlutterFFmpeg is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with FlutterFFmpeg. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. import 'dart:async';
  20. import 'package:flutter/services.dart';
  21. class FlutterFFmpeg {
  22. static const MethodChannel _methodChannel = const MethodChannel('flutter_ffmpeg');
  23. static const EventChannel _eventChannel = const EventChannel('flutter_ffmpeg_event');
  24. Function(int level, String message) logCallback;
  25. Function(int time, int size, double bitrate, double speed, int videoFrameNumber, double videoQuality, double videoFps) statisticsCallback;
  26. FlutterFFmpeg() {
  27. logCallback = null;
  28. statisticsCallback = null;
  29. print("Loading flutter-ffmpeg.");
  30. _eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
  31. enableLogs();
  32. enableStatistics();
  33. enableRedirection();
  34. getPlatform().then((name) => print("Loaded flutter-ffmpeg-$name."));
  35. }
  36. void _onEvent(Object event) {
  37. if (event is Map<dynamic, dynamic>) {
  38. final Map<String, dynamic> eventMap = event.cast();
  39. final Map<dynamic, dynamic> logEvent = eventMap['FlutterFFmpegLogCallback'];
  40. final Map<dynamic, dynamic> statisticsEvent = eventMap['FlutterFFmpegStatisticsCallback'];
  41. if (logEvent != null) {
  42. int level = logEvent['level'];
  43. String message = logEvent['log'];
  44. if (this.logCallback == null) {
  45. if (message.length > 0) {
  46. // PRINT ALREADY ADDS NEW LINE. SO REMOVE THIS ONE
  47. if (message.endsWith('\n')) {
  48. print(message.substring(0, message.length - 1));
  49. } else {
  50. print(message);
  51. }
  52. }
  53. } else {
  54. this.logCallback(level, message);
  55. }
  56. }
  57. if (statisticsEvent != null) {
  58. if (this.statisticsCallback != null) {
  59. int time = statisticsEvent['time'];
  60. int size = statisticsEvent['size'];
  61. double bitrate = _doublePrecision(statisticsEvent['bitrate'], 2);
  62. double speed = _doublePrecision(statisticsEvent['speed'], 2);
  63. int videoFrameNumber = statisticsEvent['videoFrameNumber'];
  64. double videoQuality = _doublePrecision(statisticsEvent['videoQuality'], 2);
  65. double videoFps = _doublePrecision(statisticsEvent['videoFps'], 2);
  66. this.statisticsCallback(time, size, bitrate, speed, videoFrameNumber, videoQuality, videoFps);
  67. }
  68. }
  69. }
  70. }
  71. void _onError(Object error) {
  72. print('Event error: $error');
  73. }
  74. double _doublePrecision(double value, int precision) {
  75. if (value == null) {
  76. return 0;
  77. } else {
  78. return num.parse(value.toStringAsFixed(precision));
  79. }
  80. }
  81. /// Returns FFmpeg version bundled within the library.
  82. Future<String> getFFmpegVersion() async {
  83. try {
  84. final Map<dynamic, dynamic> result = await _methodChannel.invokeMethod('getFFmpegVersion');
  85. return result['version'];
  86. } on PlatformException catch (e) {
  87. print("Plugin error: ${e.message}");
  88. return null;
  89. }
  90. }
  91. /// Returns platform name where library is loaded.
  92. Future<String> getPlatform() async {
  93. try {
  94. final Map<dynamic, dynamic> result = await _methodChannel.invokeMethod('getPlatform');
  95. return result['platform'];
  96. } on PlatformException catch (e) {
  97. print("Plugin error: ${e.message}");
  98. return null;
  99. }
  100. }
  101. /// Executes FFmpeg with [commandArguments] provided.
  102. Future<int> executeWithArguments(List<String> arguments) async {
  103. try {
  104. final Map<dynamic, dynamic> result = await _methodChannel.invokeMethod('executeWithArguments', {'arguments': arguments});
  105. return result['rc'];
  106. } on PlatformException catch (e) {
  107. print("Plugin error: ${e.message}");
  108. return -1;
  109. }
  110. }
  111. /// Executes FFmpeg [command] provided. Command is split into arguments using provided [delimiter].
  112. Future<int> execute(String command, [String delimiter = ' ']) async {
  113. try {
  114. final Map<dynamic, dynamic> result = await _methodChannel.invokeMethod('execute', {'command': command, 'delimiter': delimiter});
  115. return result['rc'];
  116. } on PlatformException catch (e) {
  117. print("Plugin error: ${e.message}");
  118. return -1;
  119. }
  120. }
  121. /// Cancels an ongoing operation.
  122. Future<void> cancel() async {
  123. try {
  124. await _methodChannel.invokeMethod('cancel');
  125. } on PlatformException catch (e) {
  126. print("Plugin error: ${e.message}");
  127. }
  128. }
  129. /// Enables redirection
  130. Future<void> enableRedirection() async {
  131. try {
  132. await _methodChannel.invokeMethod('enableRedirection');
  133. } on PlatformException catch (e) {
  134. print("Plugin error: ${e.message}");
  135. }
  136. }
  137. /// Disables log and statistics redirection. By default redirection is enabled in constructor.
  138. /// When redirection is enabled FFmpeg logs are printed to console and can be routed further to a callback function.
  139. /// By disabling redirection, logs are redirected to stderr.
  140. /// Statistics redirection behaviour is similar. Statistics are not printed at all if redirection is not enabled.
  141. /// If it is enabled then it is possible to define a statistics callback function but if you don't, they are not
  142. /// printed anywhere and only saved as codelastReceivedStatistics data which can be polled with
  143. /// [getLastReceivedStatistics()].
  144. Future<void> disableRedirection() async {
  145. try {
  146. await _methodChannel.invokeMethod('disableRedirection');
  147. } on PlatformException catch (e) {
  148. print("Plugin error: ${e.message}");
  149. }
  150. }
  151. /// Returns log level.
  152. Future<int> getLogLevel() async {
  153. try {
  154. final Map<dynamic, dynamic> result = await _methodChannel.invokeMethod('getLogLevel');
  155. return result['level'];
  156. } on PlatformException catch (e) {
  157. print("Plugin error: ${e.message}");
  158. return -1;
  159. }
  160. }
  161. /// Sets log level.
  162. Future<void> setLogLevel(int logLevel) async {
  163. try {
  164. await _methodChannel.invokeMethod('setLogLevel', {'level': logLevel});
  165. } on PlatformException catch (e) {
  166. print("Plugin error: ${e.message}");
  167. }
  168. }
  169. /// Enables log events
  170. Future<void> enableLogs() async {
  171. try {
  172. await _methodChannel.invokeMethod('enableLogs');
  173. } on PlatformException catch (e) {
  174. print("Plugin error: ${e.message}");
  175. }
  176. }
  177. /// Disables log functionality of the library. Logs will not be printed to console and log callback will be disabled.
  178. /// Note that log functionality is enabled by default.
  179. Future<void> disableLogs() async {
  180. try {
  181. await _methodChannel.invokeMethod('disableLogs');
  182. } on PlatformException catch (e) {
  183. print("Plugin error: ${e.message}");
  184. }
  185. }
  186. /// Enables statistics events.
  187. Future<void> enableStatistics() async {
  188. try {
  189. await _methodChannel.invokeMethod('enableStatistics');
  190. } on PlatformException catch (e) {
  191. print("Plugin error: ${e.message}");
  192. }
  193. }
  194. /// Disables statistics functionality of the library. Statistics callback will be disabled but the last received
  195. /// statistics data will be still available.
  196. /// Note that statistics functionality is enabled by default.
  197. Future<void> disableStatistics() async {
  198. try {
  199. await _methodChannel.invokeMethod('disableStatistics');
  200. } on PlatformException catch (e) {
  201. print("Plugin error: ${e.message}");
  202. }
  203. }
  204. /// Sets a callback to redirect FFmpeg logs. [newCallback] is a new log callback function, use null to disable a previously defined callback
  205. void enableLogCallback(Function(int level, String message) newCallback) {
  206. try {
  207. this.logCallback = newCallback;
  208. } on PlatformException catch (e) {
  209. print("Plugin error: ${e.message}");
  210. }
  211. }
  212. /// Sets a callback to redirect FFmpeg statistics. [newCallback] is a new statistics callback function, use null to disable a previously defined callback
  213. void enableStatisticsCallback(Function(int time, int size, double bitrate, double speed, int videoFrameNumber, double videoQuality, double videoFps) newCallback) {
  214. try {
  215. this.statisticsCallback = newCallback;
  216. } on PlatformException catch (e) {
  217. print("Plugin error: ${e.message}");
  218. }
  219. }
  220. /// Returns the last received statistics data stored in bitrate, size, speed, time, videoFps, videoFrameNumber and
  221. /// videoQuality fields
  222. Future<Map<dynamic, dynamic>> getLastReceivedStatistics() async {
  223. try {
  224. final Map<dynamic, dynamic> result = await _methodChannel.invokeMethod('getLastReceivedStatistics');
  225. return result;
  226. } on PlatformException catch (e) {
  227. print("Plugin error: ${e.message}");
  228. return null;
  229. }
  230. }
  231. /// Resets last received statistics. It is recommended to call it before starting a new execution.
  232. Future<void> resetStatistics() async {
  233. try {
  234. await _methodChannel.invokeMethod('resetStatistics');
  235. } on PlatformException catch (e) {
  236. print("Plugin error: ${e.message}");
  237. }
  238. }
  239. /// Sets and overrides fontconfig configuration directory.
  240. Future<void> setFontconfigConfigurationPath(String path) async {
  241. try {
  242. await _methodChannel.invokeMethod('setFontconfigConfigurationPath', {'path': path});
  243. } on PlatformException catch (e) {
  244. print("Plugin error: ${e.message}");
  245. }
  246. }
  247. /// Registers fonts inside the given [fontDirectory], so they are available to use in FFmpeg filters.
  248. Future<void> setFontDirectory(String fontDirectory, Map<String, String> fontNameMap) async {
  249. var parameters;
  250. if (fontNameMap == null) {
  251. parameters = {'fontDirectory': fontDirectory};
  252. } else {
  253. parameters = {'fontDirectory': fontDirectory, 'fontNameMap': fontNameMap};
  254. }
  255. try {
  256. await _methodChannel.invokeMethod('setFontDirectory', parameters);
  257. } on PlatformException catch (e) {
  258. print("Plugin error: ${e.message}");
  259. }
  260. }
  261. /// Returns FlutterFFmpeg package name.
  262. Future<String> getPackageName() async {
  263. try {
  264. final Map<dynamic, dynamic> result = await _methodChannel.invokeMethod('getPackageName');
  265. return result['packageName'];
  266. } on PlatformException catch (e) {
  267. print("Plugin error: ${e.message}");
  268. return null;
  269. }
  270. }
  271. /// Returns supported external libraries.
  272. Future<List<dynamic>> getExternalLibraries() async {
  273. try {
  274. final List<dynamic> result = await _methodChannel.invokeMethod('getExternalLibraries');
  275. return result;
  276. } on PlatformException catch (e) {
  277. print("Plugin error: ${e.message}");
  278. return null;
  279. }
  280. }
  281. /// Returns return code of last executed command.
  282. Future<int> getLastReturnCode() async {
  283. try {
  284. final Map<dynamic, dynamic> result = await _methodChannel.invokeMethod('getLastReturnCode');
  285. return result['lastRc'];
  286. } on PlatformException catch (e) {
  287. print("Plugin error: ${e.message}");
  288. return -1;
  289. }
  290. }
  291. /// Returns log output of last executed command. Please note that disabling redirection using
  292. /// [disableRedirection()] method also disables this functionality.
  293. Future<String> getLastCommandOutput() async {
  294. try {
  295. final Map<dynamic, dynamic> result = await _methodChannel.invokeMethod('getLastCommandOutput');
  296. return result['lastCommandOutput'];
  297. } on PlatformException catch (e) {
  298. print("Plugin error: ${e.message}");
  299. return null;
  300. }
  301. }
  302. /// Returns media information for given [path] using optional [timeout]
  303. Future<Map<dynamic, dynamic>> getMediaInformation(String path, [int timeout = 10000]) async {
  304. try {
  305. return await _methodChannel.invokeMethod('getMediaInformation', {'path': path, 'timeout': timeout});
  306. } on PlatformException catch (e) {
  307. print("Plugin error: ${e.message}");
  308. return null;
  309. }
  310. }
  311. }