Skip to content

Commit a3e639b

Browse files
committed
CB-10776: Add the ability to pause and resume an audio recording (Android)
1 parent b1ef73d commit a3e639b

File tree

2 files changed

+121
-22
lines changed

2 files changed

+121
-22
lines changed

src/android/AudioHandler.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,13 @@ public boolean execute(String action, JSONArray args, CallbackContext callbackCo
118118
promptForRecord();
119119
}
120120
else if (action.equals("stopRecordingAudio")) {
121-
this.stopRecordingAudio(args.getString(0));
121+
this.stopRecordingAudio(args.getString(0), true);
122+
}
123+
else if (action.equals("pauseRecordingAudio")) {
124+
this.stopRecordingAudio(args.getString(0), false);
125+
}
126+
else if (action.equals("resumeRecordingAudio")) {
127+
this.resumeRecordingAudio(args.getString(0));
122128
}
123129
else if (action.equals("startPlayingAudio")) {
124130
String target = args.getString(1);
@@ -282,13 +288,25 @@ public void startRecordingAudio(String id, String file) {
282288
}
283289

284290
/**
285-
* Stop recording and save to the file specified when recording started.
291+
* Stop/Pause recording and save to the file specified when recording started.
286292
* @param id The id of the audio player
293+
* @param stop If true stop recording, if false pause recording
287294
*/
288-
public void stopRecordingAudio(String id) {
295+
public void stopRecordingAudio(String id, boolean stop) {
289296
AudioPlayer audio = this.players.get(id);
290297
if (audio != null) {
291-
audio.stopRecording();
298+
audio.stopRecording(stop);
299+
}
300+
}
301+
302+
/**
303+
* Resume recording
304+
* @param id The id of the audio player
305+
*/
306+
public void resumeRecordingAudio(String id) {
307+
AudioPlayer audio = players.get(id);
308+
if (audio != null) {
309+
audio.resumeRecording();
292310
}
293311
}
294312

src/android/AudioPlayer.java

Lines changed: 99 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ Licensed to the Apache Software Foundation (ASF) under one
3232

3333
import java.io.File;
3434
import java.io.FileInputStream;
35+
import java.io.FileOutputStream;
36+
import java.io.InputStream;
37+
import java.io.OutputStream;
3538
import java.io.IOException;
39+
import java.util.LinkedList;
3640

3741
/**
3842
* This class implements the audio playback and recording capabilities used by Cordova.
@@ -81,7 +85,8 @@ public enum STATE { MEDIA_NONE,
8185
private float duration = -1; // Duration of audio
8286

8387
private MediaRecorder recorder = null; // Audio recording object
84-
private String tempFile = null; // Temporary recording file name
88+
private LinkedList<String> tempFiles = null; // Temporary recording file name
89+
private String tempFile = null;
8590

8691
private MediaPlayer player = null; // Audio player object
8792
private boolean prepareOnly = true; // playback after file prepare flag
@@ -98,13 +103,17 @@ public AudioPlayer(AudioHandler handler, String id, String file) {
98103
this.id = id;
99104
this.audioFile = file;
100105
this.recorder = new MediaRecorder();
106+
this.tempFiles = new LinkedList<String>();
107+
}
101108

102-
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
103-
this.tempFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tmprecording.3gp";
104-
} else {
105-
this.tempFile = "/data/data/" + handler.cordova.getActivity().getPackageName() + "/cache/tmprecording.3gp";
106-
}
107-
109+
private String generateTempFile() {
110+
String tempFileName = null;
111+
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
112+
tempFileName = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tmprecording-" + System.currentTimeMillis() + ".3gp";
113+
} else {
114+
tempFileName = "/data/data/" + handler.cordova.getActivity().getPackageName() + "/cache/tmprecording-" + System.currentTimeMillis() + ".3gp";
115+
}
116+
return tempFileName;
108117
}
109118

110119
/**
@@ -121,7 +130,7 @@ public void destroy() {
121130
this.player = null;
122131
}
123132
if (this.recorder != null) {
124-
this.stopRecording();
133+
this.stopRecording(true);
125134
this.recorder.release();
126135
this.recorder = null;
127136
}
@@ -141,8 +150,9 @@ public void startRecording(String file) {
141150
case NONE:
142151
this.audioFile = file;
143152
this.recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
144-
this.recorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT); // THREE_GPP);
145-
this.recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT); //AMR_NB);
153+
this.recorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR); // THREE_GPP);
154+
this.recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); //AMR_NB);
155+
this.tempFile = generateTempFile();
146156
this.recorder.setOutputFile(this.tempFile);
147157
try {
148158
this.recorder.prepare();
@@ -170,7 +180,6 @@ public void startRecording(String file) {
170180
*/
171181
public void moveFile(String file) {
172182
/* this is a hack to save the file as the specified name */
173-
File f = new File(this.tempFile);
174183

175184
if (!file.startsWith("/")) {
176185
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
@@ -180,30 +189,102 @@ public void moveFile(String file) {
180189
}
181190
}
182191

183-
String logMsg = "renaming " + this.tempFile + " to " + file;
184-
Log.d(LOG_TAG, logMsg);
185-
if (!f.renameTo(new File(file))) Log.e(LOG_TAG, "FAILED " + logMsg);
192+
int size = this.tempFiles.size();
193+
Log.d(LOG_TAG, "size = " + size);
194+
195+
// only one file so just copy it
196+
if (size == 1) {
197+
String logMsg = "renaming " + this.tempFile + " to " + file;
198+
Log.d(LOG_TAG, logMsg);
199+
File f = new File(this.tempFile);
200+
if (!f.renameTo(new File(file))) Log.e(LOG_TAG, "FAILED " + logMsg);
201+
}
202+
// more than one file so the user must have pause recording. We'll need to concat files.
203+
else {
204+
FileOutputStream outputStream = null;
205+
try {
206+
outputStream = new FileOutputStream(new File(file));
207+
FileInputStream inputStream = null;
208+
File inputFile = null;
209+
for (int i = 0; i < size; i++) {
210+
try {
211+
inputFile = new File(this.tempFiles.get(i));
212+
inputStream = new FileInputStream(inputFile);
213+
copy(inputStream, outputStream, (i>0));
214+
} catch(Exception e) {
215+
Log.e(LOG_TAG, e.getLocalizedMessage(), e);
216+
} finally {
217+
if (inputStream != null) try {
218+
inputStream.close();
219+
inputFile.delete();
220+
inputFile = null;
221+
} catch (Exception e) {
222+
Log.e(LOG_TAG, e.getLocalizedMessage(), e);
223+
}
224+
}
225+
}
226+
} catch(Exception e) {
227+
e.printStackTrace();
228+
} finally {
229+
if (outputStream != null) try {
230+
outputStream.close();
231+
} catch (Exception e) {
232+
Log.e(LOG_TAG, e.getLocalizedMessage(), e);
233+
}
234+
}
235+
}
236+
}
237+
238+
private static long copy(InputStream from, OutputStream to, boolean skipHeader)
239+
throws IOException {
240+
byte[] buf = new byte[8096];
241+
long total = 0;
242+
if (skipHeader) {
243+
from.skip(6);
244+
}
245+
while (true) {
246+
int r = from.read(buf);
247+
if (r == -1) {
248+
break;
249+
}
250+
to.write(buf, 0, r);
251+
total += r;
252+
}
253+
return total;
186254
}
187255

188256
/**
189-
* Stop recording and save to the file specified when recording started.
257+
* Stop/Pause recording and save to the file specified when recording started.
190258
*/
191-
public void stopRecording() {
259+
public void stopRecording(boolean stop) {
192260
if (this.recorder != null) {
193261
try{
194262
if (this.state == STATE.MEDIA_RUNNING) {
195263
this.recorder.stop();
196-
this.setState(STATE.MEDIA_STOPPED);
197264
}
198265
this.recorder.reset();
199-
this.moveFile(this.audioFile);
266+
this.tempFiles.add(this.tempFile);
267+
if (stop) {
268+
Log.d(LOG_TAG, "stopping recording");
269+
this.setState(STATE.MEDIA_STOPPED);
270+
this.moveFile(this.audioFile);
271+
} else {
272+
Log.d(LOG_TAG, "pause recording");
273+
}
200274
}
201275
catch (Exception e) {
202276
e.printStackTrace();
203277
}
204278
}
205279
}
206280

281+
/**
282+
* Resume recording and save to the file specified when recording started.
283+
*/
284+
public void resumeRecording() {
285+
startRecording(this.audioFile);
286+
}
287+
207288
//==========================================================================
208289
// Playback
209290
//==========================================================================

0 commit comments

Comments
 (0)