implement time out for waiting on voice recording

This commit is contained in:
Daniel Gultsch 2019-07-23 17:31:56 +02:00
parent 1af52a7a30
commit f597fc46da
1 changed files with 162 additions and 147 deletions

View File

@ -21,6 +21,8 @@ import java.io.IOException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.Locale; import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
@ -30,167 +32,180 @@ import eu.siacs.conversations.utils.ThemeHelper;
public class RecordingActivity extends Activity implements View.OnClickListener { public class RecordingActivity extends Activity implements View.OnClickListener {
public static String STORAGE_DIRECTORY_TYPE_NAME = "Recordings"; public static String STORAGE_DIRECTORY_TYPE_NAME = "Recordings";
private ActivityRecordingBinding binding; private ActivityRecordingBinding binding;
private MediaRecorder mRecorder; private MediaRecorder mRecorder;
private long mStartTime = 0; private long mStartTime = 0;
private Handler mHandler = new Handler(); private CountDownLatch outputFileWrittenLatch = new CountDownLatch(1);
private Runnable mTickExecutor = new Runnable() {
@Override
public void run() {
tick();
mHandler.postDelayed(mTickExecutor, 100);
}
};
private File mOutputFile; private Handler mHandler = new Handler();
private boolean mShouldFinishAfterWrite = false; private Runnable mTickExecutor = new Runnable() {
@Override
public void run() {
tick();
mHandler.postDelayed(mTickExecutor, 100);
}
};
private FileObserver mFileObserver; private File mOutputFile;
@Override private FileObserver mFileObserver;
protected void onCreate(Bundle savedInstanceState) {
setTheme(ThemeHelper.findDialog(this));
super.onCreate(savedInstanceState);
this.binding = DataBindingUtil.setContentView(this,R.layout.activity_recording);
this.binding.cancelButton.setOnClickListener(this);
this.binding.shareButton.setOnClickListener(this);
this.setFinishOnTouchOutside(false);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
@Override @Override
protected void onStart() { protected void onCreate(Bundle savedInstanceState) {
super.onStart(); setTheme(ThemeHelper.findDialog(this));
if (!startRecording()) { super.onCreate(savedInstanceState);
this.binding.shareButton.setEnabled(false); this.binding = DataBindingUtil.setContentView(this, R.layout.activity_recording);
this.binding.timer.setTextAppearance(this, R.style.TextAppearance_Conversations_Title); this.binding.cancelButton.setOnClickListener(this);
this.binding.timer.setText(R.string.unable_to_start_recording); this.binding.shareButton.setOnClickListener(this);
} this.setFinishOnTouchOutside(false);
} getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
@Override @Override
protected void onStop() { protected void onStart() {
super.onStop(); super.onStart();
if (mRecorder != null) { if (!startRecording()) {
mHandler.removeCallbacks(mTickExecutor); this.binding.shareButton.setEnabled(false);
stopRecording(false); this.binding.timer.setTextAppearance(this, R.style.TextAppearance_Conversations_Title);
} this.binding.timer.setText(R.string.unable_to_start_recording);
if (mFileObserver != null) { }
mFileObserver.stopWatching(); }
}
}
private boolean startRecording() { @Override
mRecorder = new MediaRecorder(); protected void onStop() {
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); super.onStop();
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); if (mRecorder != null) {
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mHandler.removeCallbacks(mTickExecutor);
mRecorder.setAudioEncodingBitRate(96000); stopRecording(false);
mRecorder.setAudioSamplingRate(22050); }
setupOutputFile(); if (mFileObserver != null) {
mRecorder.setOutputFile(mOutputFile.getAbsolutePath()); mFileObserver.stopWatching();
}
}
try { private boolean startRecording() {
mRecorder.prepare(); mRecorder = new MediaRecorder();
mRecorder.start(); mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mStartTime = SystemClock.elapsedRealtime(); mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mHandler.postDelayed(mTickExecutor, 100); mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
Log.d("Voice Recorder", "started recording to " + mOutputFile.getAbsolutePath()); mRecorder.setAudioEncodingBitRate(96000);
return true; mRecorder.setAudioSamplingRate(22050);
} catch (Exception e) { setupOutputFile();
Log.e("Voice Recorder", "prepare() failed " + e.getMessage()); mRecorder.setOutputFile(mOutputFile.getAbsolutePath());
return false;
}
}
protected void stopRecording(boolean saveFile) { try {
mShouldFinishAfterWrite = saveFile; mRecorder.prepare();
try { mRecorder.start();
mRecorder.stop(); mStartTime = SystemClock.elapsedRealtime();
mRecorder.release(); mHandler.postDelayed(mTickExecutor, 100);
} catch (Exception e) { Log.d("Voice Recorder", "started recording to " + mOutputFile.getAbsolutePath());
if (saveFile) { return true;
Toast.makeText(this,R.string.unable_to_save_recording, Toast.LENGTH_SHORT).show(); } catch (Exception e) {
} Log.e("Voice Recorder", "prepare() failed " + e.getMessage());
} finally { return false;
mRecorder = null; }
mStartTime = 0; }
}
if (!saveFile && mOutputFile != null) {
if (mOutputFile.delete()) {
Log.d(Config.LOGTAG,"deleted canceled recording");
}
}
}
private static File generateOutputFilename(Context context) { protected void stopRecording(final boolean saveFile) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US); try {
String filename = "RECORDING_" + dateFormat.format(new Date()) + ".m4a"; mRecorder.stop();
return new File(FileBackend.getConversationsDirectory(context, STORAGE_DIRECTORY_TYPE_NAME) + "/" + filename); mRecorder.release();
} } catch (Exception e) {
if (saveFile) {
Toast.makeText(this, R.string.unable_to_save_recording, Toast.LENGTH_SHORT).show();
return;
}
} finally {
mRecorder = null;
mStartTime = 0;
}
if (!saveFile && mOutputFile != null) {
if (mOutputFile.delete()) {
Log.d(Config.LOGTAG, "deleted canceled recording");
}
}
if (saveFile) {
new Thread(() -> {
try {
if (!outputFileWrittenLatch.await(2, TimeUnit.SECONDS)) {
Log.d(Config.LOGTAG, "time out waiting for output file to be written");
}
} catch (InterruptedException e) {
Log.d(Config.LOGTAG, "interrupted while waiting for output file to be written" ,e);
}
runOnUiThread(() -> {
setResult(Activity.RESULT_OK, new Intent().setData(Uri.fromFile(mOutputFile)));
finish();
});
}).start();
}
}
private void setupOutputFile() { private static File generateOutputFilename(Context context) {
mOutputFile = generateOutputFilename(this); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
File parentDirectory = mOutputFile.getParentFile(); String filename = "RECORDING_" + dateFormat.format(new Date()) + ".m4a";
if (parentDirectory.mkdirs()) { return new File(FileBackend.getConversationsDirectory(context, STORAGE_DIRECTORY_TYPE_NAME) + "/" + filename);
Log.d(Config.LOGTAG, "created " + parentDirectory.getAbsolutePath()); }
}
File noMedia = new File(parentDirectory, ".nomedia");
if (!noMedia.exists()) {
try {
if (noMedia.createNewFile()) {
Log.d(Config.LOGTAG, "created nomedia file in " + parentDirectory.getAbsolutePath());
}
} catch (IOException e) {
Log.d(Config.LOGTAG, "unable to create nomedia file in " + parentDirectory.getAbsolutePath(), e);
}
}
setupFileObserver(parentDirectory);
}
private void setupFileObserver(File directory) { private void setupOutputFile() {
mFileObserver = new FileObserver(directory.getAbsolutePath()) { mOutputFile = generateOutputFilename(this);
@Override File parentDirectory = mOutputFile.getParentFile();
public void onEvent(int event, String s) { if (parentDirectory.mkdirs()) {
if (s != null && s.equals(mOutputFile.getName()) && event == FileObserver.CLOSE_WRITE) { Log.d(Config.LOGTAG, "created " + parentDirectory.getAbsolutePath());
if (mShouldFinishAfterWrite) { }
setResult(Activity.RESULT_OK, new Intent().setData(Uri.fromFile(mOutputFile))); File noMedia = new File(parentDirectory, ".nomedia");
finish(); if (!noMedia.exists()) {
} try {
} if (noMedia.createNewFile()) {
} Log.d(Config.LOGTAG, "created nomedia file in " + parentDirectory.getAbsolutePath());
}; }
mFileObserver.startWatching(); } catch (IOException e) {
} Log.d(Config.LOGTAG, "unable to create nomedia file in " + parentDirectory.getAbsolutePath(), e);
}
}
setupFileObserver(parentDirectory);
}
@SuppressLint("SetTextI18n") private void setupFileObserver(File directory) {
private void tick() { mFileObserver = new FileObserver(directory.getAbsolutePath()) {
long time = (mStartTime < 0) ? 0 : (SystemClock.elapsedRealtime() - mStartTime); @Override
int minutes = (int) (time / 60000); public void onEvent(int event, String s) {
int seconds = (int) (time / 1000) % 60; if (s != null && s.equals(mOutputFile.getName()) && event == FileObserver.CLOSE_WRITE) {
int milliseconds = (int) (time / 100) % 10; outputFileWrittenLatch.countDown();
this.binding.timer.setText(minutes + ":" + (seconds < 10 ? "0" + seconds : seconds) + "." + milliseconds); }
} }
};
mFileObserver.startWatching();
}
@Override @SuppressLint("SetTextI18n")
public void onClick(View view) { private void tick() {
switch (view.getId()) { long time = (mStartTime < 0) ? 0 : (SystemClock.elapsedRealtime() - mStartTime);
case R.id.cancel_button: int minutes = (int) (time / 60000);
mHandler.removeCallbacks(mTickExecutor); int seconds = (int) (time / 1000) % 60;
stopRecording(false); int milliseconds = (int) (time / 100) % 10;
setResult(RESULT_CANCELED); this.binding.timer.setText(minutes + ":" + (seconds < 10 ? "0" + seconds : seconds) + "." + milliseconds);
finish(); }
break;
case R.id.share_button: @Override
this.binding.shareButton.setEnabled(false); public void onClick(View view) {
this.binding.shareButton.setText(R.string.please_wait); switch (view.getId()) {
mHandler.removeCallbacks(mTickExecutor); case R.id.cancel_button:
mHandler.postDelayed(() -> stopRecording(true), 500); mHandler.removeCallbacks(mTickExecutor);
break; stopRecording(false);
} setResult(RESULT_CANCELED);
} finish();
break;
case R.id.share_button:
this.binding.shareButton.setEnabled(false);
this.binding.shareButton.setText(R.string.please_wait);
mHandler.removeCallbacks(mTickExecutor);
mHandler.postDelayed(() -> stopRecording(true), 500);
break;
}
}
} }