Skip to content

Commit 4b134a3

Browse files
kesha-antonovShahen Hovhannisyan
authored andcommitted
feat(Exec ffmpeg async) (#91)
Execute FFPEG command asynchronously
1 parent 0642544 commit 4b134a3

1 file changed

Lines changed: 177 additions & 113 deletions

File tree

android/src/main/java/com/shahenlibrary/Trimmer/Trimmer.java

Lines changed: 177 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,15 @@
6262
import java.io.InputStream;
6363
import java.io.BufferedInputStream;
6464
import java.io.FileInputStream;
65+
import android.os.AsyncTask;
66+
import android.support.annotation.NonNull;
67+
import android.support.annotation.Nullable;
6568

6669
import java.security.NoSuchAlgorithmException;
6770
import java.security.MessageDigest;
6871
import java.util.Formatter;
6972

73+
7074
public class Trimmer {
7175

7276
private static final String LOG_TAG = "RNTrimmerManager";
@@ -77,6 +81,167 @@ public class Trimmer {
7781
private static final int DEFAULT_BUFFER_SIZE = 4096;
7882
private static final int END_OF_FILE = -1;
7983

84+
private static class FfmpegCmdAsyncTaskParams {
85+
ArrayList<String> cmd;
86+
final String pathToProcessingFile;
87+
ReactApplicationContext ctx;
88+
final Promise promise;
89+
final String errorMessageTitle;
90+
final OnCompressVideoListener cb;
91+
92+
FfmpegCmdAsyncTaskParams(ArrayList<String> cmd, final String pathToProcessingFile, ReactApplicationContext ctx, final Promise promise, final String errorMessageTitle, final OnCompressVideoListener cb) {
93+
this.cmd = cmd;
94+
this.pathToProcessingFile = pathToProcessingFile;
95+
this.ctx = ctx;
96+
this.promise = promise;
97+
this.errorMessageTitle = errorMessageTitle;
98+
this.cb = cb;
99+
}
100+
}
101+
102+
private static class FfmpegCmdAsyncTask extends AsyncTask<FfmpegCmdAsyncTaskParams, Void, Void> {
103+
104+
@Override
105+
protected Void doInBackground(FfmpegCmdAsyncTaskParams... params) {
106+
ArrayList<String> cmd = params[0].cmd;
107+
final String pathToProcessingFile = params[0].pathToProcessingFile;
108+
ReactApplicationContext ctx = params[0].ctx;
109+
final Promise promise = params[0].promise;
110+
final String errorMessageTitle = params[0].errorMessageTitle;
111+
final OnCompressVideoListener cb = params[0].cb;
112+
113+
114+
String errorMessageFromCmd = null;
115+
116+
try {
117+
// NOTE: 3. EXECUTE "ffmpeg" COMMAND
118+
String ffmpegInDir = getFfmpegAbsolutePath(ctx);
119+
cmd.add(0, ffmpegInDir);
120+
Process p = new ProcessBuilder(cmd).start();
121+
122+
BufferedReader input = getOutputFromProcess(p);
123+
String line = null;
124+
125+
StringBuilder sInput = new StringBuilder();
126+
127+
while((line=input.readLine()) != null) {
128+
Log.d(LOG_TAG, "processing ffmpeg");
129+
System.out.println(sInput);
130+
sInput.append(line);
131+
}
132+
input.close();
133+
134+
int errorCode = p.waitFor();
135+
Log.d(LOG_TAG, "ffmpeg processing completed");
136+
137+
if ( errorCode != 0 ) {
138+
BufferedReader error = getErrorFromProcess(p);
139+
StringBuilder sError = new StringBuilder();
140+
141+
Log.d(LOG_TAG, "ffmpeg error code: " + errorCode);
142+
while((line=error.readLine()) != null) {
143+
System.out.println(sError);
144+
sError.append(line);
145+
}
146+
error.close();
147+
148+
errorMessageFromCmd = sError.toString();
149+
}
150+
} catch (Exception e) {
151+
errorMessageFromCmd = e.toString();
152+
}
153+
154+
if ( errorMessageFromCmd != null ) {
155+
String errorMessage = errorMessageTitle + ": failed. " + errorMessageFromCmd;
156+
if (cb != null) {
157+
cb.onError(errorMessage);
158+
} else if (promise != null) {
159+
promise.reject(errorMessage);
160+
}
161+
} else {
162+
String filePath = "file://" + pathToProcessingFile;
163+
if (cb != null) {
164+
cb.onSuccess(filePath);
165+
} else if (promise != null) {
166+
WritableMap event = Arguments.createMap();
167+
event.putString("source", filePath);
168+
promise.resolve(event);
169+
}
170+
}
171+
172+
return null;
173+
}
174+
175+
}
176+
177+
178+
private static class LoadFfmpegAsyncTaskParams {
179+
ReactApplicationContext ctx;
180+
181+
LoadFfmpegAsyncTaskParams(ReactApplicationContext ctx) {
182+
this.ctx = ctx;
183+
}
184+
}
185+
186+
private static class LoadFfmpegAsyncTask extends AsyncTask<LoadFfmpegAsyncTaskParams, Void, Void> {
187+
188+
@Override
189+
protected Void doInBackground(LoadFfmpegAsyncTaskParams... params) {
190+
ReactApplicationContext ctx = params[0].ctx;
191+
192+
// NOTE: 1. COPY "ffmpeg" FROM ASSETS TO /data/data/com.myapp...
193+
String filesDir = getFilesDirAbsolutePath(ctx);
194+
195+
try {
196+
File ffmpegFile = new File(filesDir, FFMPEG_FILE_NAME);
197+
if ( !(ffmpegFile.exists() && getSha1FromFile(ffmpegFile).equalsIgnoreCase(FFMPEG_SHA1)) ) {
198+
final FileOutputStream ffmpegStreamToDataDir = new FileOutputStream(ffmpegFile);
199+
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
200+
201+
int n;
202+
InputStream ffmpegInAssets = ctx.getAssets().open("armeabi-v7a" + File.separator + FFMPEG_FILE_NAME);
203+
while(END_OF_FILE != (n = ffmpegInAssets.read(buffer))) {
204+
ffmpegStreamToDataDir.write(buffer, 0, n);
205+
}
206+
207+
ffmpegStreamToDataDir.flush();
208+
ffmpegStreamToDataDir.close();
209+
210+
ffmpegInAssets.close();
211+
}
212+
} catch (IOException e) {
213+
Log.d(LOG_TAG, "Failed to copy ffmpeg" + e.toString());
214+
ffmpegLoaded = false;
215+
return null;
216+
}
217+
218+
String ffmpegInDir = getFfmpegAbsolutePath(ctx);
219+
220+
// NOTE: 2. MAKE "ffmpeg" EXECUTABLE
221+
String[] cmdlineChmod = { "chmod", "700", ffmpegInDir };
222+
// TODO: 1. CHECK PERMISSIONS
223+
Process pChmod = null;
224+
try {
225+
pChmod = Runtime.getRuntime().exec(cmdlineChmod);
226+
} catch (IOException e) {
227+
Log.d(LOG_TAG, "Failed to make ffmpeg executable. Error in execution cmd. " + e.toString());
228+
ffmpegLoaded = false;
229+
return null;
230+
}
231+
232+
try {
233+
pChmod.waitFor();
234+
} catch (InterruptedException e) {
235+
Log.d(LOG_TAG, "Failed to make ffmpeg executable. Error in wait cmd. " + e.toString());
236+
ffmpegLoaded = false;
237+
return null;
238+
}
239+
240+
ffmpegLoaded = true;
241+
return null;
242+
}
243+
}
244+
80245

81246
public static void getPreviewImages(String path, Promise promise, ReactApplicationContext ctx) {
82247
FFmpegMediaMetadataRetriever retriever = new FFmpegMediaMetadataRetriever();
@@ -271,24 +436,7 @@ public static void compress(String source, ReadableMap options, final Promise pr
271436
}
272437
cmd.add(tempFile.getPath());
273438

274-
String error = executeFfmpegCommand(cmd, rctx);
275-
if ( error != null ) {
276-
if (cb != null) {
277-
cb.onError("compress error: failed. " + error);
278-
} else if (promise != null) {
279-
promise.reject("compress error: failed.", error);
280-
}
281-
return;
282-
}
283-
284-
if (cb != null) {
285-
cb.onSuccess("file://" + tempFile.getPath());
286-
} else if (promise != null) {
287-
WritableMap event = Arguments.createMap();
288-
event.putString("source", "file://" + tempFile.getPath());
289-
promise.resolve(event);
290-
return;
291-
}
439+
executeFfmpegCommand(cmd, tempFile.getPath(), rctx, promise, "compress error", cb);
292440
}
293441

294442
static File createTempFile(String extension, final Promise promise, Context ctx) {
@@ -444,59 +592,16 @@ static void crop(String source, ReadableMap options, final Promise promise, Reac
444592
// NOTE: OUTPUT FILE
445593
cmd.add(tempFile.getPath());
446594

447-
String error = executeFfmpegCommand(cmd, ctx);
448-
if ( error != null ) {
449-
promise.reject("Crop error", error);
450-
return;
451-
}
452-
453-
WritableMap event = Arguments.createMap();
454-
event.putString("source", "file://" + tempFile.getPath());
455-
promise.resolve(event);
456-
return;
595+
executeFfmpegCommand(cmd, tempFile.getPath(), ctx, promise, "Crop error", null);
457596
}
458597

459-
static private String executeFfmpegCommand(ArrayList<String> cmd, ReactApplicationContext ctx) {
460-
try {
461-
// NOTE: 3. EXECUTE "ffmpeg" COMMAND
462-
String ffmpegInDir = getFfmpegAbsolutePath(ctx);
463-
cmd.add(0, ffmpegInDir);
464-
Process p = new ProcessBuilder(cmd).start();
465-
466-
BufferedReader input = getOutputFromProcess(p);
467-
String line = null;
468-
469-
StringBuilder sInput = new StringBuilder();
470-
471-
while((line=input.readLine()) != null) {
472-
Log.d(LOG_TAG, "processing ffmpeg");
473-
System.out.println(sInput);
474-
sInput.append(line);
475-
}
476-
input.close();
477-
478-
// TODO: DO IT ASYNC
479-
int errorCode = p.waitFor();
480-
Log.d(LOG_TAG, "ffmpeg processing completed");
598+
static private Void executeFfmpegCommand(@NonNull ArrayList<String> cmd, @NonNull final String pathToProcessingFile, @NonNull ReactApplicationContext ctx, @NonNull final Promise promise, @NonNull final String errorMessageTitle, @Nullable final OnCompressVideoListener cb) {
599+
FfmpegCmdAsyncTaskParams ffmpegCmdAsyncTaskParams = new FfmpegCmdAsyncTaskParams(cmd, pathToProcessingFile, ctx, promise, errorMessageTitle, cb);
481600

482-
if ( errorCode != 0 ) {
483-
BufferedReader error = getErrorFromProcess(p);
484-
StringBuilder sError = new StringBuilder();
601+
FfmpegCmdAsyncTask ffmpegCmdAsyncTask = new FfmpegCmdAsyncTask();
602+
ffmpegCmdAsyncTask.execute(ffmpegCmdAsyncTaskParams);
485603

486-
Log.d(LOG_TAG, "ffmpeg error code: " + errorCode);
487-
while((line=error.readLine()) != null) {
488-
System.out.println(sError);
489-
sError.append(line);
490-
}
491-
error.close();
492-
493-
return sError.toString();
494-
}
495-
496-
return null;
497-
} catch (Exception e) {
498-
return e.toString();
499-
}
604+
return null;
500605
}
501606

502607
private static String getFilesDirAbsolutePath(ReactApplicationContext ctx) {
@@ -538,54 +643,13 @@ public static String getSha1FromFile(final File file) {
538643
}
539644

540645
public static void loadFfmpeg(ReactApplicationContext ctx) {
541-
// NOTE: 1. COPY "ffmpeg" FROM ASSETS TO /data/data/com.myapp...
542-
String filesDir = getFilesDirAbsolutePath(ctx);
543-
544-
try {
545-
File ffmpegFile = new File(filesDir, FFMPEG_FILE_NAME);
546-
if ( !(ffmpegFile.exists() && getSha1FromFile(ffmpegFile).equalsIgnoreCase(FFMPEG_SHA1)) ) {
547-
final FileOutputStream ffmpegStreamToDataDir = new FileOutputStream(ffmpegFile);
548-
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
549-
550-
int n;
551-
InputStream ffmpegInAssets = ctx.getAssets().open("armeabi-v7a" + File.separator + FFMPEG_FILE_NAME);
552-
while(END_OF_FILE != (n = ffmpegInAssets.read(buffer))) {
553-
ffmpegStreamToDataDir.write(buffer, 0, n);
554-
}
555-
556-
ffmpegStreamToDataDir.flush();
557-
ffmpegStreamToDataDir.close();
558-
559-
ffmpegInAssets.close();
560-
}
561-
} catch (IOException e) {
562-
Log.d(LOG_TAG, "Failed to copy ffmpeg" + e.toString());
563-
ffmpegLoaded = false;
564-
return;
565-
}
646+
LoadFfmpegAsyncTaskParams loadFfmpegAsyncTaskParams = new LoadFfmpegAsyncTaskParams(ctx);
566647

567-
String ffmpegInDir = getFfmpegAbsolutePath(ctx);
568-
569-
// NOTE: 2. MAKE "ffmpeg" EXECUTABLE
570-
String[] cmdlineChmod = { "chmod", "700", ffmpegInDir };
571-
// TODO: 1. CHECK PERMISSIONS. 2. DO IT ASYNC
572-
Process pChmod = null;
573-
try {
574-
pChmod = Runtime.getRuntime().exec(cmdlineChmod);
575-
} catch (IOException e) {
576-
Log.d(LOG_TAG, "Failed to make ffmpeg executable. Error in execution cmd. " + e.toString());
577-
ffmpegLoaded = false;
578-
return;
579-
}
648+
LoadFfmpegAsyncTask loadFfmpegAsyncTask = new LoadFfmpegAsyncTask();
649+
loadFfmpegAsyncTask.execute(loadFfmpegAsyncTaskParams);
580650

581-
try {
582-
pChmod.waitFor();
583-
} catch (InterruptedException e) {
584-
Log.d(LOG_TAG, "Failed to make ffmpeg executable. Error in wait cmd. " + e.toString());
585-
ffmpegLoaded = false;
586-
return;
587-
}
651+
// TODO: EXPOSE TO JS "isFfmpegLoaded" AND "isFfmpegLoading"
588652

589-
ffmpegLoaded = true;
653+
return;
590654
}
591655
}

0 commit comments

Comments
 (0)