Simple-Voice-Recorder/app/src/main/kotlin/com/simplemobiletools/voicerecorder/activities/EditRecordingActivity.kt

429 lines
18 KiB
Kotlin
Raw Normal View History

2023-09-27 16:23:20 +02:00
package com.simplemobiletools.voicerecorder.activities
import android.graphics.drawable.Drawable
import android.media.AudioManager
import android.media.MediaPlayer
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.PowerManager
import android.provider.DocumentsContract
import android.widget.SeekBar
import androidx.core.view.children
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.voicerecorder.R
import com.simplemobiletools.voicerecorder.databinding.ActivityEditRecordingBinding
import com.simplemobiletools.voicerecorder.extensions.getAllRecordings
import com.simplemobiletools.voicerecorder.helpers.getAudioFileContentUri
import com.simplemobiletools.voicerecorder.models.Recording
import java.io.File
import java.util.Timer
import java.util.TimerTask
class EditRecordingActivity : SimpleActivity() {
companion object {
const val RECORDING_ID = "recording_id"
}
private var player: MediaPlayer? = null
private var progressTimer = Timer()
private lateinit var recording: Recording
private lateinit var currentRecording: Recording
private var progressStart: Float = 0f
private lateinit var binding: ActivityEditRecordingBinding
override fun onCreate(savedInstanceState: Bundle?) {
isMaterialActivity = true
super.onCreate(savedInstanceState)
binding = ActivityEditRecordingBinding.inflate(layoutInflater)
setContentView(binding.root)
setupOptionsMenu()
updateMaterialActivityViews(binding.mainCoordinator, binding.recordingVisualizer, useTransparentNavigation = false, useTopSearchMenu = false)
val recordingId = intent.getIntExtra(RECORDING_ID, -1)
if (recordingId == -1) {
finish()
return
}
recording = getAllRecordings().first { it.id == recordingId }
currentRecording = recording
// AudioTool.getInstance(this)
// .withAudio(File(recording.path))
// .cutAudio("00:00:00", "00:00:00.250") {}
// .saveCurrentTo(recording.path)
// .release()
// binding.recordingVisualizer.waveProgressColor = getProperPrimaryColor()
// binding.recordingVisualizer.setSampleFrom(recording.path)
binding.recordingVisualizer.chunkColor = getProperPrimaryColor()
binding.recordingVisualizer.recreate()
binding.recordingVisualizer.editListener = {
if (binding.recordingVisualizer.startPosition >= 0f) {
binding.settingsToolbar.menu.children.forEach { it.isVisible = true }
} else {
binding.settingsToolbar.menu.children.forEach { it.isVisible = false }
}
}
updateVisualization()
// android.media.MediaCodec.createByCodecName().createInputSurface()
// binding.recordingVisualizer.update()
initMediaPlayer()
playRecording(recording.path, recording.id, recording.title, recording.duration, false)
binding.playerControlsWrapper.playPauseBtn.setOnClickListener {
togglePlayPause()
}
setupColors()
}
private fun updateVisualization() {
/*Amplituda(this).apply {
2023-09-29 11:29:07 +02:00
try {
val uri = Uri.parse(currentRecording.path)
fun handleAmplitudes(amplitudaResult: AmplitudaResult<*>) {
binding.recordingVisualizer.recreate()
binding.recordingVisualizer.clearEditing()
binding.recordingVisualizer.putAmplitudes(amplitudaResult.amplitudesAsList())
}
when {
DocumentsContract.isDocumentUri(this@EditRecordingActivity, uri) -> {
processAudio(contentResolver.openInputStream(uri)).get(AmplitudaSuccessListener {
handleAmplitudes(it)
})
}
currentRecording.path.isEmpty() -> {
processAudio(contentResolver.openInputStream(getAudioFileContentUri(currentRecording.id.toLong()))).get(AmplitudaSuccessListener {
handleAmplitudes(it)
})
}
else -> {
processAudio(currentRecording.path).get(AmplitudaSuccessListener {
handleAmplitudes(it)
})
}
}
} catch (e: Exception) {
showErrorToast(e)
return
}
}*/
2023-09-27 16:23:20 +02:00
}
private fun setupColors() {
val properPrimaryColor = getProperPrimaryColor()
updateTextColors(binding.mainCoordinator)
val textColor = getProperTextColor()
arrayListOf(binding.playerControlsWrapper.previousBtn, binding.playerControlsWrapper.nextBtn).forEach {
it.applyColorFilter(textColor)
}
binding.playerControlsWrapper.playPauseBtn.background.applyColorFilter(properPrimaryColor)
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
}
private fun setupOptionsMenu() {
binding.settingsToolbar.inflateMenu(R.menu.menu_edit)
// binding.settingsToolbar.toggleHideOnScroll(false)
// binding.settingsToolbar.setupMenu()
// binding.settingsToolbar.onSearchOpenListener = {
// if (binding.viewPager.currentItem == 0) {
// binding.viewPager.currentItem = 1
// }
// }
// binding.settingsToolbar.onSearchTextChangedListener = { text ->
// getPagerAdapter()?.searchTextChanged(text)
// }
binding.settingsToolbar.menu.children.forEach { it.isVisible = false }
binding.settingsToolbar.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.play -> {
val start = binding.recordingVisualizer.startPosition
val end = binding.recordingVisualizer.endPosition
val startMillis = start * currentRecording.duration
val durationMillis = (end - start) * currentRecording.duration
val startMillisPart = String.format("%.3f", startMillis - startMillis.toInt()).replace("0.", "")
val durationMillisPart = String.format("%.3f", durationMillis - durationMillis.toInt()).replace("0.", "")
val startFormatted = (startMillis.toInt()).getFormattedDuration(true) + ".$startMillisPart"
val durationFormatted = (durationMillis.toInt()).getFormattedDuration(true) + ".$durationMillisPart"
/*modifyAudioFile(currentRecording)
2023-09-27 16:23:20 +02:00
.cutAudio(startFormatted, durationFormatted) {
progressStart = binding.recordingVisualizer.startPosition
playRecording(it.path, null, it.name, durationMillis.toInt(), true)
}
.release()*/
2023-09-27 16:23:20 +02:00
// playRecording()
}
2023-09-27 16:23:20 +02:00
R.id.cut -> {
val start = binding.recordingVisualizer.startPosition
val end = binding.recordingVisualizer.endPosition
val startMillis = start * currentRecording.duration
val endMillis = end * currentRecording.duration
val realEnd = (1 - end) * currentRecording.duration
val startMillisPart = String.format("%.3f", startMillis - startMillis.toInt()).replace("0.", "")
val endMillisPart = String.format("%.3f", endMillis - endMillis.toInt()).replace("0.", "")
val realEndMillisPart = String.format("%.3f", realEnd - realEnd.toInt()).replace("0.", "")
val startFormatted = (startMillis.toInt()).getFormattedDuration(true) + ".$startMillisPart"
val endFormatted = (endMillis.toInt()).getFormattedDuration(true) + ".$endMillisPart"
val realEndFormatted = (realEnd.toInt()).getFormattedDuration(true) + ".$realEndMillisPart"
var leftPart: File? = null
var rightPart: File? = null
fun merge() {
if (leftPart != null && rightPart != null) {
ensureBackgroundThread {
val tempFile =
File.createTempFile("${currentRecording.title}.edit.", ".${currentRecording.title.getFilenameExtension()}", cacheDir)
/*AudioTool.getInstance(this)
2023-09-29 11:29:07 +02:00
.joinAudios(arrayOf(leftPart, rightPart), tempFile.path) {
2023-09-27 16:23:20 +02:00
runOnUiThread {
currentRecording = Recording(
-1,
it.name,
it.path,
it.lastModified().toInt(),
(startMillis + realEnd).toInt(),
it.getProperSize(false).toInt()
)
2023-09-27 16:23:20 +02:00
updateVisualization()
playRecording(currentRecording.path, currentRecording.id, currentRecording.title, currentRecording.duration, true)
}
}*/
2023-09-27 16:23:20 +02:00
}
}
}
/*modifyAudioFile(currentRecording)
2023-09-27 16:23:20 +02:00
.cutAudio("00:00:00", startFormatted) {
leftPart = it
merge()
}
2023-09-29 11:29:07 +02:00
modifyAudioFile(currentRecording)
2023-09-27 16:23:20 +02:00
.cutAudio(endFormatted, realEndFormatted) {
rightPart = it
merge()
}*/
2023-09-27 16:23:20 +02:00
}
// R.id.save -> {
// binding.recordingVisualizer.clearEditing()
// currentRecording = recording
// playRecording(currentRecording.path, currentRecording.id, currentRecording.title, currentRecording.duration, true)
// }
R.id.clear -> {
progressStart = 0f
binding.recordingVisualizer.clearEditing()
playRecording(currentRecording.path, currentRecording.id, currentRecording.title, currentRecording.duration, true)
}
2023-09-27 16:23:20 +02:00
R.id.reset -> {
progressStart = 0f
binding.recordingVisualizer.clearEditing()
currentRecording = recording
2023-09-29 11:29:07 +02:00
updateVisualization()
2023-09-27 16:23:20 +02:00
playRecording(currentRecording.path, currentRecording.id, currentRecording.title, currentRecording.duration, true)
}
2023-09-27 16:23:20 +02:00
else -> return@setOnMenuItemClickListener false
}
return@setOnMenuItemClickListener true
}
}
private fun initMediaPlayer() {
player = MediaPlayer().apply {
setWakeMode(this@EditRecordingActivity, PowerManager.PARTIAL_WAKE_LOCK)
setAudioStreamType(AudioManager.STREAM_MUSIC)
setOnCompletionListener {
progressTimer.cancel()
binding.playerControlsWrapper.playerProgressbar.progress = binding.playerControlsWrapper.playerProgressbar.max
binding.playerControlsWrapper.playerProgressCurrent.text = binding.playerControlsWrapper.playerProgressMax.text
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
}
setOnPreparedListener {
// setupProgressTimer()
// player?.start()
}
}
}
fun playRecording(path: String, id: Int?, title: String?, duration: Int?, playOnPrepared: Boolean) {
resetProgress(title, duration)
// (binding.recordingsList.adapter as RecordingsAdapter).updateCurrentRecording(recording.id)
// playOnPreparation = playOnPrepared
player!!.apply {
reset()
try {
val uri = Uri.parse(path)
when {
DocumentsContract.isDocumentUri(this@EditRecordingActivity, uri) -> {
setDataSource(this@EditRecordingActivity, uri)
}
path.isEmpty() -> {
setDataSource(this@EditRecordingActivity, getAudioFileContentUri(id?.toLong() ?: 0))
}
else -> {
setDataSource(path)
}
}
} catch (e: Exception) {
showErrorToast(e)
return
}
try {
prepareAsync()
} catch (e: Exception) {
showErrorToast(e)
return
}
}
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
binding.playerControlsWrapper.playerProgressbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
player?.seekTo(progress * 1000)
binding.playerControlsWrapper.playerProgressCurrent.text = progress.getFormattedDuration()
resumePlayback()
}
}
override fun onStartTrackingTouch(seekBar: SeekBar) {}
override fun onStopTrackingTouch(seekBar: SeekBar) {}
})
}
private fun setupProgressTimer() {
progressTimer.cancel()
progressTimer = Timer()
progressTimer.scheduleAtFixedRate(getProgressUpdateTask(), 100, 100)
}
private fun getProgressUpdateTask() = object : TimerTask() {
override fun run() {
Handler(Looper.getMainLooper()).post {
if (player != null) {
binding.recordingVisualizer.updateProgress(player!!.currentPosition.toFloat() / (currentRecording.duration * 1000) + progressStart)
val progress = Math.round(player!!.currentPosition / 1000.toDouble()).toInt()
updateCurrentProgress(progress)
binding.playerControlsWrapper.playerProgressbar.progress = progress
}
}
}
}
private fun updateCurrentProgress(seconds: Int) {
binding.playerControlsWrapper.playerProgressCurrent.text = seconds.getFormattedDuration()
}
private fun resetProgress(title: String?, duration: Int?) {
updateCurrentProgress(0)
binding.playerControlsWrapper.playerProgressbar.progress = 0
binding.playerControlsWrapper.playerProgressbar.max = duration ?: 0
binding.playerControlsWrapper.playerTitle.text = title ?: ""
binding.playerControlsWrapper.playerProgressMax.text = (duration ?: 0).getFormattedDuration()
}
private fun togglePlayPause() {
if (getIsPlaying()) {
pausePlayback()
} else {
resumePlayback()
}
}
private fun pausePlayback() {
player?.pause()
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
progressTimer.cancel()
}
private fun resumePlayback() {
player?.start()
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(true))
setupProgressTimer()
}
private fun getToggleButtonIcon(isPlaying: Boolean): Drawable {
val drawable = if (isPlaying) com.simplemobiletools.commons.R.drawable.ic_pause_vector else com.simplemobiletools.commons.R.drawable.ic_play_vector
return resources.getColoredDrawableWithColor(drawable, getProperPrimaryColor().getContrastColor())
}
private fun skip(forward: Boolean) {
// val curr = player?.currentPosition ?: return
// var newProgress = if (forward) curr + FAST_FORWARD_SKIP_MS else curr - FAST_FORWARD_SKIP_MS
// if (newProgress > player!!.duration) {
// newProgress = player!!.duration
// }
//
// player!!.seekTo(newProgress)
// resumePlayback()
}
private fun getIsPlaying() = player?.isPlaying == true
override fun onResume() {
super.onResume()
}
override fun onPause() {
super.onPause()
}
2023-09-29 11:29:07 +02:00
/*private fun modifyAudioFile(recording: Recording): AudioTool {
2023-09-29 11:29:07 +02:00
return AudioTool.getInstance(this)
.withAudio(copyToTempFile(recording))
}*/
2023-09-29 11:29:07 +02:00
private fun copyToTempFile(recording: Recording): File {
try {
val uri = Uri.parse(recording.path)
when {
DocumentsContract.isDocumentUri(this@EditRecordingActivity, uri) -> {
val tempFile = File.createTempFile(recording.title, ".${recording.title.getFilenameExtension()}", cacheDir)
contentResolver.openInputStream(uri)?.copyTo(tempFile.outputStream())
return tempFile
}
recording.path.isEmpty() -> {
val tempFile = File.createTempFile(recording.title, ".${recording.title.getFilenameExtension()}", cacheDir)
contentResolver.openInputStream(getAudioFileContentUri(recording.id.toLong()))?.copyTo(tempFile.outputStream())
return tempFile
}
else -> {
return File(recording.path)
}
}
} catch (e: Exception) {
showErrorToast(e)
return File(recording.path)
}
}
2023-09-27 16:23:20 +02:00
}