Skip to content

Commit

Permalink
- Fixed bug with occasional crash with stop
Browse files Browse the repository at this point in the history
- Implement MediaRecorderState handling

Signed-off-by: Julius Linus <[email protected]>
  • Loading branch information
rapterjet2004 committed Nov 21, 2023
1 parent c4b5051 commit ec44763
Showing 1 changed file with 85 additions and 65 deletions.
150 changes: 85 additions & 65 deletions app/src/main/java/com/nextcloud/talk/chat/ChatActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -340,16 +340,24 @@ class ChatActivity :

private val filesToUpload: MutableList<String> = ArrayList()
private lateinit var sharedText: String
var isVoiceRecordingInProgress: Boolean = false
var currentVoiceRecordFile: String = ""
var isVoiceRecordingLocked: Boolean = false
private var isVoicePreviewPlaying: Boolean = false

private var recorder: MediaRecorder? = null
private enum class MediaRecorderState {
INITIAL, INITIALIZED, CONFIGURED, PREPARED, RECORDING, RELEASED, ERROR
}
private var mediaRecorderState: MediaRecorderState = MediaRecorderState.INITIAL

private var voicePreviewMediaPlayer: MediaPlayer? = null
private var voicePreviewObjectAnimator: ObjectAnimator? = null

var mediaPlayer: MediaPlayer? = null
lateinit var mediaPlayerHandler: Handler

private var currentlyPlayedVoiceMessage: ChatMessage? = null

private lateinit var micInputAudioRecorder: AudioRecord
private var micInputAudioRecordThread: Thread? = null
private var isMicInputAudioThreadRunning: Boolean = false
Expand All @@ -358,6 +366,7 @@ class ChatActivity :
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT
)

private var voiceRecordDuration = 0L

// messy workaround for a mediaPlayer bug, don't delete
Expand Down Expand Up @@ -517,7 +526,7 @@ class ChatActivity :
if (isMicInputAudioThreadRunning) {
stopMicInputRecordingAnimation()
}
if (isVoiceRecordingInProgress) {
if (mediaRecorderState == MediaRecorderState.RECORDING) {
stopAudioRecording()
}
if (currentlyPlayedVoiceMessage != null) {
Expand Down Expand Up @@ -1036,7 +1045,7 @@ class ChatActivity :
} else {
showMicrophoneButton(true)
}
} else if (isVoiceRecordingInProgress) {
} else if (mediaRecorderState == MediaRecorderState.RECORDING) {
binding.messageInputView.playPauseBtn.visibility = View.GONE
binding.messageInputView.seekBar.visibility = View.GONE
} else {
Expand Down Expand Up @@ -1074,8 +1083,9 @@ class ChatActivity :
binding.messageInputView.seekBar.setOnTouchListener(OnTouchListener { _, _ -> true })

binding.messageInputView.micInputCloud.setOnClickListener {
if (isVoiceRecordingInProgress) {
if (mediaRecorderState == MediaRecorderState.RECORDING) {
recorder?.stop()
mediaRecorderState = MediaRecorderState.INITIAL
stopMicInputRecordingAnimation()
voiceRecordPauseTime = binding.messageInputView.audioRecordDuration.base - SystemClock.elapsedRealtime()
binding.messageInputView.audioRecordDuration.stop()
Expand All @@ -1095,7 +1105,7 @@ class ChatActivity :
sendVoiceRecordingLayoutParams.removeRule(BELOW)
sendVoiceRecordingLayoutParams.addRule(BELOW, R.id.voice_preview_container)
} else {
restartAudio()
initMediaRecorder(currentVoiceRecordFile)
startMicInputRecordingAnimation()
binding.messageInputView.audioRecordDuration.base = SystemClock.elapsedRealtime()
binding.messageInputView.audioRecordDuration.start()
Expand All @@ -1109,8 +1119,6 @@ class ChatActivity :
sendVoiceRecordingLayoutParams.removeRule(BELOW)
sendVoiceRecordingLayoutParams.addRule(BELOW, R.id.audioRecordDuration)
}

isVoiceRecordingInProgress = !isVoiceRecordingInProgress
}

binding.messageInputView.deleteVoiceRecording.setOnClickListener {
Expand Down Expand Up @@ -1175,7 +1183,7 @@ class ChatActivity :

MotionEvent.ACTION_CANCEL -> {
Log.d(TAG, "ACTION_CANCEL. same as for UP")
if (!isVoiceRecordingInProgress || !isRecordAudioPermissionGranted()) {
if (mediaRecorderState != MediaRecorderState.RECORDING || !isRecordAudioPermissionGranted()) {
return true
}

Expand All @@ -1186,7 +1194,7 @@ class ChatActivity :

MotionEvent.ACTION_UP -> {
Log.d(TAG, "ACTION_UP. stop recording??")
if (!isVoiceRecordingInProgress ||
if (mediaRecorderState != MediaRecorderState.RECORDING ||
!isRecordAudioPermissionGranted() ||
isVoiceRecordingLocked
) {
Expand Down Expand Up @@ -1217,7 +1225,7 @@ class ChatActivity :
MotionEvent.ACTION_MOVE -> {
Log.d(TAG, "ACTION_MOVE.")

if (!isVoiceRecordingInProgress || !isRecordAudioPermissionGranted()) {
if (mediaRecorderState != MediaRecorderState.RECORDING || !isRecordAudioPermissionGranted()) {
return true
}

Expand Down Expand Up @@ -1326,20 +1334,6 @@ class ChatActivity :
}
}

private fun restartAudio() {
recorder = MediaRecorder().apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setOutputFile(currentVoiceRecordFile)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
setAudioSamplingRate(VOICE_MESSAGE_SAMPLING_RATE)
setAudioEncodingBitRate(VOICE_MESSAGE_ENCODING_BIT_RATE)
setAudioChannels(VOICE_MESSAGE_CHANNELS)
prepare()
start()
}
}

private fun endVoiceRecordingUI() {
stopPreviewVoicePlaying()
showRecordAudioUi(false)
Expand Down Expand Up @@ -2075,36 +2069,40 @@ class ChatActivity :
)
isMicInputAudioThreadRunning = true
micInputAudioRecorder.startRecording()
micInputAudioRecordThread = Thread(
Runnable {
while (isMicInputAudioThreadRunning) {
val byteArr = ByteArray(bufferSize / 2)
micInputAudioRecorder.read(byteArr, 0, byteArr.size)
val d = Math.abs(byteArr[0].toDouble())
if (d > AUDIO_VALUE_MAX) {
binding.messageInputView.micInputCloud.setRotationSpeed(
Math.log10(d).toFloat(),
MicInputCloud.MAXIMUM_RADIUS
)
} else if (d > AUDIO_VALUE_MIN) {
binding.messageInputView.micInputCloud.setRotationSpeed(
Math.log10(d).toFloat(),
MicInputCloud.EXTENDED_RADIUS
)
} else {
binding.messageInputView.micInputCloud.setRotationSpeed(
1f,
MicInputCloud.DEFAULT_RADIUS
)
}
Thread.sleep(AUDIO_VALUE_SLEEP)
}
}
)
initMicInputAudioRecordThread()
micInputAudioRecordThread!!.start()
}
}

private fun initMicInputAudioRecordThread() {
micInputAudioRecordThread = Thread(
Runnable {
while (isMicInputAudioThreadRunning) {
val byteArr = ByteArray(bufferSize / 2)
micInputAudioRecorder.read(byteArr, 0, byteArr.size)
val d = Math.abs(byteArr[0].toDouble())
if (d > AUDIO_VALUE_MAX) {
binding.messageInputView.micInputCloud.setRotationSpeed(
Math.log10(d).toFloat(),
MicInputCloud.MAXIMUM_RADIUS
)
} else if (d > AUDIO_VALUE_MIN) {
binding.messageInputView.micInputCloud.setRotationSpeed(
Math.log10(d).toFloat(),
MicInputCloud.EXTENDED_RADIUS
)
} else {
binding.messageInputView.micInputCloud.setRotationSpeed(
1f,
MicInputCloud.DEFAULT_RADIUS
)
}
Thread.sleep(AUDIO_VALUE_SLEEP)
}
}
)
}

private fun stopMicInputRecordingAnimation() {
if (micInputAudioRecordThread != null) {
Log.d(TAG, "Mic Animation Ended")
Expand Down Expand Up @@ -2133,46 +2131,64 @@ class ChatActivity :
animation.repeatMode = Animation.REVERSE
binding.messageInputView.microphoneEnabledInfo.startAnimation(animation)

initMediaRecorder(file)
VibrationUtils.vibrateShort(context)
}

private fun initMediaRecorder(file: String) {
recorder = MediaRecorder().apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setOutputFile(file)
mediaRecorderState = MediaRecorderState.INITIALIZED

setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
mediaRecorderState = MediaRecorderState.CONFIGURED

setOutputFile(file)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
setAudioSamplingRate(VOICE_MESSAGE_SAMPLING_RATE)
setAudioEncodingBitRate(VOICE_MESSAGE_ENCODING_BIT_RATE)
setAudioChannels(VOICE_MESSAGE_CHANNELS)

try {
prepare()
mediaRecorderState = MediaRecorderState.PREPARED
} catch (e: IOException) {
mediaRecorderState = MediaRecorderState.ERROR
Log.e(TAG, "prepare for audio recording failed")
}

try {
start()
mediaRecorderState = MediaRecorderState.RECORDING
Log.d(TAG, "recording started")
isVoiceRecordingInProgress = true
} catch (e: IllegalStateException) {
mediaRecorderState = MediaRecorderState.ERROR
Log.e(TAG, "start for audio recording failed")
}

VibrationUtils.vibrateShort(context)
}
}

private fun stopAndSendAudioRecording() {
stopAudioRecording()
Log.d(TAG, "stopped and sent audio recording")
val uri = Uri.fromFile(File(currentVoiceRecordFile))
uploadFile(uri.toString(), true)

if (mediaRecorderState != MediaRecorderState.ERROR) {
val uri = Uri.fromFile(File(currentVoiceRecordFile))
uploadFile(uri.toString(), true)
} else {
mediaRecorderState = MediaRecorderState.INITIAL
}
}

private fun stopAndDiscardAudioRecording() {
stopAudioRecording()
Log.d(TAG, "stopped and discarded audio recording")

val cachedFile = File(currentVoiceRecordFile)
cachedFile.delete()

if (mediaRecorderState == MediaRecorderState.ERROR) {
mediaRecorderState = MediaRecorderState.INITIAL
}
}

@Suppress("Detekt.TooGenericExceptionCaught")
Expand All @@ -2182,17 +2198,21 @@ class ChatActivity :

recorder?.apply {
try {
Log.d(TAG, "recording stopped with $voiceRecordDuration")
if (voiceRecordDuration > MINIMUM_VOICE_RECORD_TO_STOP) {
if (mediaRecorderState == MediaRecorderState.RECORDING) {
stop()
mediaRecorderState = MediaRecorderState.INITIAL
Log.d(TAG, "stopped recorder")
}
release()
isVoiceRecordingInProgress = false
Log.d(TAG, "stopped recorder. isVoiceRecordingInProgress = false")
} catch (e: java.lang.IllegalStateException) {
error("error while stopping recorder!" + e)
} catch (e: java.lang.RuntimeException) {
error("error while stopping recorder!" + e)
mediaRecorderState = MediaRecorderState.RELEASED
} catch (e: Exception) {
when (e) {
is java.lang.IllegalStateException,
is java.lang.RuntimeException -> {
mediaRecorderState = MediaRecorderState.ERROR
Log.e(TAG, "error while stopping recorder! with state $mediaRecorderState $e")
}
}
}

VibrationUtils.vibrateShort(context)
Expand Down

0 comments on commit ec44763

Please sign in to comment.