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

429 lines
18 KiB
Kotlin

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 {
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
}
}*/
}
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)
.cutAudio(startFormatted, durationFormatted) {
progressStart = binding.recordingVisualizer.startPosition
playRecording(it.path, null, it.name, durationMillis.toInt(), true)
}
.release()*/
// playRecording()
}
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)
.joinAudios(arrayOf(leftPart, rightPart), tempFile.path) {
runOnUiThread {
currentRecording = Recording(
-1,
it.name,
it.path,
it.lastModified().toInt(),
(startMillis + realEnd).toInt(),
it.getProperSize(false).toInt()
)
updateVisualization()
playRecording(currentRecording.path, currentRecording.id, currentRecording.title, currentRecording.duration, true)
}
}*/
}
}
}
/*modifyAudioFile(currentRecording)
.cutAudio("00:00:00", startFormatted) {
leftPart = it
merge()
}
modifyAudioFile(currentRecording)
.cutAudio(endFormatted, realEndFormatted) {
rightPart = it
merge()
}*/
}
// 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)
}
R.id.reset -> {
progressStart = 0f
binding.recordingVisualizer.clearEditing()
currentRecording = recording
updateVisualization()
playRecording(currentRecording.path, currentRecording.id, currentRecording.title, currentRecording.duration, true)
}
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()
}
/*private fun modifyAudioFile(recording: Recording): AudioTool {
return AudioTool.getInstance(this)
.withAudio(copyToTempFile(recording))
}*/
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)
}
}
}