diff --git a/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/FFmpegKitFlutterPlugin.java b/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/FFmpegKitFlutterPlugin.java index 3ab411a..876fa2b 100644 --- a/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/FFmpegKitFlutterPlugin.java +++ b/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/FFmpegKitFlutterPlugin.java @@ -127,11 +127,11 @@ public class FFmpegKitFlutterPlugin implements FlutterPlugin, ActivityAware, Met public static final String ARGUMENT_FFPROBE_JSON_OUTPUT = "ffprobeJsonOutput"; public static final String ARGUMENT_WRITABLE = "writable"; - private static final int asyncWriteToPipeConcurrencyLimit = 10; + private static final int asyncConcurrencyLimit = 10; private final AtomicBoolean logsEnabled; private final AtomicBoolean statisticsEnabled; - private final ExecutorService asyncWriteToPipeExecutorService; + private final ExecutorService asyncExecutorService; private MethodChannel methodChannel; private EventChannel eventChannel; @@ -147,7 +147,7 @@ public class FFmpegKitFlutterPlugin implements FlutterPlugin, ActivityAware, Met public FFmpegKitFlutterPlugin() { this.logsEnabled = new AtomicBoolean(false); this.statisticsEnabled = new AtomicBoolean(false); - this.asyncWriteToPipeExecutorService = Executors.newFixedThreadPool(asyncWriteToPipeConcurrencyLimit); + this.asyncExecutorService = Executors.newFixedThreadPool(asyncConcurrencyLimit); this.resultHandler = new FFmpegKitFlutterMethodResultHandler(); Log.d(LIBRARY_NAME, String.format("FFmpegKitFlutterPlugin created %s.", this)); @@ -457,6 +457,27 @@ public class FFmpegKitFlutterPlugin implements FlutterPlugin, ActivityAware, Met resultHandler.errorAsync(result, "INVALID_SIGNAL", "Invalid signal value."); } break; + case "ffmpegSessionExecute": + if (sessionId != null) { + ffmpegSessionExecute(sessionId, result); + } else { + resultHandler.errorAsync(result, "INVALID_SESSION", "Invalid session id."); + } + break; + case "ffprobeSessionExecute": + if (sessionId != null) { + ffprobeSessionExecute(sessionId, result); + } else { + resultHandler.errorAsync(result, "INVALID_SESSION", "Invalid session id."); + } + break; + case "mediaInformationSessionExecute": + if (sessionId != null) { + mediaInformationSessionExecute(sessionId, waitTimeout, result); + } else { + resultHandler.errorAsync(result, "INVALID_SESSION", "Invalid session id."); + } + break; case "asyncFFmpegSessionExecute": if (sessionId != null) { asyncFFmpegSessionExecute(sessionId, result); @@ -994,6 +1015,54 @@ public class FFmpegKitFlutterPlugin implements FlutterPlugin, ActivityAware, Met } } + protected void ffmpegSessionExecute(@NonNull final Integer sessionId, @NonNull final Result result) { + final Session session = FFmpegKitConfig.getSession(sessionId.longValue()); + if (session == null) { + resultHandler.errorAsync(result, "SESSION_NOT_FOUND", "Session not found."); + } else { + if (session instanceof FFmpegSession) { + final FFmpegSessionExecuteTask ffmpegSessionExecuteTask = new FFmpegSessionExecuteTask((FFmpegSession) session, resultHandler, result); + asyncExecutorService.submit(ffmpegSessionExecuteTask); + } else { + resultHandler.errorAsync(result, "NOT_FFMPEG_SESSION", "A session is found but it does not have the correct type."); + } + } + } + + protected void ffprobeSessionExecute(@NonNull final Integer sessionId, @NonNull final Result result) { + final Session session = FFmpegKitConfig.getSession(sessionId.longValue()); + if (session == null) { + resultHandler.errorAsync(result, "SESSION_NOT_FOUND", "Session not found."); + } else { + if (session instanceof FFprobeSession) { + final FFprobeSessionExecuteTask ffprobeSessionExecuteTask = new FFprobeSessionExecuteTask((FFprobeSession) session, resultHandler, result); + asyncExecutorService.submit(ffprobeSessionExecuteTask); + } else { + resultHandler.errorAsync(result, "NOT_FFPROBE_SESSION", "A session is found but it does not have the correct type."); + } + } + } + + protected void mediaInformationSessionExecute(@NonNull final Integer sessionId, @Nullable final Integer waitTimeout, @NonNull final Result result) { + final Session session = FFmpegKitConfig.getSession(sessionId.longValue()); + if (session == null) { + resultHandler.errorAsync(result, "SESSION_NOT_FOUND", "Session not found."); + } else { + if (session instanceof MediaInformationSession) { + final int timeout; + if (isValidPositiveNumber(waitTimeout)) { + timeout = waitTimeout; + } else { + timeout = AbstractSession.DEFAULT_TIMEOUT_FOR_ASYNCHRONOUS_MESSAGES_IN_TRANSMIT; + } + final MediaInformationSessionExecuteTask mediaInformationSessionExecuteTask = new MediaInformationSessionExecuteTask((MediaInformationSession) session, timeout, resultHandler, result); + asyncExecutorService.submit(mediaInformationSessionExecuteTask); + } else { + resultHandler.errorAsync(result, "NOT_MEDIA_INFORMATION_SESSION", "A session is found but it does not have the correct type."); + } + } + } + protected void asyncFFmpegSessionExecute(@NonNull final Integer sessionId, @NonNull final Result result) { final Session session = FFmpegKitConfig.getSession(sessionId.longValue()); if (session == null) { @@ -1110,8 +1179,8 @@ public class FFmpegKitFlutterPlugin implements FlutterPlugin, ActivityAware, Met } protected void writeToPipe(@NonNull final String inputPath, @NonNull final String namedPipePath, @NonNull final Result result) { - final AsyncWriteToPipeTask asyncTask = new AsyncWriteToPipeTask(inputPath, namedPipePath, resultHandler, result); - asyncWriteToPipeExecutorService.submit(asyncTask); + final WriteToPipeTask asyncTask = new WriteToPipeTask(inputPath, namedPipePath, resultHandler, result); + asyncExecutorService.submit(asyncTask); } protected void selectDocument(@NonNull final Boolean writable, @Nullable final String title, @Nullable final String type, @Nullable final String[] extraTypes, @NonNull final Result result) { diff --git a/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/FFmpegSessionExecuteTask.java b/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/FFmpegSessionExecuteTask.java new file mode 100644 index 0000000..a099cf4 --- /dev/null +++ b/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/FFmpegSessionExecuteTask.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 Taner Sener + * + * This file is part of FFmpegKit. + * + * FFmpegKit is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * FFmpegKit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FFmpegKit. If not, see . + */ + +package com.arthenica.ffmpegkit.flutter; + +import androidx.annotation.NonNull; + +import com.arthenica.ffmpegkit.FFmpegKitConfig; +import com.arthenica.ffmpegkit.FFmpegSession; + +import io.flutter.plugin.common.MethodChannel; + +public class FFmpegSessionExecuteTask implements Runnable { + private final FFmpegSession ffmpegSession; + private final FFmpegKitFlutterMethodResultHandler resultHandler; + private final MethodChannel.Result result; + + public FFmpegSessionExecuteTask(@NonNull final FFmpegSession ffmpegSession, @NonNull final FFmpegKitFlutterMethodResultHandler resultHandler, @NonNull final MethodChannel.Result result) { + this.ffmpegSession = ffmpegSession; + this.resultHandler = resultHandler; + this.result = result; + } + + @Override + public void run() { + FFmpegKitConfig.ffmpegExecute(ffmpegSession); + resultHandler.successAsync(result, null); + } +} diff --git a/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/FFprobeSessionExecuteTask.java b/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/FFprobeSessionExecuteTask.java new file mode 100644 index 0000000..dc748eb --- /dev/null +++ b/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/FFprobeSessionExecuteTask.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2021 Taner Sener + * + * This file is part of FFmpegKit. + * + * FFmpegKit is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * FFmpegKit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FFmpegKit. If not, see . + */ + +package com.arthenica.ffmpegkit.flutter; + +import androidx.annotation.NonNull; + +import com.arthenica.ffmpegkit.FFmpegKitConfig; +import com.arthenica.ffmpegkit.FFprobeSession; + +import io.flutter.plugin.common.MethodChannel; + +public class FFprobeSessionExecuteTask implements Runnable { + private final FFprobeSession ffprobeSession; + private final FFmpegKitFlutterMethodResultHandler resultHandler; + private final MethodChannel.Result result; + + public FFprobeSessionExecuteTask(@NonNull final FFprobeSession ffprobeSession, @NonNull final FFmpegKitFlutterMethodResultHandler resultHandler, @NonNull final MethodChannel.Result result) { + this.ffprobeSession = ffprobeSession; + this.resultHandler = resultHandler; + this.result = result; + } + + @Override + public void run() { + FFmpegKitConfig.ffprobeExecute(ffprobeSession); + resultHandler.successAsync(result, null); + } +} diff --git a/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/MediaInformationSessionExecuteTask.java b/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/MediaInformationSessionExecuteTask.java new file mode 100644 index 0000000..f942cd2 --- /dev/null +++ b/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/MediaInformationSessionExecuteTask.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2021 Taner Sener + * + * This file is part of FFmpegKit. + * + * FFmpegKit is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * FFmpegKit is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FFmpegKit. If not, see . + */ + +package com.arthenica.ffmpegkit.flutter; + +import androidx.annotation.NonNull; + +import com.arthenica.ffmpegkit.FFmpegKitConfig; +import com.arthenica.ffmpegkit.MediaInformationSession; + +import io.flutter.plugin.common.MethodChannel; + +public class MediaInformationSessionExecuteTask implements Runnable { + private final MediaInformationSession mediaInformationSession; + private final int timeout; + private final FFmpegKitFlutterMethodResultHandler resultHandler; + private final MethodChannel.Result result; + + public MediaInformationSessionExecuteTask(@NonNull final MediaInformationSession mediaInformationSession, final int timeout, @NonNull final FFmpegKitFlutterMethodResultHandler resultHandler, @NonNull final MethodChannel.Result result) { + this.mediaInformationSession = mediaInformationSession; + this.timeout = timeout; + this.resultHandler = resultHandler; + this.result = result; + } + + @Override + public void run() { + FFmpegKitConfig.getMediaInformationExecute(mediaInformationSession, timeout); + resultHandler.successAsync(result, null); + } +} diff --git a/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/AsyncWriteToPipeTask.java b/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/WriteToPipeTask.java similarity index 89% rename from flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/AsyncWriteToPipeTask.java rename to flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/WriteToPipeTask.java index d052738..e157437 100644 --- a/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/AsyncWriteToPipeTask.java +++ b/flutter/flutter/android/src/main/java/com/arthenica/ffmpegkit/flutter/WriteToPipeTask.java @@ -29,13 +29,13 @@ import java.io.IOException; import io.flutter.plugin.common.MethodChannel; -public class AsyncWriteToPipeTask implements Runnable { +public class WriteToPipeTask implements Runnable { private final String inputPath; private final String namedPipePath; private final FFmpegKitFlutterMethodResultHandler resultHandler; private final MethodChannel.Result result; - public AsyncWriteToPipeTask(@NonNull final String inputPath, @NonNull final String namedPipePath, @NonNull final FFmpegKitFlutterMethodResultHandler resultHandler, @NonNull final MethodChannel.Result result) { + public WriteToPipeTask(@NonNull final String inputPath, @NonNull final String namedPipePath, @NonNull final FFmpegKitFlutterMethodResultHandler resultHandler, @NonNull final MethodChannel.Result result) { this.inputPath = inputPath; this.namedPipePath = namedPipePath; this.resultHandler = resultHandler; diff --git a/flutter/flutter/ios/Classes/FFmpegKitFlutterPlugin.m b/flutter/flutter/ios/Classes/FFmpegKitFlutterPlugin.m index 37b86c6..05aeb12 100644 --- a/flutter/flutter/ios/Classes/FFmpegKitFlutterPlugin.m +++ b/flutter/flutter/ios/Classes/FFmpegKitFlutterPlugin.m @@ -71,7 +71,7 @@ extern int const AbstractSessionDefaultTimeoutForAsynchronousMessagesInTransmit; FlutterEventSink _eventSink; BOOL logsEnabled; BOOL statisticsEnabled; - dispatch_queue_t asyncWriteToPipeDispatchQueue; + dispatch_queue_t asyncDispatchQueue; } - (instancetype)init { @@ -79,7 +79,7 @@ extern int const AbstractSessionDefaultTimeoutForAsynchronousMessagesInTransmit; if (self) { logsEnabled = false; statisticsEnabled = false; - asyncWriteToPipeDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + asyncDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); NSLog(@"FFmpegKitFlutterPlugin %p created.\n", self); } @@ -301,6 +301,24 @@ extern int const AbstractSessionDefaultTimeoutForAsynchronousMessagesInTransmit; } else { result([FlutterError errorWithCode:@"INVALID_SIGNAL" message:@"Invalid signal value." details:nil]); } + } else if ([@"ffmpegSessionExecute" isEqualToString:call.method]) { + if (sessionId != nil) { + [self ffmpegSessionExecute:sessionId result:result]; + } else { + result([FlutterError errorWithCode:@"INVALID_SESSION" message:@"Invalid session id." details:nil]); + } + } else if ([@"ffprobeSessionExecute" isEqualToString:call.method]) { + if (sessionId != nil) { + [self ffprobeSessionExecute:sessionId result:result]; + } else { + result([FlutterError errorWithCode:@"INVALID_SESSION" message:@"Invalid session id." details:nil]); + } + } else if ([@"mediaInformationSessionExecute" isEqualToString:call.method]) { + if (sessionId != nil) { + [self mediaInformationSessionExecute:sessionId timeout:waitTimeout result:result]; + } else { + result([FlutterError errorWithCode:@"INVALID_SESSION" message:@"Invalid session id." details:nil]); + } } else if ([@"asyncFFmpegSessionExecute" isEqualToString:call.method]) { if (sessionId != nil) { [self asyncFFmpegSessionExecute:sessionId result:result]; @@ -710,6 +728,60 @@ extern int const AbstractSessionDefaultTimeoutForAsynchronousMessagesInTransmit; } } +- (void)ffmpegSessionExecute:(NSNumber*)sessionId result:(FlutterResult)result { + AbstractSession* session = (AbstractSession*)[FFmpegKitConfig getSession:[sessionId longValue]]; + if (session == nil) { + result([FlutterError errorWithCode:@"SESSION_NOT_FOUND" message:@"Session not found." details:nil]); + } else { + if ([session isMemberOfClass:[FFmpegSession class]]) { + dispatch_async(asyncDispatchQueue, ^{ + [FFmpegKitConfig ffmpegExecute:(FFmpegSession*)session]; + result(nil); + }); + } else { + result([FlutterError errorWithCode:@"NOT_FFMPEG_SESSION" message:@"A session is found but it does not have the correct type." details:nil]); + } + } +} + +- (void)ffprobeSessionExecute:(NSNumber*)sessionId result:(FlutterResult)result { + AbstractSession* session = (AbstractSession*)[FFmpegKitConfig getSession:[sessionId longValue]]; + if (session == nil) { + result([FlutterError errorWithCode:@"SESSION_NOT_FOUND" message:@"Session not found." details:nil]); + } else { + if ([session isMemberOfClass:[FFprobeSession class]]) { + dispatch_async(asyncDispatchQueue, ^{ + [FFmpegKitConfig ffprobeExecute:(FFprobeSession*)session]; + result(nil); + }); + } else { + result([FlutterError errorWithCode:@"NOT_FFPROBE_SESSION" message:@"A session is found but it does not have the correct type." details:nil]); + } + } +} + +- (void)mediaInformationSessionExecute:(NSNumber*)sessionId timeout:(NSNumber*)waitTimeout result:(FlutterResult)result { + AbstractSession* session = (AbstractSession*)[FFmpegKitConfig getSession:[sessionId longValue]]; + if (session == nil) { + result([FlutterError errorWithCode:@"SESSION_NOT_FOUND" message:@"Session not found." details:nil]); + } else { + if ([session isMemberOfClass:[MediaInformationSession class]]) { + int timeout; + if ([FFmpegKitFlutterPlugin isValidPositiveNumber:waitTimeout]) { + timeout = [waitTimeout intValue]; + } else { + timeout = AbstractSessionDefaultTimeoutForAsynchronousMessagesInTransmit; + } + dispatch_async(asyncDispatchQueue, ^{ + [FFmpegKitConfig getMediaInformationExecute:(MediaInformationSession*)session withTimeout:timeout]; + result(nil); + }); + } else { + result([FlutterError errorWithCode:@"NOT_MEDIA_INFORMATION_SESSION" message:@"A session is found but it does not have the correct type." details:nil]); + } + } +} + - (void)asyncFFmpegSessionExecute:(NSNumber*)sessionId result:(FlutterResult)result { AbstractSession* session = (AbstractSession*)[FFmpegKitConfig getSession:[sessionId longValue]]; if (session == nil) { @@ -854,7 +926,7 @@ extern int const AbstractSessionDefaultTimeoutForAsynchronousMessagesInTransmit; } - (void)writeToPipe:(NSString*)inputPath pipe:(NSString*)namedPipePath result:(FlutterResult)result { - dispatch_async(asyncWriteToPipeDispatchQueue, ^{ + dispatch_async(asyncDispatchQueue, ^{ NSLog(@"Starting copy %@ to pipe %@ operation.\n", inputPath, namedPipePath); diff --git a/flutter/flutter/lib/ffmpeg_kit.dart b/flutter/flutter/lib/ffmpeg_kit.dart index c19efe2..20230d1 100644 --- a/flutter/flutter/lib/ffmpeg_kit.dart +++ b/flutter/flutter/lib/ffmpeg_kit.dart @@ -31,6 +31,33 @@ import 'statistics_callback.dart'; class FFmpegKit { static FFmpegKitPlatform _platform = FFmpegKitPlatform.instance; + /// Synchronously executes FFmpeg command provided. Space character is used + /// to split command into arguments. You can use single or double quote + /// characters to specify arguments inside your command. + static Future execute(String command, + [ExecuteCallback? executeCallback = null, + LogCallback? logCallback = null, + StatisticsCallback? statisticsCallback = null]) async => + FFmpegKit.executeWithArguments( + FFmpegKitConfig.parseArguments(command), + executeCallback, + logCallback, + statisticsCallback); + + /// Synchronously executes FFmpeg with arguments provided. + static Future executeWithArguments( + List commandArguments, + [ExecuteCallback? executeCallback = null, + LogCallback? logCallback = null, + StatisticsCallback? statisticsCallback = null]) async { + final session = await FFmpegSession.create(commandArguments, + executeCallback, logCallback, statisticsCallback, null); + + await FFmpegKitConfig.ffmpegExecute(session); + + return session; + } + /// Starts an asynchronous FFmpeg execution for the given command. Space character is used to split the command /// into arguments. You can use single or double quote characters to specify arguments inside your command. /// diff --git a/flutter/flutter/lib/ffmpeg_kit_config.dart b/flutter/flutter/lib/ffmpeg_kit_config.dart index b2c037e..04bae7c 100644 --- a/flutter/flutter/lib/ffmpeg_kit_config.dart +++ b/flutter/flutter/lib/ffmpeg_kit_config.dart @@ -215,6 +215,44 @@ class FFmpegKitConfig { } } + /// Synchronously executes the FFmpeg session provided. + static Future ffmpegExecute(FFmpegSession ffmpegSession) async { + try { + await init(); + return _platform + .ffmpegKitConfigFFmpegExecute(ffmpegSession.getSessionId()); + } on PlatformException catch (e, stack) { + print("Plugin ffmpegExecute error: ${e.message}"); + return Future.error("ffmpegExecute failed.", stack); + } + } + + /// Synchronously executes the FFprobe session provided. + static Future ffprobeExecute(FFprobeSession ffprobeSession) async { + try { + await init(); + return _platform + .ffmpegKitConfigFFprobeExecute(ffprobeSession.getSessionId()); + } on PlatformException catch (e, stack) { + print("Plugin ffprobeExecute error: ${e.message}"); + return Future.error("ffprobeExecute failed.", stack); + } + } + + /// Synchronously executes the media information session provided. + static Future getMediaInformationExecute( + MediaInformationSession mediaInformationSession, + [int? waitTimeout = null]) async { + try { + await init(); + return _platform.ffmpegKitConfigGetMediaInformationExecute( + mediaInformationSession.getSessionId(), waitTimeout); + } on PlatformException catch (e, stack) { + print("Plugin getMediaInformationExecute error: ${e.message}"); + return Future.error("getMediaInformationExecute failed.", stack); + } + } + /// Starts an asynchronous FFmpeg execution for the given session. /// /// Note that this method returns immediately and does not wait the execution to complete. You must use an diff --git a/flutter/flutter/lib/ffprobe_kit.dart b/flutter/flutter/lib/ffprobe_kit.dart index 47d7e2b..5140895 100644 --- a/flutter/flutter/lib/ffprobe_kit.dart +++ b/flutter/flutter/lib/ffprobe_kit.dart @@ -31,6 +31,30 @@ import 'src/ffmpeg_kit_factory.dart'; class FFprobeKit { static FFmpegKitPlatform _platform = FFmpegKitPlatform.instance; + /// Synchronously executes FFprobe command provided. Space character is used + /// to split command into arguments. You can use single or double quote + /// characters to specify arguments inside your command. + static Future execute(String command, + [ExecuteCallback? executeCallback = null, + LogCallback? logCallback = null]) async => + FFprobeKit.executeWithArguments( + FFmpegKitConfig.parseArguments(command), + executeCallback, + logCallback); + + /// Synchronously executes FFprobe with arguments provided. + static Future executeWithArguments( + List commandArguments, + [ExecuteCallback? executeCallback = null, + LogCallback? logCallback = null]) async { + final session = await FFprobeSession.create( + commandArguments, executeCallback, logCallback, null); + + await FFmpegKitConfig.ffprobeExecute(session); + + return session; + } + /// Starts an asynchronous FFprobe execution for the given command. Space character is used to split the command /// into arguments. You can use single or double quote characters to specify arguments inside your command. /// @@ -60,6 +84,58 @@ class FFprobeKit { return session; } + /// Extracts media information for the file specified with path. + static Future getMediaInformation(String path, + [ExecuteCallback? executeCallback = null, + LogCallback? logCallback = null, + int? waitTimeout = null]) async { + final commandArguments = [ + "-v", + "error", + "-hide_banner", + "-print_format", + "json", + "-show_format", + "-show_streams", + "-show_chapters", + "-i", + path + ]; + return FFprobeKit.getMediaInformationFromCommandArguments( + commandArguments, executeCallback, logCallback, waitTimeout); + } + + /// Extracts media information using the command provided. The command + /// passed to this method must generate the output in JSON format in order to + /// successfully extract media information from it. + static Future getMediaInformationFromCommand( + String command, + [ExecuteCallback? executeCallback = null, + LogCallback? logCallback = null, + int? waitTimeout = null]) async => + FFprobeKit.getMediaInformationFromCommandArguments( + FFmpegKitConfig.parseArguments(command), + executeCallback, + logCallback, + waitTimeout); + + /// Extracts media information using the command arguments provided. The + /// command passed to this method must generate the output in JSON format in + /// order to successfully extract media information from it. + static Future + getMediaInformationFromCommandArguments( + List commandArguments, + [ExecuteCallback? executeCallback = null, + LogCallback? logCallback = null, + int? waitTimeout = null]) async { + final session = await MediaInformationSession.create( + commandArguments, executeCallback, logCallback); + + await FFmpegKitConfig.getMediaInformationExecute(session, waitTimeout); + + return session; + } + /// Starts an asynchronous FFprobe execution to extract the media information for the specified file. /// /// Note that this method returns immediately and does not wait the execution to complete. You must use an @@ -100,12 +176,14 @@ class FFprobeKit { logCallback, waitTimeout); - /// Starts an asynchronous FFprobe execution to extract media information using command arguments. The command - /// passed to this method must generate the output in JSON format in order to successfully extract media information - /// from it. + /// Starts an asynchronous FFprobe execution to extract media information + /// using command arguments. The command passed to this method must generate + /// the output in JSON format in order to successfully extract media + /// information from it. /// - /// Note that this method returns immediately and does not wait the execution to complete. You must use an - /// [ExecuteCallback] if you want to be notified about the result. + /// Note that this method returns immediately and does not wait the execution + /// to complete. You must use an [ExecuteCallback] if you want to be + /// notified about the result. static Future getMediaInformationFromCommandArgumentsAsync( List commandArguments, diff --git a/flutter/flutter/macos/Classes/FFmpegKitFlutterPlugin.m b/flutter/flutter/macos/Classes/FFmpegKitFlutterPlugin.m index 33f5269..d12bdf4 100644 --- a/flutter/flutter/macos/Classes/FFmpegKitFlutterPlugin.m +++ b/flutter/flutter/macos/Classes/FFmpegKitFlutterPlugin.m @@ -71,7 +71,7 @@ extern int const AbstractSessionDefaultTimeoutForAsynchronousMessagesInTransmit; FlutterEventSink _eventSink; BOOL logsEnabled; BOOL statisticsEnabled; - dispatch_queue_t asyncWriteToPipeDispatchQueue; + dispatch_queue_t asyncDispatchQueue; } - (instancetype)init { @@ -79,7 +79,7 @@ extern int const AbstractSessionDefaultTimeoutForAsynchronousMessagesInTransmit; if (self) { logsEnabled = false; statisticsEnabled = false; - asyncWriteToPipeDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); + asyncDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); NSLog(@"FFmpegKitFlutterPlugin %p created.\n", self); } @@ -301,6 +301,24 @@ extern int const AbstractSessionDefaultTimeoutForAsynchronousMessagesInTransmit; } else { result([FlutterError errorWithCode:@"INVALID_SIGNAL" message:@"Invalid signal value." details:nil]); } + } else if ([@"ffmpegSessionExecute" isEqualToString:call.method]) { + if (sessionId != nil) { + [self ffmpegSessionExecute:sessionId result:result]; + } else { + result([FlutterError errorWithCode:@"INVALID_SESSION" message:@"Invalid session id." details:nil]); + } + } else if ([@"ffprobeSessionExecute" isEqualToString:call.method]) { + if (sessionId != nil) { + [self ffprobeSessionExecute:sessionId result:result]; + } else { + result([FlutterError errorWithCode:@"INVALID_SESSION" message:@"Invalid session id." details:nil]); + } + } else if ([@"mediaInformationSessionExecute" isEqualToString:call.method]) { + if (sessionId != nil) { + [self mediaInformationSessionExecute:sessionId timeout:waitTimeout result:result]; + } else { + result([FlutterError errorWithCode:@"INVALID_SESSION" message:@"Invalid session id." details:nil]); + } } else if ([@"asyncFFmpegSessionExecute" isEqualToString:call.method]) { if (sessionId != nil) { [self asyncFFmpegSessionExecute:sessionId result:result]; @@ -710,6 +728,60 @@ extern int const AbstractSessionDefaultTimeoutForAsynchronousMessagesInTransmit; } } +- (void)ffmpegSessionExecute:(NSNumber*)sessionId result:(FlutterResult)result { + AbstractSession* session = (AbstractSession*)[FFmpegKitConfig getSession:[sessionId longValue]]; + if (session == nil) { + result([FlutterError errorWithCode:@"SESSION_NOT_FOUND" message:@"Session not found." details:nil]); + } else { + if ([session isMemberOfClass:[FFmpegSession class]]) { + dispatch_async(asyncDispatchQueue, ^{ + [FFmpegKitConfig ffmpegExecute:(FFmpegSession*)session]; + result(nil); + }); + } else { + result([FlutterError errorWithCode:@"NOT_FFMPEG_SESSION" message:@"A session is found but it does not have the correct type." details:nil]); + } + } +} + +- (void)ffprobeSessionExecute:(NSNumber*)sessionId result:(FlutterResult)result { + AbstractSession* session = (AbstractSession*)[FFmpegKitConfig getSession:[sessionId longValue]]; + if (session == nil) { + result([FlutterError errorWithCode:@"SESSION_NOT_FOUND" message:@"Session not found." details:nil]); + } else { + if ([session isMemberOfClass:[FFprobeSession class]]) { + dispatch_async(asyncDispatchQueue, ^{ + [FFmpegKitConfig ffprobeExecute:(FFprobeSession*)session]; + result(nil); + }); + } else { + result([FlutterError errorWithCode:@"NOT_FFPROBE_SESSION" message:@"A session is found but it does not have the correct type." details:nil]); + } + } +} + +- (void)mediaInformationSessionExecute:(NSNumber*)sessionId timeout:(NSNumber*)waitTimeout result:(FlutterResult)result { + AbstractSession* session = (AbstractSession*)[FFmpegKitConfig getSession:[sessionId longValue]]; + if (session == nil) { + result([FlutterError errorWithCode:@"SESSION_NOT_FOUND" message:@"Session not found." details:nil]); + } else { + if ([session isMemberOfClass:[MediaInformationSession class]]) { + int timeout; + if ([FFmpegKitFlutterPlugin isValidPositiveNumber:waitTimeout]) { + timeout = [waitTimeout intValue]; + } else { + timeout = AbstractSessionDefaultTimeoutForAsynchronousMessagesInTransmit; + } + dispatch_async(asyncDispatchQueue, ^{ + [FFmpegKitConfig getMediaInformationExecute:(MediaInformationSession*)session withTimeout:timeout]; + result(nil); + }); + } else { + result([FlutterError errorWithCode:@"NOT_MEDIA_INFORMATION_SESSION" message:@"A session is found but it does not have the correct type." details:nil]); + } + } +} + - (void)asyncFFmpegSessionExecute:(NSNumber*)sessionId result:(FlutterResult)result { AbstractSession* session = (AbstractSession*)[FFmpegKitConfig getSession:[sessionId longValue]]; if (session == nil) { @@ -854,7 +926,7 @@ extern int const AbstractSessionDefaultTimeoutForAsynchronousMessagesInTransmit; } - (void)writeToPipe:(NSString*)inputPath pipe:(NSString*)namedPipePath result:(FlutterResult)result { - dispatch_async(asyncWriteToPipeDispatchQueue, ^{ + dispatch_async(asyncDispatchQueue, ^{ NSLog(@"Starting copy %@ to pipe %@ operation.\n", inputPath, namedPipePath); diff --git a/flutter/flutter_platform_interface/lib/ffmpeg_kit_flutter_platform_interface.dart b/flutter/flutter_platform_interface/lib/ffmpeg_kit_flutter_platform_interface.dart index 82f50ea..daf485b 100644 --- a/flutter/flutter_platform_interface/lib/ffmpeg_kit_flutter_platform_interface.dart +++ b/flutter/flutter_platform_interface/lib/ffmpeg_kit_flutter_platform_interface.dart @@ -218,6 +218,22 @@ abstract class FFmpegKitPlatform extends PlatformInterface { ' has not been implemented!'); } + Future ffmpegKitConfigFFmpegExecute(int? sessionId) async { + throw UnimplementedError( + 'ffmpegKitConfigFFmpegExecute() has not been implemented!'); + } + + Future ffmpegKitConfigFFprobeExecute(int? sessionId) async { + throw UnimplementedError( + 'ffmpegKitConfigFFprobeExecute() has not been implemented!'); + } + + Future ffmpegKitConfigGetMediaInformationExecute( + int? sessionId, int? waitTimeout) async { + throw UnimplementedError('ffmpegKitConfigGetMediaInformationExecute()' + ' has not been implemented!'); + } + Future ffmpegKitConfigSetLogLevel(int logLevel) async { throw UnimplementedError( 'ffmpegKitConfigSetLogLevel() has not been implemented!'); diff --git a/flutter/flutter_platform_interface/lib/method_channel_ffmpeg_kit_flutter.dart b/flutter/flutter_platform_interface/lib/method_channel_ffmpeg_kit_flutter.dart index 1076405..7ec5914 100644 --- a/flutter/flutter_platform_interface/lib/method_channel_ffmpeg_kit_flutter.dart +++ b/flutter/flutter_platform_interface/lib/method_channel_ffmpeg_kit_flutter.dart @@ -179,6 +179,22 @@ class MethodChannelFFmpegKit extends FFmpegKitPlatform { Future ffmpegKitConfigIgnoreSignal(int signal) async => _channel.invokeMethod('ignoreSignal', {'signal': signal}); + @override + Future ffmpegKitConfigFFmpegExecute(int? sessionId) async => + _channel.invokeMethod( + 'ffmpegSessionExecute', {'sessionId': sessionId}); + + @override + Future ffmpegKitConfigFFprobeExecute(int? sessionId) async => + _channel.invokeMethod( + 'ffprobeSessionExecute', {'sessionId': sessionId}); + + @override + Future ffmpegKitConfigGetMediaInformationExecute( + int? sessionId, int? waitTimeout) async => + _channel.invokeMethod('mediaInformationSessionExecute', + {'sessionId': sessionId, 'waitTimeout': waitTimeout}); + @override Future ffmpegKitConfigAsyncFFmpegExecute(int? sessionId) async => _channel.invokeMethod(