implement time out for waiting on voice recording
This commit is contained in:
parent
1af52a7a30
commit
f597fc46da
|
@ -21,6 +21,8 @@ import java.io.IOException;
|
|||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
|
@ -30,167 +32,180 @@ import eu.siacs.conversations.utils.ThemeHelper;
|
|||
|
||||
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 long mStartTime = 0;
|
||||
private MediaRecorder mRecorder;
|
||||
private long mStartTime = 0;
|
||||
|
||||
private Handler mHandler = new Handler();
|
||||
private Runnable mTickExecutor = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
tick();
|
||||
mHandler.postDelayed(mTickExecutor, 100);
|
||||
}
|
||||
};
|
||||
private CountDownLatch outputFileWrittenLatch = new CountDownLatch(1);
|
||||
|
||||
private File mOutputFile;
|
||||
private boolean mShouldFinishAfterWrite = false;
|
||||
private Handler mHandler = new Handler();
|
||||
private Runnable mTickExecutor = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
tick();
|
||||
mHandler.postDelayed(mTickExecutor, 100);
|
||||
}
|
||||
};
|
||||
|
||||
private FileObserver mFileObserver;
|
||||
private File mOutputFile;
|
||||
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
private FileObserver mFileObserver;
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
if (!startRecording()) {
|
||||
this.binding.shareButton.setEnabled(false);
|
||||
this.binding.timer.setTextAppearance(this, R.style.TextAppearance_Conversations_Title);
|
||||
this.binding.timer.setText(R.string.unable_to_start_recording);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
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
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
if (mRecorder != null) {
|
||||
mHandler.removeCallbacks(mTickExecutor);
|
||||
stopRecording(false);
|
||||
}
|
||||
if (mFileObserver != null) {
|
||||
mFileObserver.stopWatching();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
if (!startRecording()) {
|
||||
this.binding.shareButton.setEnabled(false);
|
||||
this.binding.timer.setTextAppearance(this, R.style.TextAppearance_Conversations_Title);
|
||||
this.binding.timer.setText(R.string.unable_to_start_recording);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean startRecording() {
|
||||
mRecorder = new MediaRecorder();
|
||||
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
|
||||
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
|
||||
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
|
||||
mRecorder.setAudioEncodingBitRate(96000);
|
||||
mRecorder.setAudioSamplingRate(22050);
|
||||
setupOutputFile();
|
||||
mRecorder.setOutputFile(mOutputFile.getAbsolutePath());
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
if (mRecorder != null) {
|
||||
mHandler.removeCallbacks(mTickExecutor);
|
||||
stopRecording(false);
|
||||
}
|
||||
if (mFileObserver != null) {
|
||||
mFileObserver.stopWatching();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
mRecorder.prepare();
|
||||
mRecorder.start();
|
||||
mStartTime = SystemClock.elapsedRealtime();
|
||||
mHandler.postDelayed(mTickExecutor, 100);
|
||||
Log.d("Voice Recorder", "started recording to " + mOutputFile.getAbsolutePath());
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log.e("Voice Recorder", "prepare() failed " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private boolean startRecording() {
|
||||
mRecorder = new MediaRecorder();
|
||||
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
|
||||
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
|
||||
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
|
||||
mRecorder.setAudioEncodingBitRate(96000);
|
||||
mRecorder.setAudioSamplingRate(22050);
|
||||
setupOutputFile();
|
||||
mRecorder.setOutputFile(mOutputFile.getAbsolutePath());
|
||||
|
||||
protected void stopRecording(boolean saveFile) {
|
||||
mShouldFinishAfterWrite = saveFile;
|
||||
try {
|
||||
mRecorder.stop();
|
||||
mRecorder.release();
|
||||
} catch (Exception e) {
|
||||
if (saveFile) {
|
||||
Toast.makeText(this,R.string.unable_to_save_recording, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} finally {
|
||||
mRecorder = null;
|
||||
mStartTime = 0;
|
||||
}
|
||||
if (!saveFile && mOutputFile != null) {
|
||||
if (mOutputFile.delete()) {
|
||||
Log.d(Config.LOGTAG,"deleted canceled recording");
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
mRecorder.prepare();
|
||||
mRecorder.start();
|
||||
mStartTime = SystemClock.elapsedRealtime();
|
||||
mHandler.postDelayed(mTickExecutor, 100);
|
||||
Log.d("Voice Recorder", "started recording to " + mOutputFile.getAbsolutePath());
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
Log.e("Voice Recorder", "prepare() failed " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static File generateOutputFilename(Context context) {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
|
||||
String filename = "RECORDING_" + dateFormat.format(new Date()) + ".m4a";
|
||||
return new File(FileBackend.getConversationsDirectory(context, STORAGE_DIRECTORY_TYPE_NAME) + "/" + filename);
|
||||
}
|
||||
protected void stopRecording(final boolean saveFile) {
|
||||
try {
|
||||
mRecorder.stop();
|
||||
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() {
|
||||
mOutputFile = generateOutputFilename(this);
|
||||
File parentDirectory = mOutputFile.getParentFile();
|
||||
if (parentDirectory.mkdirs()) {
|
||||
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 static File generateOutputFilename(Context context) {
|
||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
|
||||
String filename = "RECORDING_" + dateFormat.format(new Date()) + ".m4a";
|
||||
return new File(FileBackend.getConversationsDirectory(context, STORAGE_DIRECTORY_TYPE_NAME) + "/" + filename);
|
||||
}
|
||||
|
||||
private void setupFileObserver(File directory) {
|
||||
mFileObserver = new FileObserver(directory.getAbsolutePath()) {
|
||||
@Override
|
||||
public void onEvent(int event, String s) {
|
||||
if (s != null && s.equals(mOutputFile.getName()) && event == FileObserver.CLOSE_WRITE) {
|
||||
if (mShouldFinishAfterWrite) {
|
||||
setResult(Activity.RESULT_OK, new Intent().setData(Uri.fromFile(mOutputFile)));
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
mFileObserver.startWatching();
|
||||
}
|
||||
private void setupOutputFile() {
|
||||
mOutputFile = generateOutputFilename(this);
|
||||
File parentDirectory = mOutputFile.getParentFile();
|
||||
if (parentDirectory.mkdirs()) {
|
||||
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);
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void tick() {
|
||||
long time = (mStartTime < 0) ? 0 : (SystemClock.elapsedRealtime() - mStartTime);
|
||||
int minutes = (int) (time / 60000);
|
||||
int seconds = (int) (time / 1000) % 60;
|
||||
int milliseconds = (int) (time / 100) % 10;
|
||||
this.binding.timer.setText(minutes + ":" + (seconds < 10 ? "0" + seconds : seconds) + "." + milliseconds);
|
||||
}
|
||||
private void setupFileObserver(File directory) {
|
||||
mFileObserver = new FileObserver(directory.getAbsolutePath()) {
|
||||
@Override
|
||||
public void onEvent(int event, String s) {
|
||||
if (s != null && s.equals(mOutputFile.getName()) && event == FileObserver.CLOSE_WRITE) {
|
||||
outputFileWrittenLatch.countDown();
|
||||
}
|
||||
}
|
||||
};
|
||||
mFileObserver.startWatching();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
switch (view.getId()) {
|
||||
case R.id.cancel_button:
|
||||
mHandler.removeCallbacks(mTickExecutor);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@SuppressLint("SetTextI18n")
|
||||
private void tick() {
|
||||
long time = (mStartTime < 0) ? 0 : (SystemClock.elapsedRealtime() - mStartTime);
|
||||
int minutes = (int) (time / 60000);
|
||||
int seconds = (int) (time / 1000) % 60;
|
||||
int milliseconds = (int) (time / 100) % 10;
|
||||
this.binding.timer.setText(minutes + ":" + (seconds < 10 ? "0" + seconds : seconds) + "." + milliseconds);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
switch (view.getId()) {
|
||||
case R.id.cancel_button:
|
||||
mHandler.removeCallbacks(mTickExecutor);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue