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) } } }