429 lines
18 KiB
Kotlin
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)
|
|
}
|
|
}
|
|
}
|