diff --git a/react-native/README.md b/react-native/README.md index 87bbbd4..c178125 100644 --- a/react-native/README.md +++ b/react-native/README.md @@ -8,11 +8,11 @@ - `arm-v7a`, `arm-v7a-neon`, `arm64-v8a`, `x86` and `x86_64` architectures on Android - `Android API Level 16` or later - `armv7`, `armv7s`, `arm64`, `arm64-simulator`, `i386`, `x86_64`, `x86_64-mac-catalyst` and `arm64-mac-catalyst` architectures on iOS - - `iOS SDK 9.3` or later + - `iOS SDK 10` or later - Can process Storage Access Framework (SAF) Uris on Android - - 24 external libraries + - 25 external libraries - `dav1d`, `fontconfig`, `freetype`, `fribidi`, `gmp`, `gnutls`, `kvazaar`, `lame`, `libass`, `libiconv`, `libilbc`, `libtheora`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `opencore-amr`, `opus`, `shine`, `snappy`, `soxr`, `speex`, `twolame`, `vo-amrwbenc` + `dav1d`, `fontconfig`, `freetype`, `fribidi`, `gmp`, `gnutls`, `kvazaar`, `lame`, `libass`, `libiconv`, `libilbc`, `libtheora`, `libvorbis`, `libvpx`, `libwebp`, `libxml2`, `opencore-amr`, `opus`, `shine`, `snappy`, `soxr`, `speex`, `twolame`, `vo-amrwbenc`, `zimg` - 4 external libraries with GPL license @@ -73,7 +73,7 @@ The following table shows all package names and their respective API levels, iOS 12.1 min-lts 16 -9.3 +10 min-gpl @@ -82,7 +82,7 @@ The following table shows all package names and their respective API levels, iOS 12.1 min-gpl-lts 16 -9.3 +10 https @@ -91,7 +91,7 @@ The following table shows all package names and their respective API levels, iOS 12.1 https-lts 16 -9.3 +10 https-gpl @@ -100,7 +100,7 @@ The following table shows all package names and their respective API levels, iOS 12.1 https-gpl-lts 16 -9.3 +10 audio @@ -109,7 +109,7 @@ The following table shows all package names and their respective API levels, iOS 12.1 audio-lts 16 -9.3 +10 video @@ -118,7 +118,7 @@ The following table shows all package names and their respective API levels, iOS 12.1 video-lts 16 -9.3 +10 full @@ -127,7 +127,7 @@ The following table shows all package names and their respective API levels, iOS 12.1 full-lts 16 -9.3 +10 full-gpl @@ -136,7 +136,7 @@ The following table shows all package names and their respective API levels, iOS 12.1 full-gpl-lts 16 -9.3 +10 @@ -152,7 +152,7 @@ packages using the instructions below. - Edit `android/build.gradle` file and add the package name in `ext.ffmpegKitPackage` variable. - ``` + ```gradle ext { ffmpegKitPackage = "" } @@ -163,7 +163,7 @@ packages using the instructions below. - Edit `ios/Podfile` file and add the package name as `subspec`. After that run `pod install` again. - ``` + ```ruby pod 'ffmpeg-kit-react-native', :subspecs => [''], :podspec => '../node_modules/ffmpeg-kit-react-native/ffmpeg-kit-react-native.podspec' ``` @@ -196,7 +196,7 @@ compare to each other. ```js import { FFmpegKit } from 'ffmpeg-kit-react-native'; - FFmpegKit.executeAsync('-i file1.mp4 -c:v mpeg4 file2.mp4', async (session) => { + FFmpegKit.execute('-i file1.mp4 -c:v mpeg4 file2.mp4').then(async (session) => { const returnCode = await session.getReturnCode(); if (ReturnCode.isSuccess(returnCode)) { @@ -219,7 +219,7 @@ compare to each other. session created. ```js - FFmpegKit.executeAsync('-i file1.mp4 -c:v mpeg4 file2.mp4').then(async (session) => { + FFmpegKit.execute('-i file1.mp4 -c:v mpeg4 file2.mp4').then(async (session) => { // Unique session id created for this execution const sessionId = session.getSessionId(); @@ -276,7 +276,7 @@ compare to each other. 4. Execute `FFprobe` commands. ```js - FFprobeKit.executeAsync(ffprobeCommand, session => { + FFprobeKit.execute(ffprobeCommand).then(async (session) => { // CALLED WHEN SESSION IS EXECUTED @@ -286,8 +286,18 @@ compare to each other. 5. Get media information for a file/url. ```js - FFprobeKit.getMediaInformationAsync('', async (session) => { + FFprobeKit.getMediaInformation(testUrl).then(async (session) => { const information = await session.getMediaInformation(); + + if (information === undefined) { + + // CHECK THE FOLLOWING ATTRIBUTES ON ERROR + const state = FFmpegKitConfig.sessionStateToString(await session.getState()); + const returnCode = await session.getReturnCode(); + const failStackTrace = await session.getFailStackTrace(); + const duration = await session.getDuration(); + const output = await session.getOutput(); + } }); ``` @@ -323,7 +333,7 @@ compare to each other. }); ``` -8. Get previous `FFmpeg` and `FFprobe` sessions from the session history. +8. Get previous `FFmpeg`, `FFprobe` and `MediaInformation` sessions from the session history. ```js FFmpegKit.listSessions().then(sessionList => { @@ -332,7 +342,13 @@ compare to each other. }); }); - FFprobeKit.listSessions().then(sessionList => { + FFprobeKit.listFFprobeSessions().then(sessionList => { + sessionList.forEach(async session => { + const sessionId = session.getSessionId(); + }); + }); + + FFprobeKit.listMediaInformationSessions().then(sessionList => { sessionList.forEach(async session => { const sessionId = session.getSessionId(); }); @@ -340,10 +356,18 @@ compare to each other. ``` 9. Enable global callbacks. - - Execute Callback, called when an async execution is ended + - Session type specific Complete Callbacks, called when an async session has been completed ```js - FFmpegKitConfig.enableExecuteCallback(session => { + FFmpegKitConfig.enableFFmpegSessionCompleteCallback(session => { + const sessionId = session.getSessionId(); + }); + + FFmpegKitConfig.enableFFprobeSessionCompleteCallback(session => { + const sessionId = session.getSessionId(); + }); + + FFmpegKitConfig.enableMediaInformationSessionCompleteCallback(session => { const sessionId = session.getSessionId(); }); ``` diff --git a/react-native/android/build.gradle b/react-native/android/build.gradle index e51ec38..e509ee4 100644 --- a/react-native/android/build.gradle +++ b/react-native/android/build.gradle @@ -31,8 +31,8 @@ android { defaultConfig { minSdkVersion safeExtGet('ffmpegKitPackage', 'https').contains("-lts") ? 16 : 24 targetSdkVersion 30 - versionCode 450 - versionName "4.5.0" + versionCode 451 + versionName "4.5.1" } buildTypes { diff --git a/react-native/android/gradle.properties b/react-native/android/gradle.properties index 48aa8f1..389aa70 100644 --- a/react-native/android/gradle.properties +++ b/react-native/android/gradle.properties @@ -1,3 +1,3 @@ android.useAndroidX=true -ffmpegKit.android.main.version=4.5 -ffmpegKit.android.lts.version=4.5 +ffmpegKit.android.main.version=4.5.1-1 +ffmpegKit.android.lts.version=4.5.1-1 diff --git a/react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/FFmpegKitReactNativeModule.java b/react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/FFmpegKitReactNativeModule.java index e8c702a..7f9ce9d 100644 --- a/react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/FFmpegKitReactNativeModule.java +++ b/react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/FFmpegKitReactNativeModule.java @@ -74,7 +74,6 @@ import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.stream.Stream; public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule implements LifecycleEventListener { @@ -112,7 +111,7 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple // EVENTS public static final String EVENT_LOG_CALLBACK_EVENT = "FFmpegKitLogCallbackEvent"; public static final String EVENT_STATISTICS_CALLBACK_EVENT = "FFmpegKitStatisticsCallbackEvent"; - public static final String EVENT_EXECUTE_CALLBACK_EVENT = "FFmpegKitExecuteCallbackEvent"; + public static final String EVENT_COMPLETE_CALLBACK_EVENT = "FFmpegKitCompleteCallbackEvent"; // REQUEST CODES public static final int READABLE_REQUEST_CODE = 10000; @@ -122,14 +121,14 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple private final AtomicBoolean logsEnabled; private final AtomicBoolean statisticsEnabled; - private final ExecutorService asyncWriteToPipeExecutorService; + private final ExecutorService asyncExecutorService; public FFmpegKitReactNativeModule(@Nullable ReactApplicationContext reactContext) { super(reactContext); this.logsEnabled = new AtomicBoolean(false); this.statisticsEnabled = new AtomicBoolean(false); - this.asyncWriteToPipeExecutorService = Executors.newFixedThreadPool(asyncWriteToPipeConcurrencyLimit); + this.asyncExecutorService = Executors.newFixedThreadPool(asyncWriteToPipeConcurrencyLimit); if (reactContext != null) { reactContext.addLifecycleEventListener(this); @@ -162,13 +161,23 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple @Override public void onHostDestroy() { - this.asyncWriteToPipeExecutorService.shutdown(); + this.asyncExecutorService.shutdown(); } protected void registerGlobalCallbacks(final ReactApplicationContext reactContext) { - FFmpegKitConfig.enableExecuteCallback(session -> { + FFmpegKitConfig.enableFFmpegSessionCompleteCallback(session -> { final DeviceEventManagerModule.RCTDeviceEventEmitter jsModule = reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class); - jsModule.emit(EVENT_EXECUTE_CALLBACK_EVENT, toMap(session)); + jsModule.emit(EVENT_COMPLETE_CALLBACK_EVENT, toMap(session)); + }); + + FFmpegKitConfig.enableFFprobeSessionCompleteCallback(session -> { + final DeviceEventManagerModule.RCTDeviceEventEmitter jsModule = reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class); + jsModule.emit(EVENT_COMPLETE_CALLBACK_EVENT, toMap(session)); + }); + + FFmpegKitConfig.enableMediaInformationSessionCompleteCallback(session -> { + final DeviceEventManagerModule.RCTDeviceEventEmitter jsModule = reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class); + jsModule.emit(EVENT_COMPLETE_CALLBACK_EVENT, toMap(session)); }); FFmpegKitConfig.enableLogCallback(log -> { @@ -235,7 +244,7 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple timeout = AbstractSession.DEFAULT_TIMEOUT_FOR_ASYNCHRONOUS_MESSAGES_IN_TRANSMIT; } final List allLogs = session.getAllLogs(timeout); - promise.resolve(toArray(allLogs.stream().map(FFmpegKitReactNativeModule::toMap))); + promise.resolve(toLogArray(allLogs)); } } else { promise.reject("INVALID_SESSION", "Invalid session id."); @@ -250,7 +259,7 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple promise.reject("SESSION_NOT_FOUND", "Session not found."); } else { final List allLogs = session.getLogs(); - promise.resolve(toArray(allLogs.stream().map(FFmpegKitReactNativeModule::toMap))); + promise.resolve(toLogArray(allLogs)); } } else { promise.reject("INVALID_SESSION", "Invalid session id."); @@ -360,7 +369,7 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple if (session == null) { promise.reject("SESSION_NOT_FOUND", "Session not found."); } else { - if (session instanceof FFmpegSession) { + if (session.isFFmpeg()) { final int timeout; if (isValidPositiveNumber(waitTimeout)) { timeout = waitTimeout.intValue(); @@ -368,7 +377,7 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple timeout = AbstractSession.DEFAULT_TIMEOUT_FOR_ASYNCHRONOUS_MESSAGES_IN_TRANSMIT; } final List allStatistics = ((FFmpegSession) session).getAllStatistics(timeout); - promise.resolve(toArray(allStatistics.stream().map(FFmpegKitReactNativeModule::toMap))); + promise.resolve(toStatisticsArray(allStatistics)); } else { promise.reject("NOT_FFMPEG_SESSION", "A session is found but it does not have the correct type."); } @@ -385,9 +394,9 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple if (session == null) { promise.reject("SESSION_NOT_FOUND", "Session not found."); } else { - if (session instanceof FFmpegSession) { + if (session.isFFmpeg()) { final List statistics = ((FFmpegSession) session).getStatistics(); - promise.resolve(toArray(statistics.stream().map(FFmpegKitReactNativeModule::toMap))); + promise.resolve(toStatisticsArray(statistics)); } else { promise.reject("NOT_FFMPEG_SESSION", "A session is found but it does not have the correct type."); } @@ -574,6 +583,69 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple } } + @ReactMethod + public void ffmpegSessionExecute(final Double sessionId, final Promise promise) { + if (sessionId != null) { + Session session = FFmpegKitConfig.getSession(sessionId.longValue()); + if (session == null) { + promise.reject("SESSION_NOT_FOUND", "Session not found."); + } else { + if (session.isFFmpeg()) { + final FFmpegSessionExecuteTask ffmpegSessionExecuteTask = new FFmpegSessionExecuteTask((FFmpegSession) session, promise); + asyncExecutorService.submit(ffmpegSessionExecuteTask); + } else { + promise.reject("NOT_FFMPEG_SESSION", "A session is found but it does not have the correct type."); + } + } + } else { + promise.reject("INVALID_SESSION", "Invalid session id."); + } + } + + @ReactMethod + public void ffprobeSessionExecute(final Double sessionId, final Promise promise) { + if (sessionId != null) { + Session session = FFmpegKitConfig.getSession(sessionId.longValue()); + if (session == null) { + promise.reject("SESSION_NOT_FOUND", "Session not found."); + } else { + if (session.isFFprobe()) { + final FFprobeSessionExecuteTask ffprobeSessionExecuteTask = new FFprobeSessionExecuteTask((FFprobeSession) session, promise); + asyncExecutorService.submit(ffprobeSessionExecuteTask); + } else { + promise.reject("NOT_FFPROBE_SESSION", "A session is found but it does not have the correct type."); + } + } + } else { + promise.reject("INVALID_SESSION", "Invalid session id."); + } + } + + @ReactMethod + public void mediaInformationSessionExecute(final Double sessionId, final Double waitTimeout, final Promise promise) { + if (sessionId != null) { + Session session = FFmpegKitConfig.getSession(sessionId.longValue()); + if (session == null) { + promise.reject("SESSION_NOT_FOUND", "Session not found."); + } else { + if (session.isMediaInformation()) { + final int timeout; + if (isValidPositiveNumber(waitTimeout)) { + timeout = waitTimeout.intValue(); + } else { + timeout = AbstractSession.DEFAULT_TIMEOUT_FOR_ASYNCHRONOUS_MESSAGES_IN_TRANSMIT; + } + final MediaInformationSessionExecuteTask mediaInformationSessionExecuteTask = new MediaInformationSessionExecuteTask((MediaInformationSession) session, timeout, promise); + asyncExecutorService.submit(mediaInformationSessionExecuteTask); + } else { + promise.reject("NOT_MEDIA_INFORMATION_SESSION", "A session is found but it does not have the correct type."); + } + } + } else { + promise.reject("INVALID_SESSION", "Invalid session id."); + } + } + @ReactMethod public void asyncFFmpegSessionExecute(final Double sessionId, final Promise promise) { if (sessionId != null) { @@ -581,7 +653,7 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple if (session == null) { promise.reject("SESSION_NOT_FOUND", "Session not found."); } else { - if (session instanceof FFmpegSession) { + if (session.isFFmpeg()) { FFmpegKitConfig.asyncFFmpegExecute((FFmpegSession) session); promise.resolve(null); } else { @@ -600,7 +672,7 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple if (session == null) { promise.reject("SESSION_NOT_FOUND", "Session not found."); } else { - if (session instanceof FFprobeSession) { + if (session.isFFprobe()) { FFmpegKitConfig.asyncFFprobeExecute((FFprobeSession) session); promise.resolve(null); } else { @@ -619,7 +691,7 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple if (session == null) { promise.reject("SESSION_NOT_FOUND", "Session not found."); } else { - if (session instanceof MediaInformationSession) { + if (session.isMediaInformation()) { final int timeout; if (isValidPositiveNumber(waitTimeout)) { timeout = waitTimeout.intValue(); @@ -744,8 +816,8 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple @ReactMethod public void writeToPipe(final String inputPath, final String namedPipePath, final Promise promise) { - final AsyncWriteToPipeTask asyncTask = new AsyncWriteToPipeTask(inputPath, namedPipePath, promise); - asyncWriteToPipeExecutorService.submit(asyncTask); + final WriteToPipeTask asyncTask = new WriteToPipeTask(inputPath, namedPipePath, promise); + asyncExecutorService.submit(asyncTask); } @ReactMethod @@ -822,22 +894,18 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple } @ReactMethod - public void getSafParameter(final Boolean writable, final String uriString, final Promise promise) { + public void getSafParameter(final String uriString, final String openMode, final Promise promise) { final ReactApplicationContext reactContext = getReactApplicationContext(); final Uri uri = Uri.parse(uriString); if (uri == null) { - Log.w(LIBRARY_NAME, String.format("Cannot getSafParameter using parameters writable: %s, uriString: %s. Uri string cannot be parsed.", writable, uriString)); + Log.w(LIBRARY_NAME, String.format("Cannot getSafParameter using parameters uriString: %s, openMode: %s. Uri string cannot be parsed.", uriString, openMode)); promise.reject("GET_SAF_PARAMETER_FAILED", "Uri string cannot be parsed."); } else { final String safParameter; - if (writable) { - safParameter = FFmpegKitConfig.getSafParameterForWrite(reactContext, uri); - } else { - safParameter = FFmpegKitConfig.getSafParameterForRead(reactContext, uri); - } + safParameter = FFmpegKitConfig.getSafParameter(reactContext, uri, openMode); - Log.d(LIBRARY_NAME, String.format("getSafParameter using parameters writable: %s, uriString: %s completed with saf parameter: %s.", writable, uriString, safParameter)); + Log.d(LIBRARY_NAME, String.format("getSafParameter using parameters uriString: %s, openMode: %s completed with saf parameter: %s.", uriString, openMode, safParameter)); promise.resolve(safParameter); } @@ -870,7 +938,38 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple @ReactMethod public void getFFprobeSessions(final Promise promise) { - promise.resolve(toSessionArray(FFprobeKit.listSessions())); + promise.resolve(toSessionArray(FFprobeKit.listFFprobeSessions())); + } + + @ReactMethod + public void getMediaInformationSessions(final Promise promise) { + promise.resolve(toSessionArray(FFprobeKit.listMediaInformationSessions())); + } + + // MediaInformationSession + + @ReactMethod + public void getMediaInformation(final Double sessionId, final Promise promise) { + if (sessionId != null) { + final Session session = FFmpegKitConfig.getSession(sessionId.longValue()); + if (session == null) { + promise.reject("SESSION_NOT_FOUND", "Session not found."); + } else { + if (session.isMediaInformation()) { + final MediaInformationSession mediaInformationSession = (MediaInformationSession) session; + final MediaInformation mediaInformation = mediaInformationSession.getMediaInformation(); + if (mediaInformation != null) { + promise.resolve(toMap(mediaInformation)); + } else { + promise.resolve(null); + } + } else { + promise.reject("NOT_MEDIA_INFORMATION_SESSION", "A session is found but it does not have the correct type."); + } + } + } else { + promise.reject("INVALID_SESSION", "Invalid session id."); + } } // Packages @@ -917,19 +1016,17 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple sessionMap.putDouble(KEY_SESSION_START_TIME, toLong(session.getStartTime())); sessionMap.putString(KEY_SESSION_COMMAND, session.getCommand()); - if (session.isFFprobe()) { - if (session instanceof MediaInformationSession) { - final MediaInformationSession mediaInformationSession = (MediaInformationSession) session; - final MediaInformation mediaInformation = mediaInformationSession.getMediaInformation(); - if (mediaInformation != null) { - sessionMap.putMap(KEY_SESSION_MEDIA_INFORMATION, toMap(mediaInformation)); - } - sessionMap.putDouble(KEY_SESSION_TYPE, SESSION_TYPE_MEDIA_INFORMATION); - } else { - sessionMap.putDouble(KEY_SESSION_TYPE, SESSION_TYPE_FFPROBE); - } - } else { + if (session.isFFmpeg()) { sessionMap.putDouble(KEY_SESSION_TYPE, SESSION_TYPE_FFMPEG); + } else if (session.isFFprobe()) { + sessionMap.putDouble(KEY_SESSION_TYPE, SESSION_TYPE_FFPROBE); + } else if (session.isMediaInformation()) { + final MediaInformationSession mediaInformationSession = (MediaInformationSession) session; + final MediaInformation mediaInformation = mediaInformationSession.getMediaInformation(); + if (mediaInformation != null) { + sessionMap.putMap(KEY_SESSION_MEDIA_INFORMATION, toMap(mediaInformation)); + } + sessionMap.putDouble(KEY_SESSION_TYPE, SESSION_TYPE_MEDIA_INFORMATION); } return sessionMap; @@ -1046,16 +1143,6 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple return statisticsMap; } - protected static WritableArray toSessionArray(final List sessions) { - final WritableArray sessionArray = Arguments.createArray(); - - for (int i = 0; i < sessions.size(); i++) { - sessionArray.pushMap(toMap(sessions.get(i))); - } - - return sessionArray; - } - protected static WritableMap toMap(final MediaInformation mediaInformation) { WritableMap map = Arguments.createMap(); @@ -1102,14 +1189,6 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple return map; } - protected static WritableArray toArray(final Stream stream) { - final WritableArray list = Arguments.createArray(); - - stream.forEachOrdered(list::pushMap); - - return list; - } - protected static WritableArray toList(final JSONArray array) { final WritableArray list = Arguments.createArray(); @@ -1152,6 +1231,36 @@ public class FFmpegKitReactNativeModule extends ReactContextBaseJavaModule imple return arguments.toArray(new String[0]); } + protected static WritableArray toSessionArray(final List sessionList) { + final WritableArray sessionArray = Arguments.createArray(); + + for (int i = 0; i < sessionList.size(); i++) { + sessionArray.pushMap(toMap(sessionList.get(i))); + } + + return sessionArray; + } + + protected static WritableArray toLogArray(final List logList) { + final WritableArray logArray = Arguments.createArray(); + + for (int i = 0; i < logList.size(); i++) { + logArray.pushMap(toMap(logList.get(i))); + } + + return logArray; + } + + protected static WritableArray toStatisticsArray(final List statisticsList) { + final WritableArray statisticsArray = Arguments.createArray(); + + for (int i = 0; i < statisticsList.size(); i++) { + statisticsArray.pushMap(toMap(statisticsList.get(i))); + } + + return statisticsArray; + } + protected static boolean isValidPositiveNumber(final Double value) { return (value != null) && (value.intValue() >= 0); } diff --git a/react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/FFmpegSessionExecuteTask.java b/react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/FFmpegSessionExecuteTask.java new file mode 100644 index 0000000..2b65919 --- /dev/null +++ b/react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/FFmpegSessionExecuteTask.java @@ -0,0 +1,40 @@ +/* + * 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.reactnative; + +import com.arthenica.ffmpegkit.FFmpegKitConfig; +import com.arthenica.ffmpegkit.FFmpegSession; +import com.facebook.react.bridge.Promise; + +public class FFmpegSessionExecuteTask implements Runnable { + private final FFmpegSession ffmpegSession; + private final Promise promise; + + public FFmpegSessionExecuteTask(final FFmpegSession ffmpegSession, final Promise promise) { + this.ffmpegSession = ffmpegSession; + this.promise = promise; + } + + @Override + public void run() { + FFmpegKitConfig.ffmpegExecute(ffmpegSession); + promise.resolve(null); + } +} diff --git a/react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/FFprobeSessionExecuteTask.java b/react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/FFprobeSessionExecuteTask.java new file mode 100644 index 0000000..8cae904 --- /dev/null +++ b/react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/FFprobeSessionExecuteTask.java @@ -0,0 +1,40 @@ +/* + * 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.reactnative; + +import com.arthenica.ffmpegkit.FFmpegKitConfig; +import com.arthenica.ffmpegkit.FFprobeSession; +import com.facebook.react.bridge.Promise; + +public class FFprobeSessionExecuteTask implements Runnable { + private final FFprobeSession ffprobeSession; + private final Promise promise; + + public FFprobeSessionExecuteTask(final FFprobeSession ffprobeSession, final Promise promise) { + this.ffprobeSession = ffprobeSession; + this.promise = promise; + } + + @Override + public void run() { + FFmpegKitConfig.ffprobeExecute(ffprobeSession); + promise.resolve(null); + } +} diff --git a/react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/MediaInformationSessionExecuteTask.java b/react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/MediaInformationSessionExecuteTask.java new file mode 100644 index 0000000..b74dc87 --- /dev/null +++ b/react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/MediaInformationSessionExecuteTask.java @@ -0,0 +1,42 @@ +/* + * 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.reactnative; + +import com.arthenica.ffmpegkit.FFmpegKitConfig; +import com.arthenica.ffmpegkit.MediaInformationSession; +import com.facebook.react.bridge.Promise; + +public class MediaInformationSessionExecuteTask implements Runnable { + private final MediaInformationSession mediaInformationSession; + private final int timeout; + private final Promise promise; + + public MediaInformationSessionExecuteTask(final MediaInformationSession mediaInformationSession, final int timeout, final Promise promise) { + this.mediaInformationSession = mediaInformationSession; + this.timeout = timeout; + this.promise = promise; + } + + @Override + public void run() { + FFmpegKitConfig.getMediaInformationExecute(mediaInformationSession, timeout); + promise.resolve(null); + } +} diff --git a/react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/AsyncWriteToPipeTask.java b/react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/WriteToPipeTask.java similarity index 93% rename from react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/AsyncWriteToPipeTask.java rename to react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/WriteToPipeTask.java index a2d1079..7b247d0 100644 --- a/react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/AsyncWriteToPipeTask.java +++ b/react-native/android/src/main/java/com/arthenica/ffmpegkit/reactnative/WriteToPipeTask.java @@ -27,12 +27,12 @@ import com.facebook.react.bridge.Promise; import java.io.IOException; -public class AsyncWriteToPipeTask implements Runnable { +public class WriteToPipeTask implements Runnable { private final String inputPath; private final String namedPipePath; private final Promise promise; - public AsyncWriteToPipeTask(final String inputPath, final String namedPipePath, final Promise promise) { + public WriteToPipeTask(final String inputPath, final String namedPipePath, final Promise promise) { this.inputPath = inputPath; this.namedPipePath = namedPipePath; this.promise = promise; diff --git a/react-native/ffmpeg-kit-react-native.podspec b/react-native/ffmpeg-kit-react-native.podspec index e56aada..9df9380 100644 --- a/react-native/ffmpeg-kit-react-native.podspec +++ b/react-native/ffmpeg-kit-react-native.podspec @@ -23,113 +23,113 @@ Pod::Spec.new do |s| s.subspec 'min' do |ss| ss.source_files = '**/FFmpegKitReactNativeModule.m', '**/FFmpegKitReactNativeModule.h' - ss.dependency 'ffmpeg-kit-ios-min', "4.5" + ss.dependency 'ffmpeg-kit-ios-min', "4.5.1" ss.ios.deployment_target = '12.1' end s.subspec 'min-lts' do |ss| ss.source_files = '**/FFmpegKitReactNativeModule.m', '**/FFmpegKitReactNativeModule.h' - ss.dependency 'ffmpeg-kit-ios-min', "4.5.LTS" - ss.ios.deployment_target = '9.3' + ss.dependency 'ffmpeg-kit-ios-min', "4.5.1.LTS" + ss.ios.deployment_target = '10' end s.subspec 'min-gpl' do |ss| ss.source_files = '**/FFmpegKitReactNativeModule.m', '**/FFmpegKitReactNativeModule.h' - ss.dependency 'ffmpeg-kit-ios-min-gpl', "4.5" + ss.dependency 'ffmpeg-kit-ios-min-gpl', "4.5.1" ss.ios.deployment_target = '12.1' end s.subspec 'min-gpl-lts' do |ss| ss.source_files = '**/FFmpegKitReactNativeModule.m', '**/FFmpegKitReactNativeModule.h' - ss.dependency 'ffmpeg-kit-ios-min-gpl', "4.5.LTS" - ss.ios.deployment_target = '9.3' + ss.dependency 'ffmpeg-kit-ios-min-gpl', "4.5.1.LTS" + ss.ios.deployment_target = '10' end s.subspec 'https' do |ss| ss.source_files = '**/FFmpegKitReactNativeModule.m', '**/FFmpegKitReactNativeModule.h' - ss.dependency 'ffmpeg-kit-ios-https', "4.5" + ss.dependency 'ffmpeg-kit-ios-https', "4.5.1" ss.ios.deployment_target = '12.1' end s.subspec 'https-lts' do |ss| ss.source_files = '**/FFmpegKitReactNativeModule.m', '**/FFmpegKitReactNativeModule.h' - ss.dependency 'ffmpeg-kit-ios-https', "4.5.LTS" - ss.ios.deployment_target = '9.3' + ss.dependency 'ffmpeg-kit-ios-https', "4.5.1.LTS" + ss.ios.deployment_target = '10' end s.subspec 'https-gpl' do |ss| ss.source_files = '**/FFmpegKitReactNativeModule.m', '**/FFmpegKitReactNativeModule.h' - ss.dependency 'ffmpeg-kit-ios-https-gpl', "4.5" + ss.dependency 'ffmpeg-kit-ios-https-gpl', "4.5.1" ss.ios.deployment_target = '12.1' end s.subspec 'https-gpl-lts' do |ss| ss.source_files = '**/FFmpegKitReactNativeModule.m', '**/FFmpegKitReactNativeModule.h' - ss.dependency 'ffmpeg-kit-ios-https-gpl', "4.5.LTS" - ss.ios.deployment_target = '9.3' + ss.dependency 'ffmpeg-kit-ios-https-gpl', "4.5.1.LTS" + ss.ios.deployment_target = '10' end s.subspec 'audio' do |ss| ss.source_files = '**/FFmpegKitReactNativeModule.m', '**/FFmpegKitReactNativeModule.h' - ss.dependency 'ffmpeg-kit-ios-audio', "4.5" + ss.dependency 'ffmpeg-kit-ios-audio', "4.5.1" ss.ios.deployment_target = '12.1' end s.subspec 'audio-lts' do |ss| ss.source_files = '**/FFmpegKitReactNativeModule.m', '**/FFmpegKitReactNativeModule.h' - ss.dependency 'ffmpeg-kit-ios-audio', "4.5.LTS" - ss.ios.deployment_target = '9.3' + ss.dependency 'ffmpeg-kit-ios-audio', "4.5.1.LTS" + ss.ios.deployment_target = '10' end s.subspec 'video' do |ss| ss.source_files = '**/FFmpegKitReactNativeModule.m', '**/FFmpegKitReactNativeModule.h' - ss.dependency 'ffmpeg-kit-ios-video', "4.5" + ss.dependency 'ffmpeg-kit-ios-video', "4.5.1" ss.ios.deployment_target = '12.1' end s.subspec 'video-lts' do |ss| ss.source_files = '**/FFmpegKitReactNativeModule.m', '**/FFmpegKitReactNativeModule.h' - ss.dependency 'ffmpeg-kit-ios-video', "4.5.LTS" - ss.ios.deployment_target = '9.3' + ss.dependency 'ffmpeg-kit-ios-video', "4.5.1.LTS" + ss.ios.deployment_target = '10' end s.subspec 'full' do |ss| ss.source_files = '**/FFmpegKitReactNativeModule.m', '**/FFmpegKitReactNativeModule.h' - ss.dependency 'ffmpeg-kit-ios-full', "4.5" + ss.dependency 'ffmpeg-kit-ios-full', "4.5.1" ss.ios.deployment_target = '12.1' end s.subspec 'full-lts' do |ss| ss.source_files = '**/FFmpegKitReactNativeModule.m', '**/FFmpegKitReactNativeModule.h' - ss.dependency 'ffmpeg-kit-ios-full', "4.5.LTS" - ss.ios.deployment_target = '9.3' + ss.dependency 'ffmpeg-kit-ios-full', "4.5.1.LTS" + ss.ios.deployment_target = '10' end s.subspec 'full-gpl' do |ss| ss.source_files = '**/FFmpegKitReactNativeModule.m', '**/FFmpegKitReactNativeModule.h' - ss.dependency 'ffmpeg-kit-ios-full-gpl', "4.5" + ss.dependency 'ffmpeg-kit-ios-full-gpl', "4.5.1" ss.ios.deployment_target = '12.1' end s.subspec 'full-gpl-lts' do |ss| ss.source_files = '**/FFmpegKitReactNativeModule.m', '**/FFmpegKitReactNativeModule.h' - ss.dependency 'ffmpeg-kit-ios-full-gpl', "4.5.LTS" - ss.ios.deployment_target = '9.3' + ss.dependency 'ffmpeg-kit-ios-full-gpl', "4.5.1.LTS" + ss.ios.deployment_target = '10' end end diff --git a/react-native/ios/FFmpegKitReactNativeModule.m b/react-native/ios/FFmpegKitReactNativeModule.m index 7861877..9babaa4 100644 --- a/react-native/ios/FFmpegKitReactNativeModule.m +++ b/react-native/ios/FFmpegKitReactNativeModule.m @@ -61,14 +61,14 @@ static int const SESSION_TYPE_MEDIA_INFORMATION = 3; // EVENTS static NSString *const EVENT_LOG_CALLBACK_EVENT = @"FFmpegKitLogCallbackEvent"; static NSString *const EVENT_STATISTICS_CALLBACK_EVENT = @"FFmpegKitStatisticsCallbackEvent"; -static NSString *const EVENT_EXECUTE_CALLBACK_EVENT = @"FFmpegKitExecuteCallbackEvent"; +static NSString *const EVENT_COMPLETE_CALLBACK_EVENT = @"FFmpegKitCompleteCallbackEvent"; extern int const AbstractSessionDefaultTimeoutForAsynchronousMessagesInTransmit; @implementation FFmpegKitReactNativeModule { BOOL logsEnabled; BOOL statisticsEnabled; - dispatch_queue_t asyncWriteToPipeDispatchQueue; + dispatch_queue_t asyncDispatchQueue; } RCT_EXPORT_MODULE(FFmpegKitReactNativeModule); @@ -78,7 +78,7 @@ RCT_EXPORT_MODULE(FFmpegKitReactNativeModule); 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); [self registerGlobalCallbacks]; } @@ -91,15 +91,25 @@ RCT_EXPORT_MODULE(FFmpegKitReactNativeModule); [array addObject:EVENT_LOG_CALLBACK_EVENT]; [array addObject:EVENT_STATISTICS_CALLBACK_EVENT]; - [array addObject:EVENT_EXECUTE_CALLBACK_EVENT]; + [array addObject:EVENT_COMPLETE_CALLBACK_EVENT]; return array; } - (void)registerGlobalCallbacks { - [FFmpegKitConfig enableExecuteCallback:^(id session){ + [FFmpegKitConfig enableFFmpegSessionCompleteCallback:^(FFmpegSession* session){ NSDictionary *dictionary = [FFmpegKitReactNativeModule toSessionDictionary:session]; - [self sendEventWithName:EVENT_EXECUTE_CALLBACK_EVENT body:dictionary]; + [self sendEventWithName:EVENT_COMPLETE_CALLBACK_EVENT body:dictionary]; + }]; + + [FFmpegKitConfig enableFFprobeSessionCompleteCallback:^(FFprobeSession* session){ + NSDictionary *dictionary = [FFmpegKitReactNativeModule toSessionDictionary:session]; + [self sendEventWithName:EVENT_COMPLETE_CALLBACK_EVENT body:dictionary]; + }]; + + [FFmpegKitConfig enableMediaInformationSessionCompleteCallback:^(MediaInformationSession* session){ + NSDictionary *dictionary = [FFmpegKitReactNativeModule toSessionDictionary:session]; + [self sendEventWithName:EVENT_COMPLETE_CALLBACK_EVENT body:dictionary]; }]; [FFmpegKitConfig enableLogCallback: ^(Log* log){ @@ -234,7 +244,7 @@ RCT_EXPORT_METHOD(getArch:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRe // FFmpegSession RCT_EXPORT_METHOD(ffmpegSession:(NSArray*)arguments resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - FFmpegSession* session = [[FFmpegSession alloc] init:arguments withExecuteCallback:nil withLogCallback:nil withStatisticsCallback:nil withLogRedirectionStrategy:LogRedirectionStrategyNeverPrintLogs]; + FFmpegSession* session = [[FFmpegSession alloc] init:arguments withCompleteCallback:nil withLogCallback:nil withStatisticsCallback:nil withLogRedirectionStrategy:LogRedirectionStrategyNeverPrintLogs]; resolve([FFmpegKitReactNativeModule toSessionDictionary:session]); } @@ -243,7 +253,7 @@ RCT_EXPORT_METHOD(ffmpegSessionGetAllStatistics:(int)sessionId withTimeout:(int) if (session == nil) { reject(@"SESSION_NOT_FOUND", @"Session not found.", nil); } else { - if ([session isMemberOfClass:[FFmpegSession class]]) { + if ([session isFFmpeg]) { int timeout; if ([FFmpegKitReactNativeModule isValidPositiveNumber:waitTimeout]) { timeout = waitTimeout; @@ -263,7 +273,7 @@ RCT_EXPORT_METHOD(ffmpegSessionGetStatistics:(int)sessionId resolver:(RCTPromise if (session == nil) { reject(@"SESSION_NOT_FOUND", @"Session not found.", nil); } else { - if ([session isMemberOfClass:[FFmpegSession class]]) { + if ([session isFFmpeg]) { NSArray* statistics = [(FFmpegSession*)session getStatistics]; resolve([FFmpegKitReactNativeModule toStatisticsArray:statistics]); } else { @@ -275,14 +285,14 @@ RCT_EXPORT_METHOD(ffmpegSessionGetStatistics:(int)sessionId resolver:(RCTPromise // FFprobeSession RCT_EXPORT_METHOD(ffprobeSession:(NSArray*)arguments resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - FFprobeSession* session = [[FFprobeSession alloc] init:arguments withExecuteCallback:nil withLogCallback:nil withLogRedirectionStrategy:LogRedirectionStrategyNeverPrintLogs]; + FFprobeSession* session = [[FFprobeSession alloc] init:arguments withCompleteCallback:nil withLogCallback:nil withLogRedirectionStrategy:LogRedirectionStrategyNeverPrintLogs]; resolve([FFmpegKitReactNativeModule toSessionDictionary:session]); } // MediaInformationSession RCT_EXPORT_METHOD(mediaInformationSession:(NSArray*)arguments resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - MediaInformationSession* session = [[MediaInformationSession alloc] init:arguments withExecuteCallback:nil withLogCallback:nil]; + MediaInformationSession* session = [[MediaInformationSession alloc] init:arguments withCompleteCallback:nil withLogCallback:nil]; resolve([FFmpegKitReactNativeModule toSessionDictionary:session]); } @@ -404,12 +414,65 @@ RCT_EXPORT_METHOD(ignoreSignal:(int)signalValue resolver:(RCTPromiseResolveBlock } } +RCT_EXPORT_METHOD(ffmpegSessionExecute:(int)sessionId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { + AbstractSession* session = (AbstractSession*)[FFmpegKitConfig getSession:sessionId]; + if (session == nil) { + reject(@"SESSION_NOT_FOUND", @"Session not found.", nil); + } else { + if ([session isFFmpeg]) { + dispatch_async(asyncDispatchQueue, ^{ + [FFmpegKitConfig ffmpegExecute:(FFmpegSession*)session]; + resolve(nil); + }); + } else { + reject(@"NOT_FFMPEG_SESSION", @"A session is found but it does not have the correct type.", nil); + } + } +} + +RCT_EXPORT_METHOD(ffprobeSessionExecute:(int)sessionId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { + AbstractSession* session = (AbstractSession*)[FFmpegKitConfig getSession:sessionId]; + if (session == nil) { + reject(@"SESSION_NOT_FOUND", @"Session not found.", nil); + } else { + if ([session isFFprobe]) { + dispatch_async(asyncDispatchQueue, ^{ + [FFmpegKitConfig ffprobeExecute:(FFprobeSession*)session]; + resolve(nil); + }); + } else { + reject(@"NOT_FFPROBE_SESSION", @"A session is found but it does not have the correct type.", nil); + } + } +} + +RCT_EXPORT_METHOD(mediaInformationSessionExecute:(int)sessionId withTimeout:(int)waitTimeout resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { + AbstractSession* session = (AbstractSession*)[FFmpegKitConfig getSession:sessionId]; + if (session == nil) { + reject(@"SESSION_NOT_FOUND", @"Session not found.", nil); + } else { + if ([session isMediaInformation]) { + int timeout; + if ([FFmpegKitReactNativeModule isValidPositiveNumber:waitTimeout]) { + timeout = waitTimeout; + } else { + timeout = AbstractSessionDefaultTimeoutForAsynchronousMessagesInTransmit; + } + dispatch_async(asyncDispatchQueue, ^{ + [FFmpegKitConfig getMediaInformationExecute:(MediaInformationSession*)session withTimeout:timeout]; + resolve(nil); + }); + } else { + reject(@"NOT_MEDIA_INFORMATION_SESSION", @"A session is found but it does not have the correct type.", nil); + } + } +} RCT_EXPORT_METHOD(asyncFFmpegSessionExecute:(int)sessionId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { AbstractSession* session = (AbstractSession*)[FFmpegKitConfig getSession:sessionId]; if (session == nil) { reject(@"SESSION_NOT_FOUND", @"Session not found.", nil); } else { - if ([session isMemberOfClass:[FFmpegSession class]]) { + if ([session isFFmpeg]) { [FFmpegKitConfig asyncFFmpegExecute:(FFmpegSession*)session]; resolve(nil); } else { @@ -423,7 +486,7 @@ RCT_EXPORT_METHOD(asyncFFprobeSessionExecute:(int)sessionId resolver:(RCTPromise if (session == nil) { reject(@"SESSION_NOT_FOUND", @"Session not found.", nil); } else { - if ([session isMemberOfClass:[FFprobeSession class]]) { + if ([session isFFprobe]) { [FFmpegKitConfig asyncFFprobeExecute:(FFprobeSession*)session]; resolve(nil); } else { @@ -437,7 +500,7 @@ RCT_EXPORT_METHOD(asyncMediaInformationSessionExecute:(int)sessionId withTimeout if (session == nil) { reject(@"SESSION_NOT_FOUND", @"Session not found.", nil); } else { - if ([session isMemberOfClass:[MediaInformationSession class]]) { + if ([session isMediaInformation]) { int timeout; if ([FFmpegKitReactNativeModule isValidPositiveNumber:waitTimeout]) { timeout = waitTimeout; @@ -518,7 +581,7 @@ RCT_EXPORT_METHOD(getPlatform:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromi } RCT_EXPORT_METHOD(writeToPipe:(NSString*)inputPath onPipe:(NSString*)namedPipePath resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - dispatch_async(asyncWriteToPipeDispatchQueue, ^{ + dispatch_async(asyncDispatchQueue, ^{ NSLog(@"Starting copy %@ to pipe %@ operation.\n", inputPath, namedPipePath); @@ -574,7 +637,7 @@ RCT_EXPORT_METHOD(selectDocument:(BOOL)writable title:(NSString*)title type:(NSS reject(@"Not Supported", @"Not supported on iOS platform.", nil); } -RCT_EXPORT_METHOD(getSafParameter:(BOOL)writable uri:(NSString*)uriString resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { +RCT_EXPORT_METHOD(getSafParameter:(NSString*)uriString mode:(NSString*)openMode resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { reject(@"Not Supported", @"Not supported on iOS platform.", nil); } @@ -599,7 +662,27 @@ RCT_EXPORT_METHOD(getFFmpegSessions:(RCTPromiseResolveBlock)resolve rejecter:(RC // FFprobeKit RCT_EXPORT_METHOD(getFFprobeSessions:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { - resolve([FFmpegKitReactNativeModule toSessionArray:[FFprobeKit listSessions]]); + resolve([FFmpegKitReactNativeModule toSessionArray:[FFprobeKit listFFprobeSessions]]); +} + +RCT_EXPORT_METHOD(getMediaInformationSessions:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { + resolve([FFmpegKitReactNativeModule toSessionArray:[FFprobeKit listMediaInformationSessions]]); +} + +// MediaInformationSession + +RCT_EXPORT_METHOD(getMediaInformation:(int)sessionId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { + AbstractSession* session = (AbstractSession*)[FFmpegKitConfig getSession:sessionId]; + if (session == nil) { + reject(@"SESSION_NOT_FOUND", @"Session not found.", nil); + } else { + if ([session isMediaInformation]) { + MediaInformationSession *mediaInformationSession = (MediaInformationSession*)session; + resolve([FFmpegKitReactNativeModule toMediaInformationDictionary:[mediaInformationSession getMediaInformation]]); + } else { + reject(@"NOT_MEDIA_INFORMATION_SESSION", @"A session is found but it does not have the correct type.", nil); + } + } } // Packages @@ -628,6 +711,10 @@ RCT_EXPORT_METHOD(getExternalLibraries:(RCTPromiseResolveBlock)resolve rejecter: statisticsEnabled = false; } ++ (BOOL)requiresMainQueueSetup { + return NO; +} + + (NSDictionary*)toSessionDictionary:(id) session { if (session != nil) { NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init]; @@ -638,7 +725,7 @@ RCT_EXPORT_METHOD(getExternalLibraries:(RCTPromiseResolveBlock)resolve rejecter: dictionary[KEY_SESSION_COMMAND] = [session getCommand]; if ([session isFFprobe]) { - if ([(AbstractSession*)session isMemberOfClass:[MediaInformationSession class]]) { + if ([session isMediaInformation]) { MediaInformationSession *mediaInformationSession = (MediaInformationSession*)session; dictionary[KEY_SESSION_MEDIA_INFORMATION] = [FFmpegKitReactNativeModule toMediaInformationDictionary:[mediaInformationSession getMediaInformation]]; dictionary[KEY_SESSION_TYPE] = [NSNumber numberWithInt:SESSION_TYPE_MEDIA_INFORMATION]; diff --git a/react-native/package.json b/react-native/package.json index 0d46320..7e72211 100644 --- a/react-native/package.json +++ b/react-native/package.json @@ -1,22 +1,11 @@ { "name": "ffmpeg-kit-react-native", - "version": "4.5.0", + "version": "4.5.1", "description": "FFmpeg Kit for React Native", "main": "src/index", "types": "src/index.d.ts", "react-native": "src/index", "source": "src/index", - "files": [ - "android", - "ffmpeg-kit-react-native.podspec", - "ios", - "src", - "!android/build", - "!ios/build", - "!**/__tests__", - "!**/__fixtures__", - "!**/__mocks__" - ], "scripts": { "test": "jest", "lint": "eslint \"**/*.{js,ts,tsx}\"", diff --git a/react-native/src/index.d.ts b/react-native/src/index.d.ts index 348362e..5836754 100644 --- a/react-native/src/index.d.ts +++ b/react-native/src/index.d.ts @@ -16,8 +16,6 @@ declare module 'ffmpeg-kit-react-native' { static createMediaInformationSessionFromMap(sessionMap: { [key: string]: any }): MediaInformationSession; - getExecuteCallback(): ExecuteCallback; - getLogCallback(): LogCallback; getSessionId(): number; @@ -58,6 +56,8 @@ declare module 'ffmpeg-kit-react-native' { isFFprobe(): boolean; + isMediaInformation(): boolean; + cancel(): Promise; } @@ -68,13 +68,19 @@ declare module 'ffmpeg-kit-react-native' { } - export type ExecuteCallback = (session: Session) => void; + export type FFmpegSessionCompleteCallback = (session: FFmpegSession) => void; + export type FFprobeSessionCompleteCallback = (session: FFprobeSession) => void; + export type MediaInformationSessionCompleteCallback = (session: MediaInformationSession) => void; export class FFmpegKit { - static executeAsync(command: string, executeCallback?: ExecuteCallback, logCallback?: LogCallback, statisticsCallback?: StatisticsCallback): Promise; + static execute(command: string): Promise; + + static executeWithArguments(commandArguments: string[]): Promise; - static executeWithArgumentsAsync(commandArguments: string[], executeCallback?: ExecuteCallback, logCallback?: LogCallback, statisticsCallback?: StatisticsCallback): Promise; + static executeAsync(command: string, completeCallback?: FFmpegSessionCompleteCallback, logCallback?: LogCallback, statisticsCallback?: StatisticsCallback): Promise; + + static executeWithArgumentsAsync(commandArguments: string[], completeCallback?: FFmpegSessionCompleteCallback, logCallback?: LogCallback, statisticsCallback?: StatisticsCallback): Promise; static cancel(sessionId?: number): Promise; @@ -112,6 +118,12 @@ declare module 'ffmpeg-kit-react-native' { static ignoreSignal(signal: Signal): Promise; + static ffmpegExecute(session: FFmpegSession): Promise; + + static ffprobeExecute(session: FFprobeSession): Promise; + + static getMediaInformationExecute(session: MediaInformationSession, waitTimeout?: number): Promise; + static asyncFFmpegExecute(session: FFmpegSession): Promise; static asyncFFprobeExecute(session: FFprobeSession): Promise; @@ -122,12 +134,28 @@ declare module 'ffmpeg-kit-react-native' { static enableStatisticsCallback(statisticsCallback: StatisticsCallback): void; - static enableExecuteCallback(executeCallback: ExecuteCallback): void; + static enableFFmpegSessionCompleteCallback(completeCallback: FFmpegSessionCompleteCallback): void; + + static getFFmpegSessionCompleteCallback(): FFmpegSessionCompleteCallback; + + static enableFFprobeSessionCompleteCallback(completeCallback: FFprobeSessionCompleteCallback): void; + + static getFFprobeSessionCompleteCallback(): FFprobeSessionCompleteCallback; + + static enableMediaInformationSessionCompleteCallback(completeCallback: MediaInformationSessionCompleteCallback): void; + + static getMediaInformationSessionCompleteCallback(): MediaInformationSessionCompleteCallback; static getLogLevel(): Level; static setLogLevel(level: Level): Promise; + static getSafParameterForRead(uriString: String): Promise; + + static getSafParameterForWrite(uriString: String): Promise; + + static getSafParameter(uriString: String, openMode: String): Promise; + static getSessionHistorySize(): Promise; static setSessionHistorySize(sessionHistorySize: number): Promise; @@ -142,6 +170,12 @@ declare module 'ffmpeg-kit-react-native' { static clearSessions(): Promise; + static getFFmpegSessions(): Promise; + + static getFFprobeSessions(): Promise; + + static getMediaInformationSessions(): Promise; + static getSessionsByState(state): Promise; static getLogRedirectionStrategy(): LogRedirectionStrategy; @@ -172,22 +206,20 @@ declare module 'ffmpeg-kit-react-native' { static selectDocumentForWrite(title?: string, type?: string, extraTypes?: string[]): Promise; - static getSafParameterForRead(uriString): Promise; - - static getSafParameterForWrite(uriString): Promise; - } export class FFmpegSession extends AbstractSession implements Session { constructor(); - static create(argumentsArray: Array, executeCallback?: ExecuteCallback, logCallback?: LogCallback, statisticsCallback?: StatisticsCallback, logRedirectionStrategy?: LogRedirectionStrategy): Promise; + static create(argumentsArray: Array, completeCallback?: FFmpegSessionCompleteCallback, logCallback?: LogCallback, statisticsCallback?: StatisticsCallback, logRedirectionStrategy?: LogRedirectionStrategy): Promise; static fromMap(sessionMap: { [key: string]: any }): FFmpegSession; getStatisticsCallback(): StatisticsCallback; + getCompleteCallback(): FFmpegSessionCompleteCallback; + getAllStatistics(waitTimeout?: number): Promise>; getStatistics(): Promise>; @@ -198,21 +230,35 @@ declare module 'ffmpeg-kit-react-native' { isFFprobe(): boolean; + isMediaInformation(): boolean; + } export class FFprobeKit { - static executeAsync(command: string, executeCallback?: ExecuteCallback, logCallback?: LogCallback): Promise; + static execute(command: string): Promise; + + static executeWithArguments(commandArguments: string[]): Promise; + + static executeAsync(command: string, completeCallback?: FFprobeSessionCompleteCallback, logCallback?: LogCallback): Promise; + + static executeWithArgumentsAsync(commandArguments: string[], completeCallback?: FFprobeSessionCompleteCallback, logCallback?: LogCallback): Promise; - static executeWithArgumentsAsync(commandArguments: string[], executeCallback?: ExecuteCallback, logCallback?: LogCallback): Promise; + static getMediaInformation(path: string, waitTimeout?: number): Promise; - static getMediaInformationAsync(path: string, executeCallback?: ExecuteCallback, logCallback?: LogCallback, waitTimeout?: number): Promise; + static getMediaInformationFromCommand(command: string, waitTimeout?: number): Promise; - static getMediaInformationFromCommandAsync(command: string, executeCallback?: ExecuteCallback, logCallback?: LogCallback, waitTimeout?: number): Promise; + static getMediaInformationFromCommandArguments(commandArguments: string[], waitTimeout?: number): Promise; - static getMediaInformationFromCommandArgumentsAsync(commandArguments: string[], executeCallback?: ExecuteCallback, logCallback?: LogCallback, waitTimeout?: number): Promise; + static getMediaInformationAsync(path: string, completeCallback?: FFprobeSessionCompleteCallback, logCallback?: LogCallback, waitTimeout?: number): Promise; - static listSessions(): Promise; + static getMediaInformationFromCommandAsync(command: string, completeCallback?: FFprobeSessionCompleteCallback, logCallback?: LogCallback, waitTimeout?: number): Promise; + + static getMediaInformationFromCommandArgumentsAsync(commandArguments: string[], completeCallback?: FFprobeSessionCompleteCallback, logCallback?: LogCallback, waitTimeout?: number): Promise; + + static listFFprobeSessions(): Promise; + + static listMediaInformationSessions(): Promise; } @@ -220,14 +266,18 @@ declare module 'ffmpeg-kit-react-native' { constructor(); - static create(argumentsArray: Array, executeCallback?: ExecuteCallback, logCallback?: LogCallback, logRedirectionStrategy?: LogRedirectionStrategy): Promise; + static create(argumentsArray: Array, completeCallback?: FFprobeSessionCompleteCallback, logCallback?: LogCallback, logRedirectionStrategy?: LogRedirectionStrategy): Promise; static fromMap(sessionMap: { [key: string]: any }): FFprobeSession; + getCompleteCallback(): FFprobeSessionCompleteCallback; + isFFmpeg(): boolean; isFFprobe(): boolean; + isMediaInformation(): boolean; + } export class Level { @@ -299,6 +349,8 @@ declare module 'ffmpeg-kit-react-native' { getStreams(): Array; + getChapters(): Array; + getStringProperty(key: string): string; getNumberProperty(key: string): number; @@ -319,11 +371,11 @@ declare module 'ffmpeg-kit-react-native' { } - export class MediaInformationSession extends FFprobeSession { + export class MediaInformationSession extends AbstractSession implements Session { constructor(); - static create(argumentsArray: Array, executeCallback?: ExecuteCallback, logCallback?: LogCallback): Promise; + static create(argumentsArray: Array, completeCallback?: MediaInformationSessionCompleteCallback, logCallback?: LogCallback): Promise; static fromMap(sessionMap: { [key: string]: any }): MediaInformationSession; @@ -331,6 +383,14 @@ declare module 'ffmpeg-kit-react-native' { setMediaInformation(mediaInformation: MediaInformation): void; + getCompleteCallback(): MediaInformationSessionCompleteCallback; + + isFFmpeg(): boolean; + + isFFprobe(): boolean; + + isMediaInformation(): boolean; + } export class Packages { @@ -365,8 +425,6 @@ declare module 'ffmpeg-kit-react-native' { export interface Session { - getExecuteCallback(): ExecuteCallback; - getLogCallback(): LogCallback; getSessionId(): number; @@ -407,6 +465,8 @@ declare module 'ffmpeg-kit-react-native' { isFFprobe(): boolean; + isMediaInformation(): boolean; + cancel(): Promise; } @@ -535,4 +595,40 @@ declare module 'ffmpeg-kit-react-native' { } + export class Chapter { + + static readonly KEY_ID: string; + static readonly KEY_TIME_BASE: string; + static readonly KEY_START: string; + static readonly KEY_START_TIME: string; + static readonly KEY_END: string; + static readonly KEY_END_TIME: string; + static readonly KEY_TAGS: string; + + constructor(properties: Record); + + getId(): number; + + getTimeBase(): string; + + getStart(): number; + + getStartTime(): string; + + getEnd(): number; + + getEndTime(): string; + + getTags(): Record; + + getStringProperty(key): string; + + getNumberProperty(key): number; + + getProperties(key): Record; + + getAllProperties(): Record; + + } + } diff --git a/react-native/src/index.js b/react-native/src/index.js index 8bc1857..4b28ff5 100644 --- a/react-native/src/index.js +++ b/react-native/src/index.js @@ -2,14 +2,16 @@ import {NativeEventEmitter, NativeModules} from 'react-native'; const {FFmpegKitReactNativeModule} = NativeModules; -const executeCallbackMap = new Map() +const ffmpegSessionCompleteCallbackMap = new Map() +const ffprobeSessionCompleteCallbackMap = new Map() +const mediaInformationSessionCompleteCallbackMap = new Map() const logCallbackMap = new Map() const statisticsCallbackMap = new Map() const logRedirectionStrategyMap = new Map() const eventLogCallbackEvent = "FFmpegKitLogCallbackEvent"; const eventStatisticsCallbackEvent = "FFmpegKitStatisticsCallbackEvent"; -const eventExecuteCallbackEvent = "FFmpegKitExecuteCallbackEvent"; +const eventCompleteCallbackEvent = "FFmpegKitCompleteCallbackEvent"; export const LogRedirectionStrategy = { ALWAYS_PRINT_LOGS: 0, @@ -20,18 +22,11 @@ export const LogRedirectionStrategy = { } export const SessionState = { - CREATED: 0, - RUNNING: 1, - FAILED: 2, - COMPLETED: 3 + CREATED: 0, RUNNING: 1, FAILED: 2, COMPLETED: 3 } export const Signal = { - SIGINT: 2, - SIGQUIT: 3, - SIGPIPE: 13, - SIGTERM: 15, - SIGXCPU: 24 + SIGINT: 2, SIGQUIT: 3, SIGPIPE: 13, SIGTERM: 15, SIGXCPU: 24 } class FFmpegKitReactNativeEventEmitter extends NativeEventEmitter { @@ -66,17 +61,9 @@ class FFmpegKitReactNativeEventEmitter extends NativeEventEmitter { export class Session { /** - * Returns the session specific execute callback function. + * Returns the session specific log callback. * - * @return session specific execute callback function - */ - getExecuteCallback() { - } - - /** - * Returns the session specific log callback function. - * - * @return session specific log callback function + * @return session specific log callback */ getLogCallback() { } @@ -252,6 +239,14 @@ export class Session { isFFprobe() { } + /** + * Returns whether it is a MediaInformation session or not. + * + * @return true if it is a MediaInformation session, false otherwise + */ + isMediaInformation() { + } + /** * Cancels running the session. */ @@ -261,8 +256,8 @@ export class Session { } /** - * Abstract session implementation which includes common features shared by FFmpeg - * and FFprobe sessions. + * Abstract session implementation which includes common features shared by FFmpeg, + * FFprobe and MediaInformation sessions. */ export class AbstractSession extends Session { @@ -452,18 +447,9 @@ export class AbstractSession extends Session { } /** - * Returns the session specific execute callback function. - * - * @return session specific execute callback function - */ - getExecuteCallback() { - return FFmpegKitFactory.getExecuteCallback(this.getSessionId()) - } - - /** - * Returns the session specific log callback function. + * Returns the session specific log callback. * - * @return session specific log callback function + * @return session specific log callback */ getLogCallback() { return FFmpegKitFactory.getLogCallback(this.getSessionId()) @@ -673,6 +659,15 @@ export class AbstractSession extends Session { return false; } + /** + * Returns whether it is a MediaInformation session or not. + * + * @return true if it is a MediaInformation session, false otherwise + */ + isMediaInformation() { + return false; + } + /** * Cancels running the session. */ @@ -709,37 +704,62 @@ export class ArchDetect { */ export class FFmpegKit { + /** + *

Synchronously executes FFmpeg command provided. Space character is used to split the command + * into arguments. You can use single or double quote characters to specify arguments inside your command. + * + * @param command FFmpeg command + * @return FFmpeg session created for this execution + */ + static async execute(command) { + return FFmpegKit.executeWithArguments(FFmpegKitConfig.parseArguments(command)); + } + + /** + *

Synchronously executes FFmpeg with arguments provided. + * + * @param commandArguments FFmpeg command options/arguments as string array + * @return FFmpeg session created for this execution + */ + static async executeWithArguments(commandArguments) { + let session = await FFmpegSession.create(commandArguments, undefined, undefined, undefined); + + 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. * *

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. + * FFmpegSessionCompleteCallback if you want to be notified about the result. * * @param command FFmpeg command - * @param executeCallback callback that will be called when the execution is completed + * @param completeCallback callback that will be called when the execution has completed * @param logCallback callback that will receive logs * @param statisticsCallback callback that will receive statistics * @return FFmpeg session created for this execution */ - static async executeAsync(command, executeCallback, logCallback, statisticsCallback) { - return FFmpegKit.executeWithArgumentsAsync(FFmpegKitConfig.parseArguments(command), executeCallback, logCallback, statisticsCallback); + static async executeAsync(command, completeCallback, logCallback, statisticsCallback) { + return FFmpegKit.executeWithArgumentsAsync(FFmpegKitConfig.parseArguments(command), completeCallback, logCallback, statisticsCallback); } /** *

Starts an asynchronous FFmpeg execution with arguments provided. * *

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. + * FFmpegSessionCompleteCallback if you want to be notified about the result. * * @param commandArguments FFmpeg command options/arguments as string array - * @param executeCallback callback that will be called when the execution is completed + * @param completeCallback callback that will be called when the execution has completed * @param logCallback callback that will receive logs * @param statisticsCallback callback that will receive statistics * @return FFmpeg session created for this execution */ - static async executeWithArgumentsAsync(commandArguments, executeCallback, logCallback, statisticsCallback) { - let session = await FFmpegSession.create(commandArguments, executeCallback, logCallback, statisticsCallback); + static async executeWithArgumentsAsync(commandArguments, completeCallback, logCallback, statisticsCallback) { + let session = await FFmpegSession.create(commandArguments, completeCallback, logCallback, statisticsCallback); await FFmpegKitConfig.asyncFFmpegExecute(session); @@ -749,7 +769,7 @@ export class FFmpegKit { /** *

Cancels the session specified with sessionId. * - *

This function does not wait for termination to complete and returns immediately. + *

This method does not wait for termination to complete and returns immediately. * * @param sessionId id of the session that will be cancelled */ @@ -957,11 +977,45 @@ export class FFmpegKitConfig { return FFmpegKitReactNativeModule.ignoreSignal(signal); } + /** + *

Synchronously executes the FFmpeg session provided. + * + * @param ffmpegSession FFmpeg session which includes command options/arguments + */ + static async ffmpegExecute(ffmpegSession) { + await FFmpegKitConfig.init(); + + return FFmpegKitReactNativeModule.ffmpegSessionExecute(ffmpegSession.getSessionId()); + } + + /** + *

Synchronously executes the FFprobe session provided. + * + * @param ffprobeSession FFprobe session which includes command options/arguments + */ + static async ffprobeExecute(ffprobeSession) { + await FFmpegKitConfig.init(); + + return FFmpegKitReactNativeModule.ffprobeSessionExecute(ffprobeSession.getSessionId()); + } + + /** + *

Synchronously executes the media information session provided. + * + * @param mediaInformationSession media information session which includes command options/arguments + * @param waitTimeout max time to wait until media information is transmitted + */ + static async getMediaInformationExecute(mediaInformationSession, waitTimeout) { + await FFmpegKitConfig.init(); + + return FFmpegKitReactNativeModule.mediaInformationSessionExecute(mediaInformationSession.getSessionId(), FFmpegKitFactory.optionalNumericParameter(waitTimeout)); + } + /** *

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 - * ExecuteCallback if you want to be notified about the result. + * FFmpegSessionCompleteCallback if you want to be notified about the result. * * @param ffmpegSession FFmpeg session which includes command options/arguments */ @@ -975,7 +1029,7 @@ export class FFmpegKitConfig { *

Starts an asynchronous FFprobe execution for the given session. * *

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. + * FFprobeSessionCompleteCallback if you want to be notified about the result. * * @param ffprobeSession FFprobe session which includes command options/arguments */ @@ -989,7 +1043,7 @@ export class FFmpegKitConfig { *

Starts an asynchronous FFprobe execution for the given media information session. * *

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. + * MediaInformationSessionCompleteCallback if you want to be notified about the result. * * @param mediaInformationSession media information session which includes command options/arguments * @param waitTimeout max time to wait until media information is transmitted @@ -1001,9 +1055,9 @@ export class FFmpegKitConfig { } /** - *

Sets a global callback function to redirect FFmpeg/FFprobe logs. + *

Sets a global callback to redirect FFmpeg/FFprobe logs. * - * @param logCallback log callback function or undefined to disable a previously defined + * @param logCallback log callback or undefined to disable a previously defined * callback */ static enableLogCallback(logCallback) { @@ -1011,9 +1065,9 @@ export class FFmpegKitConfig { } /** - *

Sets a global callback function to redirect FFmpeg statistics. + *

Sets a global callback to redirect FFmpeg statistics. * - * @param statisticsCallback statistics callback function or undefined to disable a previously + * @param statisticsCallback statistics callback or undefined to disable a previously * defined callback */ static enableStatisticsCallback(statisticsCallback) { @@ -1021,13 +1075,58 @@ export class FFmpegKitConfig { } /** - *

Sets a global callback function to receive execution results. + *

Sets a global FFmpegSessionCompleteCallback to receive execution results for FFmpeg sessions. * - * @param executeCallback execute callback function or undefined to disable a previously - * defined callback + * @param ffmpegSessionCompleteCallback complete callback or undefined to disable a previously defined callback */ - static enableExecuteCallback(executeCallback) { - FFmpegKitFactory.setGlobalExecuteCallback(executeCallback); + static enableFFmpegSessionCompleteCallback(ffmpegSessionCompleteCallback) { + FFmpegKitFactory.setGlobalFFmpegSessionCompleteCallback(ffmpegSessionCompleteCallback); + } + + /** + *

Returns the global FFmpegSessionCompleteCallback set. + * + * @return global FFmpegSessionCompleteCallback or undefined if it is not set + */ + static getFFmpegSessionCompleteCallback() { + return FFmpegKitFactory.getGlobalFFmpegSessionCompleteCallback(); + } + + /** + *

Sets a global FFprobeSessionCompleteCallback to receive execution results for FFprobe sessions. + * + * @param ffprobeSessionCompleteCallback complete callback or undefined to disable a previously defined callback + */ + static enableFFprobeSessionCompleteCallback(ffprobeSessionCompleteCallback) { + FFmpegKitFactory.setGlobalFFprobeSessionCompleteCallback(ffprobeSessionCompleteCallback); + } + + /** + *

Returns the global FFprobeSessionCompleteCallback set. + * + * @return global FFprobeSessionCompleteCallback or undefined if it is not set + */ + static getFFprobeSessionCompleteCallback() { + return FFmpegKitFactory.getGlobalFFprobeSessionCompleteCallback(); + } + + /** + *

Sets a global MediaInformationSessionCompleteCallback to receive execution results for MediaInformation sessions. + * + * @param mediaInformationSessionCompleteCallback complete callback or undefined to disable a previously defined + * callback + */ + static enableMediaInformationSessionCompleteCallback(mediaInformationSessionCompleteCallback) { + FFmpegKitFactory.setGlobalMediaInformationSessionCompleteCallback(mediaInformationSessionCompleteCallback); + } + + /** + *

Returns the global MediaInformationSessionCompleteCallback set. + * + * @return global MediaInformationSessionCompleteCallback or undefined if it is not set + */ + static getMediaInformationSessionCompleteCallback() { + return FFmpegKitFactory.getGlobalMediaInformationSessionCompleteCallback(); } /** @@ -1051,6 +1150,54 @@ export class FFmpegKitConfig { return FFmpegKitReactNativeModule.setLogLevel(level); } + /** + *

Converts the given Structured Access Framework Uri into an input url that can be used in FFmpeg + * and FFprobe commands. + * + *

Note that this method is Android only. It will fail if called on other platforms. It also requires + * API Level ≥ 19. On older API levels it returns an empty url. + * + * @param uriString SAF uri ("content:…") + * @return input url that can be passed to FFmpegKit or FFprobeKit + */ + static async getSafParameterForRead(uriString) { + await FFmpegKitConfig.init(); + + return FFmpegKitReactNativeModule.getSafParameter(uriString, "r"); + } + + /** + *

Converts the given Structured Access Framework Uri into an output url that can be used in FFmpeg + * and FFprobe commands. + * + *

Note that this method is Android only. It will fail if called on other platforms. It also requires + * API Level ≥ 19. On older API levels it returns an empty url. + * + * @param uriString SAF uri ("content:…") + * @return output url that can be passed to FFmpegKit or FFprobeKit + */ + static async getSafParameterForWrite(uriString) { + await FFmpegKitConfig.init(); + + return FFmpegKitReactNativeModule.getSafParameter(uriString, "w"); + } + + /** + *

Converts the given Structured Access Framework Uri into an saf protocol url opened with the given open mode. + * + *

Note that this method is Android only. It will fail if called on other platforms. It also requires + * API Level ≥ 19. On older API levels it returns an empty url. + * + * @param uriString SAF uri ("content:…") + * @param openMode file mode to use as defined in Android Structured Access Framework documentation + * @return saf protocol url that can be passed to FFmpegKit or FFprobeKit + */ + static async getSafParameter(uriString, openMode) { + await FFmpegKitConfig.init(); + + return FFmpegKitReactNativeModule.getSafParameter(uriString, openMode); + } + /** * Returns the session history size. * @@ -1137,6 +1284,42 @@ export class FFmpegKitConfig { return FFmpegKitReactNativeModule.clearSessions(); } + /** + *

Returns all FFmpeg sessions in the session history. + * + * @return all FFmpeg sessions in the session history + */ + static async getFFmpegSessions() { + await FFmpegKitConfig.init(); + + const sessionArray = await FFmpegKitReactNativeModule.getFFmpegSessions(); + return sessionArray.map(FFmpegKitFactory.mapToSession); + } + + /** + *

Returns all FFprobe sessions in the session history. + * + * @return all FFprobe sessions in the session history + */ + static async getFFprobeSessions() { + await FFmpegKitConfig.init(); + + const sessionArray = await FFmpegKitReactNativeModule.getFFprobeSessions(); + return sessionArray.map(FFmpegKitFactory.mapToSession); + } + + /** + *

Returns all MediaInformation sessions in the session history. + * + * @return all MediaInformation sessions in the session history + */ + static async getMediaInformationSessions() { + await FFmpegKitConfig.init(); + + const sessionArray = await FFmpegKitReactNativeModule.getMediaInformationSessions(); + return sessionArray.map(FFmpegKitFactory.mapToSession); + } + /** *

Returns sessions that have the given state. * @@ -1381,59 +1564,20 @@ export class FFmpegKitConfig { return FFmpegKitReactNativeModule.selectDocument(true, title, type, extraTypes); } - /** - *

Converts the given Structured Access Framework Uri into an input url that can be used in FFmpeg - * and FFprobe commands. - * - *

Note that this method is Android only. It will fail if called on other platforms. It also requires - * API Level ≥ 19. On older API levels it returns an empty url. - * - * @param uriString SAF uri ("content:…") - * @return input url that can be passed to FFmpegKit or FFprobeKit - */ - static async getSafParameterForRead(uriString) { - await FFmpegKitConfig.init(); - - return FFmpegKitReactNativeModule.getSafParameter(false, uriString); - } - - /** - *

Converts the given Structured Access Framework Uri into an output url that can be used in FFmpeg - * and FFprobe commands. - * - *

Note that this method is Android only. It will fail if called on other platforms. It also requires - * API Level ≥ 19. On older API levels it returns an empty url. - * - * @param uriString SAF uri ("content:…") - * @return output url that can be passed to FFmpegKit or FFprobeKit - */ - static async getSafParameterForWrite(uriString) { - await FFmpegKitConfig.init(); - - return FFmpegKitReactNativeModule.getSafParameter(true, uriString); - } - } class FFmpegKitFactory { + static #ffmpegSessionCompleteCallback = undefined; + static #ffprobeSessionCompleteCallback = undefined; + static #mediaInformationSessionCompleteCallback = undefined; static #logCallback = undefined; static #statisticsCallback = undefined; - static #executeCallback = undefined; static #activeLogLevel = undefined; static mapToStatistics(statisticsMap) { if (statisticsMap !== undefined) { - return new Statistics( - statisticsMap.sessionId, - statisticsMap.videoFrameNumber, - statisticsMap.videoFps, - statisticsMap.videoQuality, - statisticsMap.size, - statisticsMap.time, - statisticsMap.bitrate, - statisticsMap.speed - ); + return new Statistics(statisticsMap.sessionId, statisticsMap.videoFrameNumber, statisticsMap.videoFps, statisticsMap.videoQuality, statisticsMap.size, statisticsMap.time, statisticsMap.bitrate, statisticsMap.speed); } else { return undefined; } @@ -1464,7 +1608,7 @@ class FFmpegKitFactory { } static getVersion() { - return "4.5"; + return "4.5.1"; } static getLogRedirectionStrategy(sessionId) { @@ -1511,22 +1655,58 @@ class FFmpegKitFactory { this.#statisticsCallback = statisticsCallback; } - static getExecuteCallback(sessionId) { - return executeCallbackMap.get(sessionId); + static getFFmpegSessionCompleteCallback(sessionId) { + return ffmpegSessionCompleteCallbackMap.get(sessionId); + } + + static setFFmpegSessionCompleteCallback(sessionId, completeCallback) { + if (completeCallback !== undefined) { + ffmpegSessionCompleteCallbackMap.set(sessionId, completeCallback); + } + } + + static getGlobalFFmpegSessionCompleteCallback() { + return this.#ffmpegSessionCompleteCallback; + } + + static setGlobalFFmpegSessionCompleteCallback(completeCallback) { + this.#ffmpegSessionCompleteCallback = completeCallback; + } + + static getFFprobeSessionCompleteCallback(sessionId) { + return ffprobeSessionCompleteCallbackMap.get(sessionId); } - static setExecuteCallback(sessionId, executeCallback) { - if (executeCallback !== undefined) { - executeCallbackMap.set(sessionId, executeCallback); + static setFFprobeSessionCompleteCallback(sessionId, completeCallback) { + if (completeCallback !== undefined) { + ffprobeSessionCompleteCallbackMap.set(sessionId, completeCallback); } } - static getGlobalExecuteCallback() { - return this.#executeCallback; + static getGlobalFFprobeSessionCompleteCallback() { + return this.#ffprobeSessionCompleteCallback; } - static setGlobalExecuteCallback(executeCallback) { - this.#executeCallback = executeCallback; + static setGlobalFFprobeSessionCompleteCallback(completeCallback) { + this.#ffprobeSessionCompleteCallback = completeCallback; + } + + static getMediaInformationSessionCompleteCallback(sessionId) { + return mediaInformationSessionCompleteCallbackMap.get(sessionId); + } + + static setMediaInformationSessionCompleteCallback(sessionId, completeCallback) { + if (completeCallback !== undefined) { + mediaInformationSessionCompleteCallbackMap.set(sessionId, completeCallback); + } + } + + static getGlobalMediaInformationSessionCompleteCallback() { + return this.#mediaInformationSessionCompleteCallback; + } + + static setGlobalMediaInformationSessionCompleteCallback(completeCallback) { + this.#mediaInformationSessionCompleteCallback = completeCallback; } static setLogLevel(logLevel) { @@ -1571,124 +1751,147 @@ class FFmpegKitInitializer { return; } - FFmpegKitConfig.getSession(sessionId).then(session => { - activeLogRedirectionStrategy = session.getLogRedirectionStrategy(); - - if (session.getLogCallback() !== undefined) { - sessionCallbackDefined = true; - - try { - // NOTIFY SESSION CALLBACK DEFINED - session.getLogCallback()(log); - } catch (err) { - console.log("Exception thrown inside session LogCallback block.", err.stack); - } + if (FFmpegKitFactory.getLogRedirectionStrategy(sessionId) !== undefined) { + activeLogRedirectionStrategy = FFmpegKitFactory.getLogRedirectionStrategy(sessionId); + } + let activeLogCallback = FFmpegKitFactory.getLogCallback(sessionId); + if (activeLogCallback !== undefined) { + sessionCallbackDefined = true; + + try { + // NOTIFY SESSION CALLBACK DEFINED + activeLogCallback(log); + } catch (err) { + console.log("Exception thrown inside session log callback.", err.stack); } + } - let globalLogCallbackFunction = FFmpegKitFactory.getGlobalLogCallback(); - if (globalLogCallbackFunction !== undefined) { - globalCallbackDefined = true; + let globalLogCallbackFunction = FFmpegKitFactory.getGlobalLogCallback(); + if (globalLogCallbackFunction !== undefined) { + globalCallbackDefined = true; - try { - // NOTIFY GLOBAL CALLBACK DEFINED - globalLogCallbackFunction(log); - } catch (err) { - console.log("Exception thrown inside global LogCallback block.", err.stack); - } + try { + // NOTIFY GLOBAL CALLBACK DEFINED + globalLogCallbackFunction(log); + } catch (err) { + console.log("Exception thrown inside global log callback.", err.stack); } + } - // EXECUTE THE LOG STRATEGY - switch (activeLogRedirectionStrategy) { - case LogRedirectionStrategy.NEVER_PRINT_LOGS: { + // EXECUTE THE LOG STRATEGY + switch (activeLogRedirectionStrategy) { + case LogRedirectionStrategy.NEVER_PRINT_LOGS: { + return; + } + case LogRedirectionStrategy.PRINT_LOGS_WHEN_GLOBAL_CALLBACK_NOT_DEFINED: { + if (globalCallbackDefined) { return; } - case LogRedirectionStrategy.PRINT_LOGS_WHEN_GLOBAL_CALLBACK_NOT_DEFINED: { - if (globalCallbackDefined) { - return; - } - } - break; - case LogRedirectionStrategy.PRINT_LOGS_WHEN_SESSION_CALLBACK_NOT_DEFINED: { - if (sessionCallbackDefined) { - return; - } - } - break; - case LogRedirectionStrategy.PRINT_LOGS_WHEN_NO_CALLBACKS_DEFINED: { - if (globalCallbackDefined || sessionCallbackDefined) { - return; - } + } + break; + case LogRedirectionStrategy.PRINT_LOGS_WHEN_SESSION_CALLBACK_NOT_DEFINED: { + if (sessionCallbackDefined) { + return; } - break; - case LogRedirectionStrategy.ALWAYS_PRINT_LOGS: { + } + break; + case LogRedirectionStrategy.PRINT_LOGS_WHEN_NO_CALLBACKS_DEFINED: { + if (globalCallbackDefined || sessionCallbackDefined) { + return; } - break; } + break; + case LogRedirectionStrategy.ALWAYS_PRINT_LOGS: { + } + break; + } - // PRINT LOGS - switch (level) { - case Level.AV_LOG_QUIET: { - // PRINT NO OUTPUT - } - break; - default: { - console.log(text); - } + // PRINT LOGS + switch (level) { + case Level.AV_LOG_QUIET: { + // PRINT NO OUTPUT + } + break; + default: { + console.log(text); } - }); + } } static processStatisticsCallbackEvent(event) { let statistics = FFmpegKitFactory.mapToStatistics(event); let sessionId = event.sessionId; - FFmpegKitConfig.getSession(sessionId).then(session => { - if (session.isFFmpeg()) { - if (session.getStatisticsCallback() !== undefined) { - try { - // NOTIFY SESSION CALLBACK DEFINED - session.getStatisticsCallback()(statistics); - } catch (err) { - console.log("Exception thrown inside session StatisticsCallback block.", err.stack); - } - } + let activeStatisticsCallback = FFmpegKitFactory.getStatisticsCallback(sessionId); + if (activeStatisticsCallback !== undefined) { + try { + // NOTIFY SESSION CALLBACK DEFINED + activeStatisticsCallback(statistics); + } catch (err) { + console.log("Exception thrown inside session statistics callback.", err.stack); } + } - let globalStatisticsCallbackFunction = FFmpegKitFactory.getGlobalStatisticsCallback(); - if (globalStatisticsCallbackFunction !== undefined) { - try { - // NOTIFY GLOBAL CALLBACK DEFINED - globalStatisticsCallbackFunction(statistics); - } catch (err) { - console.log("Exception thrown inside global StatisticsCallback block.", err.stack); - } + let globalStatisticsCallbackFunction = FFmpegKitFactory.getGlobalStatisticsCallback(); + if (globalStatisticsCallbackFunction !== undefined) { + try { + // NOTIFY GLOBAL CALLBACK DEFINED + globalStatisticsCallbackFunction(statistics); + } catch (err) { + console.log("Exception thrown inside global statistics callback.", err.stack); } - }); + } } - static processExecuteCallbackEvent(event) { - let sessionId = event.sessionId; + static processCompleteCallbackEvent(event) { + if (event !== undefined) { + let sessionId = event.sessionId; - FFmpegKitConfig.getSession(sessionId).then(session => { - if (session.getExecuteCallback() !== undefined) { - try { - // NOTIFY SESSION CALLBACK DEFINED - session.getExecuteCallback()(session); - } catch (err) { - console.log("Exception thrown inside session ExecuteCallback block.", err.stack); - } - } + FFmpegKitConfig.getSession(sessionId).then(session => { + if (session !== undefined) { + if (session.getCompleteCallback() !== undefined) { + try { + // NOTIFY SESSION CALLBACK DEFINED + session.getCompleteCallback()(session); + } catch (err) { + console.log("Exception thrown inside session complete callback.", err.stack); + } + } - let globalExecuteCallbackFunction = FFmpegKitFactory.getGlobalExecuteCallback(); - if (globalExecuteCallbackFunction !== undefined) { - try { - // NOTIFY GLOBAL CALLBACK DEFINED - globalExecuteCallbackFunction(session); - } catch (err) { - console.log("Exception thrown inside global ExecuteCallback block.", err.stack); + if (session.isFFmpeg()) { + let globalFFmpegSessionCompleteCallback = FFmpegKitFactory.getGlobalFFmpegSessionCompleteCallback(); + if (globalFFmpegSessionCompleteCallback !== undefined) { + try { + // NOTIFY GLOBAL CALLBACK DEFINED + globalFFmpegSessionCompleteCallback(session); + } catch (err) { + console.log("Exception thrown inside global complete callback.", err.stack); + } + } + } else if (session.isFFprobe()) { + let globalFFprobeSessionCompleteCallback = FFmpegKitFactory.getGlobalFFprobeSessionCompleteCallback(); + if (globalFFprobeSessionCompleteCallback !== undefined) { + try { + // NOTIFY GLOBAL CALLBACK DEFINED + globalFFprobeSessionCompleteCallback(session); + } catch (err) { + console.log("Exception thrown inside global complete callback.", err.stack); + } + } + } else if (session.isMediaInformation()) { + let globalMediaInformationSessionCompleteCallback = FFmpegKitFactory.getGlobalMediaInformationSessionCompleteCallback(); + if (globalMediaInformationSessionCompleteCallback !== undefined) { + try { + // NOTIFY GLOBAL CALLBACK DEFINED + globalMediaInformationSessionCompleteCallback(session); + } catch (err) { + console.log("Exception thrown inside global complete callback.", err.stack); + } + } + } } - } - }); + }); + } } static async initialize() { @@ -1702,7 +1905,7 @@ class FFmpegKitInitializer { this.#eventEmitter.addListener(eventLogCallbackEvent, FFmpegKitInitializer.processLogCallbackEvent); this.#eventEmitter.addListener(eventStatisticsCallbackEvent, FFmpegKitInitializer.processStatisticsCallbackEvent); - this.#eventEmitter.addListener(eventExecuteCallbackEvent, FFmpegKitInitializer.processExecuteCallbackEvent); + this.#eventEmitter.addListener(eventCompleteCallbackEvent, FFmpegKitInitializer.processCompleteCallbackEvent); FFmpegKitFactory.setLogLevel(await FFmpegKitReactNativeModule.getLogLevel()); const version = FFmpegKitFactory.getVersion(); @@ -1710,8 +1913,9 @@ class FFmpegKitInitializer { const arch = await ArchDetect.getArch(); const packageName = await Packages.getPackageName(); await FFmpegKitConfig.enableRedirection(); + const isLTSPostfix = (await FFmpegKitConfig.isLTSBuild()) ? "-lts" : ""; - console.log(`Loaded ffmpeg-kit-react-native-${platform}-${packageName}-${arch}-${version}.`); + console.log(`Loaded ffmpeg-kit-react-native-${platform}-${packageName}-${arch}-${version}${isLTSPostfix}.`); } } @@ -1732,17 +1936,17 @@ export class FFmpegSession extends AbstractSession { * Creates a new FFmpeg session. * * @param argumentsArray FFmpeg command arguments - * @param executeCallback callback that will be called when the execution is completed + * @param completeCallback callback that will be called when the execution has completed * @param logCallback callback that will receive logs * @param statisticsCallback callback that will receive statistics * @param logRedirectionStrategy defines how logs will be redirected * @returns FFmpeg session created */ - static async create(argumentsArray, executeCallback, logCallback, statisticsCallback, logRedirectionStrategy) { + static async create(argumentsArray, completeCallback, logCallback, statisticsCallback, logRedirectionStrategy) { const session = await AbstractSession.createFFmpegSession(argumentsArray, logRedirectionStrategy); const sessionId = session.getSessionId(); - FFmpegKitFactory.setExecuteCallback(sessionId, executeCallback); + FFmpegKitFactory.setFFmpegSessionCompleteCallback(sessionId, completeCallback); FFmpegKitFactory.setLogCallback(sessionId, logCallback); FFmpegKitFactory.setStatisticsCallback(sessionId, statisticsCallback); @@ -1760,14 +1964,23 @@ export class FFmpegSession extends AbstractSession { } /** - * Returns the session specific statistics callback function. + * Returns the session specific statistics callback. * - * @return session specific statistics callback function + * @return session specific statistics callback */ getStatisticsCallback() { return FFmpegKitFactory.getStatisticsCallback(this.getSessionId()); } + /** + * Returns the session specific complete callback. + * + * @return session specific complete callback + */ + getCompleteCallback() { + return FFmpegKitFactory.getFFmpegSessionCompleteCallback(this.getSessionId()); + } + /** * Returns all statistics entries generated for this session. If there are asynchronous * messages that are not delivered yet, this method waits for them until the given timeout. @@ -1806,7 +2019,7 @@ export class FFmpegSession extends AbstractSession { let statistics = await this.getStatistics(); if (statistics.length > 0) { - return statistics[0]; + return statistics[statistics.length - 1]; } else { return undefined; } @@ -1820,6 +2033,10 @@ export class FFmpegSession extends AbstractSession { return false; } + isMediaInformation() { + return false; + } + } /** @@ -1827,56 +2044,127 @@ export class FFmpegSession extends AbstractSession { */ export class FFprobeKit { + /** + *

Synchronously executes FFprobe command provided. Space character is used to split the command + * into arguments. You can use single or double quote characters to specify arguments inside your command. + * + * @param command FFprobe command + * @return FFprobe session created for this execution + */ + static async execute(command) { + return FFprobeKit.executeWithArguments(FFmpegKitConfig.parseArguments(command)); + } + + /** + *

Synchronously executes FFprobe with arguments provided. + * + * @param commandArguments FFprobe command options/arguments as string array + * @return FFprobe session created for this execution + */ + static async executeWithArguments(commandArguments) { + let session = await FFprobeSession.create(commandArguments, undefined, undefined); + + 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. * *

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. + * FFprobeSessionCompleteCallback if you want to be notified about the result. * * @param command FFprobe command - * @param executeCallback callback that will be called when the execution is completed + * @param completeCallback callback that will be called when the execution has completed * @param logCallback callback that will receive logs * @return FFprobe session created for this execution */ - static async executeAsync(command, executeCallback, logCallback) { - return FFprobeKit.executeWithArgumentsAsync(FFmpegKitConfig.parseArguments(command), executeCallback, logCallback); + static async executeAsync(command, completeCallback, logCallback) { + return FFprobeKit.executeWithArgumentsAsync(FFmpegKitConfig.parseArguments(command), completeCallback, logCallback); } /** *

Starts an asynchronous FFprobe execution with arguments provided. * *

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. + * FFprobeSessionCompleteCallback if you want to be notified about the result. * * @param commandArguments FFprobe command options/arguments as string array - * @param executeCallback callback that will be called when the execution is completed + * @param completeCallback callback that will be called when the execution has completed * @param logCallback callback that will receive logs * @return FFprobe session created for this execution */ - static async executeWithArgumentsAsync(commandArguments, executeCallback, logCallback) { - let session = await FFprobeSession.create(commandArguments, executeCallback, logCallback); + static async executeWithArgumentsAsync(commandArguments, completeCallback, logCallback) { + let session = await FFprobeSession.create(commandArguments, completeCallback, logCallback); await FFmpegKitConfig.asyncFFprobeExecute(session); return session; } + /** + *

Extracts media information for the file specified with path. + * + * @param path path or uri of a media file + * @param waitTimeout max time to wait until media information is transmitted + * @return media information session created for this execution + */ + static async getMediaInformation(path, waitTimeout) { + const commandArguments = ["-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-show_chapters", "-i", path]; + return FFprobeKit.getMediaInformationFromCommandArguments(commandArguments, 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. + * + * @param command FFprobe command that prints media information for a file in JSON format + * @param waitTimeout max time to wait until media information is transmitted + * @return media information session created for this execution + */ + static async getMediaInformationFromCommand(command, waitTimeout) { + return FFprobeKit.getMediaInformationFromCommandArguments(FFmpegKitConfig.parseArguments(command), 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. + * + * @param commandArguments FFprobe command arguments that prints media information for a file in JSON format + * @param waitTimeout max time to wait until media information is transmitted + * @return media information session created for this execution + */ + static async getMediaInformationFromCommandArguments(commandArguments, waitTimeout) { + let session = await MediaInformationSession.create(commandArguments, undefined, undefined); + + await FFmpegKitConfig.getMediaInformationExecute(session, waitTimeout); + + const mediaInformation = await FFmpegKitReactNativeModule.getMediaInformation(session.getSessionId()); + if (mediaInformation !== undefined && mediaInformation !== null) { + session.setMediaInformation(new MediaInformation(mediaInformation)); + } + + 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 - * ExecuteCallback if you want to be notified about the result. + * MediaInformationSessionCompleteCallback if you want to be notified about the result. * * @param path path or uri of a media file - * @param executeCallback callback that will be notified when execution is completed + * @param completeCallback callback that will be notified when execution has completed * @param logCallback callback that will receive logs * @param waitTimeout max time to wait until media information is transmitted * @return media information session created for this execution */ - static async getMediaInformationAsync(path, executeCallback, logCallback, waitTimeout) { - const commandArguments = ["-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-i", path]; - return FFprobeKit.getMediaInformationFromCommandArgumentsAsync(commandArguments, executeCallback, logCallback, waitTimeout); + static async getMediaInformationAsync(path, completeCallback, logCallback, waitTimeout) { + const commandArguments = ["-v", "error", "-hide_banner", "-print_format", "json", "-show_format", "-show_streams", "-show_chapters", "-i", path]; + return FFprobeKit.getMediaInformationFromCommandArgumentsAsync(commandArguments, completeCallback, logCallback, waitTimeout); } /** @@ -1884,16 +2172,16 @@ export class FFprobeKit { * 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. + * MediaInformationSessionCompleteCallback if you want to be notified about the result. * * @param command FFprobe command that prints media information for a file in JSON format - * @param executeCallback callback that will be notified when execution is completed + * @param completeCallback callback that will be notified when execution has completed * @param logCallback callback that will receive logs * @param waitTimeout max time to wait until media information is transmitted * @return media information session created for this execution */ - static async getMediaInformationFromCommandAsync(command, executeCallback, logCallback, waitTimeout) { - return FFprobeKit.getMediaInformationFromCommandArgumentsAsync(FFmpegKitConfig.parseArguments(command), executeCallback, logCallback, waitTimeout); + static async getMediaInformationFromCommandAsync(command, completeCallback, logCallback, waitTimeout) { + return FFprobeKit.getMediaInformationFromCommandArgumentsAsync(FFmpegKitConfig.parseArguments(command), completeCallback, logCallback, waitTimeout); } /** @@ -1902,19 +2190,24 @@ export class FFprobeKit { * 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. + * MediaInformationSessionCompleteCallback if you want to be notified about the result. * * @param commandArguments FFprobe command arguments that prints media information for a file in JSON format - * @param executeCallback callback that will be notified when execution is completed + * @param completeCallback callback that will be notified when execution has completed * @param logCallback callback that will receive logs * @param waitTimeout max time to wait until media information is transmitted * @return media information session created for this execution */ - static async getMediaInformationFromCommandArgumentsAsync(commandArguments, executeCallback, logCallback, waitTimeout) { - let session = await MediaInformationSession.create(commandArguments, executeCallback, logCallback); + static async getMediaInformationFromCommandArgumentsAsync(commandArguments, completeCallback, logCallback, waitTimeout) { + let session = await MediaInformationSession.create(commandArguments, completeCallback, logCallback); await FFmpegKitConfig.asyncGetMediaInformationExecute(session, waitTimeout); + const mediaInformation = await FFmpegKitReactNativeModule.getMediaInformation(session.getSessionId()); + if (mediaInformation !== undefined && mediaInformation !== null) { + session.setMediaInformation(new MediaInformation(mediaInformation)); + } + return session; } @@ -1923,13 +2216,25 @@ export class FFprobeKit { * * @return all FFprobe sessions in the session history */ - static async listSessions() { + static async listFFprobeSessions() { await FFmpegKitConfig.init(); const sessionArray = await FFmpegKitReactNativeModule.getFFprobeSessions(); return sessionArray.map(FFmpegKitFactory.mapToSession); } + /** + *

Lists all MediaInformation sessions in the session history. + * + * @return all MediaInformation sessions in the session history + */ + static async listMediaInformationSessions() { + await FFmpegKitConfig.init(); + + const sessionArray = await FFmpegKitReactNativeModule.getMediaInformationSessions(); + return sessionArray.map(FFmpegKitFactory.mapToSession); + } + } /** @@ -1948,16 +2253,16 @@ export class FFprobeSession extends AbstractSession { * Creates a new FFprobe session. * * @param argumentsArray FFprobe command arguments - * @param executeCallback callback that will be called when the execution is completed + * @param completeCallback callback that will be called when the execution has completed * @param logCallback callback that will receive logs * @param logRedirectionStrategy defines how logs will be redirected * @returns FFprobe session created */ - static async create(argumentsArray, executeCallback, logCallback, logRedirectionStrategy) { + static async create(argumentsArray, completeCallback, logCallback, logRedirectionStrategy) { const session = await AbstractSession.createFFprobeSession(argumentsArray, logRedirectionStrategy); const sessionId = session.getSessionId(); - FFmpegKitFactory.setExecuteCallback(sessionId, executeCallback); + FFmpegKitFactory.setFFprobeSessionCompleteCallback(sessionId, completeCallback); FFmpegKitFactory.setLogCallback(sessionId, logCallback); return session; @@ -1973,6 +2278,15 @@ export class FFprobeSession extends AbstractSession { return AbstractSession.createFFprobeSessionFromMap(sessionMap); } + /** + * Returns the session specific complete callback. + * + * @return session specific complete callback + */ + getCompleteCallback() { + return FFmpegKitFactory.getFFprobeSessionCompleteCallback(this.getSessionId()); + } + isFFmpeg() { return false; } @@ -1981,6 +2295,10 @@ export class FFprobeSession extends AbstractSession { return true; } + isMediaInformation() { + return false; + } + } /** @@ -2220,6 +2538,28 @@ export class MediaInformation { return list; } + /** + * Returns the chapters found as array. + * + * @returns Chapter array + */ + getChapters() { + let list = []; + let chapterList; + + if (this.#allProperties !== undefined) { + chapterList = this.#allProperties.chapters; + } + + if (chapterList !== undefined) { + chapterList.forEach((chapter) => { + list.push(new Chapter(chapter)); + }); + } + + return list; + } + /** * Returns the media property associated with the key. * @@ -2324,7 +2664,7 @@ export class MediaInformationJsonParser { *

A custom FFprobe session, which produces a MediaInformation object using the * FFprobe output. */ -export class MediaInformationSession extends FFprobeSession { +export class MediaInformationSession extends AbstractSession { #mediaInformation; /** @@ -2338,15 +2678,15 @@ export class MediaInformationSession extends FFprobeSession { * Creates a new MediaInformationSession session. * * @param argumentsArray FFprobe command arguments - * @param executeCallback callback that will be called when the execution is completed + * @param completeCallback callback that will be called when the execution has completed * @param logCallback callback that will receive logs * @returns MediaInformationSession session created */ - static async create(argumentsArray, executeCallback, logCallback) { + static async create(argumentsArray, completeCallback, logCallback) { const session = await AbstractSession.createMediaInformationSession(argumentsArray); const sessionId = session.getSessionId(); - FFmpegKitFactory.setExecuteCallback(sessionId, executeCallback); + FFmpegKitFactory.setMediaInformationSessionCompleteCallback(sessionId, completeCallback); FFmpegKitFactory.setLogCallback(sessionId, logCallback); return session; @@ -2381,6 +2721,27 @@ export class MediaInformationSession extends FFprobeSession { this.#mediaInformation = mediaInformation; } + /** + * Returns the session specific complete callback. + * + * @return session specific complete callback + */ + getCompleteCallback() { + return FFmpegKitFactory.getMediaInformationSessionCompleteCallback(this.getSessionId()); + } + + isFFmpeg() { + return false; + } + + isFFprobe() { + return false; + } + + isMediaInformation() { + return true; + } + } /** @@ -2393,7 +2754,9 @@ export class Packages { * * @return predicted FFmpegKit ReactNative binary package name */ - static getPackageName() { + static async getPackageName() { + await FFmpegKitConfig.init(); + return FFmpegKitReactNativeModule.getPackageName(); } @@ -2402,7 +2765,9 @@ export class Packages { * * @return enabled external libraries */ - static getExternalLibraries() { + static async getExternalLibraries() { + await FFmpegKitConfig.init(); + return FFmpegKitReactNativeModule.getExternalLibraries(); } @@ -2783,3 +3148,137 @@ export class StreamInformation { return this.#allProperties; } } + +/** + * Chapter class. + */ +export class Chapter { + + static KEY_ID = "id"; + static KEY_TIME_BASE = "time_base"; + static KEY_START = "start"; + static KEY_START_TIME = "start_time"; + static KEY_END = "end"; + static KEY_END_TIME = "end_time"; + static KEY_TAGS = "tags"; + + #allProperties; + + constructor(properties) { + this.#allProperties = properties; + } + + /** + * Returns id. + * + * @return id + */ + getId() { + return this.getNumberProperty(Chapter.KEY_ID); + } + + /** + * Returns time base. + * + * @return time base + */ + getTimeBase() { + return this.getStringProperty(Chapter.KEY_TIME_BASE); + } + + /** + * Returns start. + * + * @return start + */ + getStart() { + return this.getNumberProperty(Chapter.KEY_START); + } + + /** + * Returns start time. + * + * @return start time + */ + getStartTime() { + return this.getStringProperty(Chapter.KEY_START_TIME); + } + + /** + * Returns end. + * + * @return end + */ + getEnd() { + return this.getNumberProperty(Chapter.KEY_END); + } + + /** + * Returns end time. + * + * @return end time + */ + getEndTime() { + return this.getStringProperty(Chapter.KEY_END_TIME); + } + + /** + * Returns all tags. + * + * @return tags object + */ + getTags() { + return this.getProperties(StreamInformation.KEY_TAGS); + } + + /** + * Returns the chapter property associated with the key. + * + * @param key property key + * @return chapter property as string or undefined if the key is not found + */ + getStringProperty(key) { + if (this.#allProperties !== undefined) { + return this.#allProperties[key]; + } else { + return undefined; + } + } + + /** + * Returns the chapter property associated with the key. + * + * @param key property key + * @return chapter property as number or undefined if the key is not found + */ + getNumberProperty(key) { + if (this.#allProperties !== undefined) { + return this.#allProperties[key]; + } else { + return undefined; + } + } + + /** + * Returns the chapter properties associated with the key. + * + * @param key properties key + * @return chapter properties as an object or undefined if the key is not found + */ + getProperties(key) { + if (this.#allProperties !== undefined) { + return this.#allProperties[key]; + } else { + return undefined; + } + } + + /** + * Returns all properties found. + * + * @returns an object in which properties can be accessed by property names + */ + getAllProperties() { + return this.#allProperties; + } +}