WIP: Audio editor
This commit is contained in:
parent
15d23dbca1
commit
cd62cabb52
|
@ -100,5 +100,7 @@ dependencies {
|
||||||
implementation(libs.androidx.swiperefreshlayout)
|
implementation(libs.androidx.swiperefreshlayout)
|
||||||
implementation(libs.androidx.constraintlayout)
|
implementation(libs.androidx.constraintlayout)
|
||||||
implementation(libs.tandroidlame)
|
implementation(libs.tandroidlame)
|
||||||
|
implementation(libs.bundles.audiotool)
|
||||||
|
implementation(libs.bundles.amplituda)
|
||||||
implementation(libs.autofittextview)
|
implementation(libs.autofittextview)
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,6 +77,8 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".activities.EditRecordingActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".activities.SettingsActivity"
|
android:name=".activities.SettingsActivity"
|
||||||
android:configChanges="orientation"
|
android:configChanges="orientation"
|
||||||
|
|
|
@ -0,0 +1,362 @@
|
||||||
|
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.deleteRecordings
|
||||||
|
import com.simplemobiletools.voicerecorder.extensions.getAllRecordings
|
||||||
|
import com.simplemobiletools.voicerecorder.helpers.getAudioFileContentUri
|
||||||
|
import com.simplemobiletools.voicerecorder.models.Recording
|
||||||
|
import linc.com.amplituda.Amplituda
|
||||||
|
import linc.com.amplituda.callback.AmplitudaSuccessListener
|
||||||
|
import linc.com.library.AudioTool
|
||||||
|
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).processAudio(currentRecording.path)
|
||||||
|
.get(AmplitudaSuccessListener {
|
||||||
|
binding.recordingVisualizer.recreate()
|
||||||
|
binding.recordingVisualizer.clearEditing()
|
||||||
|
binding.recordingVisualizer.putAmplitudes(it.amplitudesAsList())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
AudioTool.getInstance(this)
|
||||||
|
.withAudio(File(currentRecording.path))
|
||||||
|
.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 {
|
||||||
|
AudioTool.getInstance(this)
|
||||||
|
.joinAudios(arrayOf(leftPart, rightPart), "${currentRecording.path}.edit.${currentRecording.path.getFilenameExtension()}") {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioTool.getInstance(this)
|
||||||
|
.withAudio(File(currentRecording.path))
|
||||||
|
.cutAudio("00:00:00", startFormatted) {
|
||||||
|
leftPart = it
|
||||||
|
merge()
|
||||||
|
}
|
||||||
|
AudioTool.getInstance(this)
|
||||||
|
.withAudio(File(currentRecording.path))
|
||||||
|
.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
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package com.simplemobiletools.voicerecorder.adapters
|
package com.simplemobiletools.voicerecorder.adapters
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.PopupMenu
|
import android.widget.PopupMenu
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
@ -11,6 +12,7 @@ import com.simplemobiletools.commons.helpers.isQPlus
|
||||||
import com.simplemobiletools.commons.views.MyRecyclerView
|
import com.simplemobiletools.commons.views.MyRecyclerView
|
||||||
import com.simplemobiletools.voicerecorder.BuildConfig
|
import com.simplemobiletools.voicerecorder.BuildConfig
|
||||||
import com.simplemobiletools.voicerecorder.R
|
import com.simplemobiletools.voicerecorder.R
|
||||||
|
import com.simplemobiletools.voicerecorder.activities.EditRecordingActivity
|
||||||
import com.simplemobiletools.voicerecorder.activities.SimpleActivity
|
import com.simplemobiletools.voicerecorder.activities.SimpleActivity
|
||||||
import com.simplemobiletools.voicerecorder.databinding.ItemRecordingBinding
|
import com.simplemobiletools.voicerecorder.databinding.ItemRecordingBinding
|
||||||
import com.simplemobiletools.voicerecorder.dialogs.DeleteConfirmationDialog
|
import com.simplemobiletools.voicerecorder.dialogs.DeleteConfirmationDialog
|
||||||
|
@ -281,6 +283,13 @@ class RecordingsAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.cab_edit -> {
|
||||||
|
Intent(activity, EditRecordingActivity::class.java).apply {
|
||||||
|
putExtra(EditRecordingActivity.RECORDING_ID, recordingId)
|
||||||
|
activity.startActivity(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
R.id.cab_delete -> {
|
R.id.cab_delete -> {
|
||||||
executeItemMenuOperation(recordingId, removeAfterCallback = false) {
|
executeItemMenuOperation(recordingId, removeAfterCallback = false) {
|
||||||
askConfirmDelete()
|
askConfirmDelete()
|
||||||
|
|
|
@ -84,23 +84,23 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupViews() {
|
private fun setupViews() {
|
||||||
binding.playPauseBtn.setOnClickListener {
|
binding.playerControlsWrapper.playPauseBtn.setOnClickListener {
|
||||||
if (playedRecordingIDs.empty() || binding.playerProgressbar.max == 0) {
|
if (playedRecordingIDs.empty() || binding.playerControlsWrapper.playerProgressbar.max == 0) {
|
||||||
binding.nextBtn.callOnClick()
|
binding.playerControlsWrapper.nextBtn.callOnClick()
|
||||||
} else {
|
} else {
|
||||||
togglePlayPause()
|
togglePlayPause()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.playerProgressCurrent.setOnClickListener {
|
binding.playerControlsWrapper.playerProgressCurrent.setOnClickListener {
|
||||||
skip(false)
|
skip(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.playerProgressMax.setOnClickListener {
|
binding.playerControlsWrapper.playerProgressMax.setOnClickListener {
|
||||||
skip(true)
|
skip(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.previousBtn.setOnClickListener {
|
binding.playerControlsWrapper.previousBtn.setOnClickListener {
|
||||||
if (playedRecordingIDs.isEmpty()) {
|
if (playedRecordingIDs.isEmpty()) {
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
}
|
}
|
||||||
|
@ -116,14 +116,14 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
||||||
playRecording(prevRecording, true)
|
playRecording(prevRecording, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.playerTitle.setOnLongClickListener {
|
binding.playerControlsWrapper.playerTitle.setOnLongClickListener {
|
||||||
if (binding.playerTitle.value.isNotEmpty()) {
|
if (binding.playerControlsWrapper.playerTitle.value.isNotEmpty()) {
|
||||||
context.copyToClipboard(binding.playerTitle.value)
|
context.copyToClipboard(binding.playerControlsWrapper.playerTitle.value)
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.nextBtn.setOnClickListener {
|
binding.playerControlsWrapper.nextBtn.setOnClickListener {
|
||||||
val adapter = getRecordingsAdapter()
|
val adapter = getRecordingsAdapter()
|
||||||
if (adapter == null || adapter.recordings.isEmpty()) {
|
if (adapter == null || adapter.recordings.isEmpty()) {
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
|
@ -193,9 +193,9 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
||||||
|
|
||||||
setOnCompletionListener {
|
setOnCompletionListener {
|
||||||
progressTimer.cancel()
|
progressTimer.cancel()
|
||||||
binding.playerProgressbar.progress = binding.playerProgressbar.max
|
binding.playerControlsWrapper.playerProgressbar.progress = binding.playerControlsWrapper.playerProgressbar.max
|
||||||
binding.playerProgressCurrent.text = binding.playerProgressMax.text
|
binding.playerControlsWrapper.playerProgressCurrent.text = binding.playerControlsWrapper.playerProgressMax.text
|
||||||
binding.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnPreparedListener {
|
setOnPreparedListener {
|
||||||
|
@ -245,12 +245,12 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.playPauseBtn.setImageDrawable(getToggleButtonIcon(playOnPreparation))
|
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(playOnPreparation))
|
||||||
binding.playerProgressbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
binding.playerControlsWrapper.playerProgressbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||||
if (fromUser && !playedRecordingIDs.isEmpty()) {
|
if (fromUser && !playedRecordingIDs.isEmpty()) {
|
||||||
player?.seekTo(progress * 1000)
|
player?.seekTo(progress * 1000)
|
||||||
binding.playerProgressCurrent.text = progress.getFormattedDuration()
|
binding.playerControlsWrapper.playerProgressCurrent.text = progress.getFormattedDuration()
|
||||||
resumePlayback()
|
resumePlayback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,22 +273,22 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
val progress = Math.round(player!!.currentPosition / 1000.toDouble()).toInt()
|
val progress = Math.round(player!!.currentPosition / 1000.toDouble()).toInt()
|
||||||
updateCurrentProgress(progress)
|
updateCurrentProgress(progress)
|
||||||
binding.playerProgressbar.progress = progress
|
binding.playerControlsWrapper.playerProgressbar.progress = progress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateCurrentProgress(seconds: Int) {
|
private fun updateCurrentProgress(seconds: Int) {
|
||||||
binding.playerProgressCurrent.text = seconds.getFormattedDuration()
|
binding.playerControlsWrapper.playerProgressCurrent.text = seconds.getFormattedDuration()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetProgress(recording: Recording?) {
|
private fun resetProgress(recording: Recording?) {
|
||||||
updateCurrentProgress(0)
|
updateCurrentProgress(0)
|
||||||
binding.playerProgressbar.progress = 0
|
binding.playerControlsWrapper.playerProgressbar.progress = 0
|
||||||
binding.playerProgressbar.max = recording?.duration ?: 0
|
binding.playerControlsWrapper.playerProgressbar.max = recording?.duration ?: 0
|
||||||
binding.playerTitle.text = recording?.title ?: ""
|
binding.playerControlsWrapper.playerTitle.text = recording?.title ?: ""
|
||||||
binding.playerProgressMax.text = (recording?.duration ?: 0).getFormattedDuration()
|
binding.playerControlsWrapper.playerProgressMax.text = (recording?.duration ?: 0).getFormattedDuration()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSearchTextChanged(text: String) {
|
fun onSearchTextChanged(text: String) {
|
||||||
|
@ -307,13 +307,13 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
||||||
|
|
||||||
private fun pausePlayback() {
|
private fun pausePlayback() {
|
||||||
player?.pause()
|
player?.pause()
|
||||||
binding.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
||||||
progressTimer.cancel()
|
progressTimer.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resumePlayback() {
|
private fun resumePlayback() {
|
||||||
player?.start()
|
player?.start()
|
||||||
binding.playPauseBtn.setImageDrawable(getToggleButtonIcon(true))
|
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(true))
|
||||||
setupProgressTimer()
|
setupProgressTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -352,12 +352,12 @@ class PlayerFragment(context: Context, attributeSet: AttributeSet) : MyViewPager
|
||||||
context.updateTextColors(binding.playerHolder)
|
context.updateTextColors(binding.playerHolder)
|
||||||
|
|
||||||
val textColor = context.getProperTextColor()
|
val textColor = context.getProperTextColor()
|
||||||
arrayListOf(binding.previousBtn, binding.nextBtn).forEach {
|
arrayListOf(binding.playerControlsWrapper.previousBtn, binding.playerControlsWrapper.nextBtn).forEach {
|
||||||
it.applyColorFilter(textColor)
|
it.applyColorFilter(textColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.playPauseBtn.background.applyColorFilter(properPrimaryColor)
|
binding.playerControlsWrapper.playPauseBtn.background.applyColorFilter(properPrimaryColor)
|
||||||
binding.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
binding.playerControlsWrapper.playPauseBtn.setImageDrawable(getToggleButtonIcon(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun finishActMode() = getRecordingsAdapter()?.finishActMode()
|
fun finishActMode() = getRecordingsAdapter()?.finishActMode()
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
package com.simplemobiletools.voicerecorder.views
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import com.simplemobiletools.commons.extensions.adjustAlpha
|
||||||
|
import com.simplemobiletools.commons.extensions.getProperPrimaryColor
|
||||||
|
import com.simplemobiletools.commons.extensions.getProperTextColor
|
||||||
|
import com.simplemobiletools.commons.helpers.LOWER_ALPHA
|
||||||
|
import com.visualizer.amplitude.dp
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
class AudioEditorView @JvmOverloads constructor(
|
||||||
|
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||||
|
) : View(context, attrs, defStyleAttr) {
|
||||||
|
private val chunkPaint = Paint()
|
||||||
|
private val highlightPaint = Paint()
|
||||||
|
private val progressPaint = Paint()
|
||||||
|
|
||||||
|
private var chunks = ArrayList<Float>()
|
||||||
|
private var topBottomPadding = 6.dp()
|
||||||
|
|
||||||
|
private var startX: Float = -1f
|
||||||
|
private var endX: Float = -1f
|
||||||
|
|
||||||
|
private var currentProgress: Float = 0f
|
||||||
|
|
||||||
|
private enum class Dragging {
|
||||||
|
START,
|
||||||
|
END,
|
||||||
|
NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
private var dragging = Dragging.NONE
|
||||||
|
|
||||||
|
var startPosition: Float = -1f
|
||||||
|
var endPosition: Float = -1f
|
||||||
|
|
||||||
|
var editListener: (() -> Unit)? = null
|
||||||
|
|
||||||
|
var chunkColor = Color.RED
|
||||||
|
set(value) {
|
||||||
|
chunkPaint.color = value
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
private var chunkWidth = 20.dp()
|
||||||
|
set(value) {
|
||||||
|
chunkPaint.strokeWidth = value
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
private var chunkSpace = 1.dp()
|
||||||
|
var chunkMinHeight = 3.dp() // recommended size > 10 dp
|
||||||
|
var chunkRoundedCorners = false
|
||||||
|
set(value) {
|
||||||
|
if (value) {
|
||||||
|
chunkPaint.strokeCap = Paint.Cap.ROUND
|
||||||
|
} else {
|
||||||
|
chunkPaint.strokeCap = Paint.Cap.BUTT
|
||||||
|
}
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
chunkPaint.strokeWidth = chunkWidth
|
||||||
|
chunkPaint.color = chunkColor
|
||||||
|
chunkRoundedCorners = false
|
||||||
|
highlightPaint.color = context.getProperPrimaryColor().adjustAlpha(LOWER_ALPHA)
|
||||||
|
progressPaint.color = context.getProperTextColor()
|
||||||
|
progressPaint.strokeWidth = 4.dp()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun recreate() {
|
||||||
|
chunks.clear()
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearEditing() {
|
||||||
|
startX = -1f
|
||||||
|
endX = -1f
|
||||||
|
startPosition = -1f
|
||||||
|
endPosition = -1f
|
||||||
|
editListener?.invoke()
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun putAmplitudes(amplitudes: List<Int>) {
|
||||||
|
val maxAmp = amplitudes.max()
|
||||||
|
chunkWidth = (1.0f / amplitudes.size) * (2.0f / 3)
|
||||||
|
chunkSpace = chunkWidth / 2
|
||||||
|
|
||||||
|
chunks.addAll(amplitudes.map { it.toFloat() / maxAmp })
|
||||||
|
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) {
|
||||||
|
super.onDraw(canvas)
|
||||||
|
if (chunkWidth < 1f) {
|
||||||
|
chunkWidth *= width
|
||||||
|
chunkSpace = chunkWidth / 2
|
||||||
|
}
|
||||||
|
val verticalCenter = height / 2
|
||||||
|
var x = chunkSpace
|
||||||
|
val maxHeight = height - (topBottomPadding * 2)
|
||||||
|
val verticalDrawScale = maxHeight - chunkMinHeight
|
||||||
|
if (verticalDrawScale == 0f) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
chunks.forEach {
|
||||||
|
val chunkHeight = it * verticalDrawScale + chunkMinHeight
|
||||||
|
val startY = verticalCenter - chunkHeight / 2
|
||||||
|
val stopY = verticalCenter + chunkHeight / 2
|
||||||
|
|
||||||
|
canvas.drawLine(x, startY, x, stopY, chunkPaint)
|
||||||
|
x += chunkWidth + chunkSpace
|
||||||
|
}
|
||||||
|
|
||||||
|
if (startPosition >= 0f || startX >= 0f ) {
|
||||||
|
val start: Float
|
||||||
|
val end: Float
|
||||||
|
if (startX >= 0f) {
|
||||||
|
start = startX
|
||||||
|
end = endX
|
||||||
|
} else {
|
||||||
|
start = width * startPosition
|
||||||
|
end = width * endPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.drawRect(start, 0f, end, height.toFloat(), highlightPaint)
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas.drawLine(width * currentProgress, 0f, width * currentProgress, height.toFloat(), progressPaint)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTouchEvent(event: MotionEvent?): Boolean {
|
||||||
|
when (event?.actionMasked) {
|
||||||
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
if (abs(event.x - startPosition * width) < 50.0f) {
|
||||||
|
startX = event.x
|
||||||
|
endX = endPosition * width
|
||||||
|
dragging = Dragging.START
|
||||||
|
} else if (abs(event.x - endPosition * width) < 50.0f) {
|
||||||
|
endX = event.x
|
||||||
|
startX = startPosition * width
|
||||||
|
dragging = Dragging.END
|
||||||
|
} else {
|
||||||
|
startX = event.x
|
||||||
|
endX = event.x
|
||||||
|
dragging = Dragging.END
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_MOVE -> {
|
||||||
|
if (dragging == Dragging.START) {
|
||||||
|
startX = event.x
|
||||||
|
} else if (dragging == Dragging.END) {
|
||||||
|
endX = event.x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_UP -> {
|
||||||
|
if (dragging == Dragging.START) {
|
||||||
|
startX = event.x
|
||||||
|
} else if (dragging == Dragging.END) {
|
||||||
|
endX = event.x
|
||||||
|
}
|
||||||
|
dragging = Dragging.NONE
|
||||||
|
startPosition = min(startX, endX) / width
|
||||||
|
endPosition = max(startX, endX) / width
|
||||||
|
startX = -1f
|
||||||
|
endX = -1f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
invalidate()
|
||||||
|
editListener?.invoke()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateProgress(progress: Float) {
|
||||||
|
currentProgress = progress
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M9.64,7.64c0.23,-0.5 0.36,-1.05 0.36,-1.64 0,-2.21 -1.79,-4 -4,-4S2,3.79 2,6s1.79,4 4,4c0.59,0 1.14,-0.13 1.64,-0.36L10,12l-2.36,2.36C7.14,14.13 6.59,14 6,14c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4c0,-0.59 -0.13,-1.14 -0.36,-1.64L12,14l7,7h3v-1L9.64,7.64zM6,8c-1.1,0 -2,-0.89 -2,-2s0.9,-2 2,-2 2,0.89 2,2 -0.9,2 -2,2zM6,20c-1.1,0 -2,-0.89 -2,-2s0.9,-2 2,-2 2,0.89 2,2 -0.9,2 -2,2zM12,12.5c-0.28,0 -0.5,-0.22 -0.5,-0.5s0.22,-0.5 0.5,-0.5 0.5,0.22 0.5,0.5 -0.22,0.5 -0.5,0.5zM19,3l-6,6 2,2 7,-7L22,3z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/main_coordinator"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/settings_toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="@color/color_primary"
|
||||||
|
app:title="@string/settings"
|
||||||
|
app:titleTextAppearance="@style/AppTheme.ActionBar.TitleTextStyle" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/content_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="?attr/actionBarSize">
|
||||||
|
|
||||||
|
<com.simplemobiletools.voicerecorder.views.AudioEditorView
|
||||||
|
android:id="@+id/recording_visualizer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/player_controls_wrapper"
|
||||||
|
android:layout_marginTop="?attr/actionBarSize"
|
||||||
|
android:layout_marginStart="@dimen/big_margin"
|
||||||
|
android:layout_marginEnd="@dimen/big_margin" />
|
||||||
|
|
||||||
|
<include
|
||||||
|
android:id="@+id/player_controls_wrapper"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/recording_visualizer"
|
||||||
|
layout="@layout/layout_player_controls" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -1,7 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<com.simplemobiletools.voicerecorder.fragments.PlayerFragment xmlns:android="http://schemas.android.com/apk/res/android"
|
<com.simplemobiletools.voicerecorder.fragments.PlayerFragment xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/player_holder"
|
android:id="@+id/player_holder"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
@ -37,120 +36,7 @@
|
||||||
|
|
||||||
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
|
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
|
||||||
|
|
||||||
<RelativeLayout
|
<include
|
||||||
android:id="@+id/player_controls_wrapper"
|
android:id="@+id/player_controls_wrapper"
|
||||||
android:layout_width="match_parent"
|
layout="@layout/layout_player_controls" />
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
tools:ignore="HardcodedText">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/player_divider"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1px"
|
|
||||||
android:background="@color/divider_grey"
|
|
||||||
android:importantForAccessibility="no" />
|
|
||||||
|
|
||||||
<com.simplemobiletools.commons.views.MyTextView
|
|
||||||
android:id="@+id/player_title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="@dimen/medium_margin"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:padding="@dimen/activity_margin"
|
|
||||||
android:textSize="@dimen/big_text_size"
|
|
||||||
tools:text="Recording title" />
|
|
||||||
|
|
||||||
<com.simplemobiletools.commons.views.MyTextView
|
|
||||||
android:id="@+id/player_progress_current"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@+id/player_controls"
|
|
||||||
android:layout_alignTop="@+id/player_progressbar"
|
|
||||||
android:layout_alignBottom="@+id/player_progressbar"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:paddingStart="@dimen/normal_margin"
|
|
||||||
android:paddingEnd="@dimen/medium_margin"
|
|
||||||
android:text="00:00" />
|
|
||||||
|
|
||||||
<com.simplemobiletools.commons.views.MySeekBar
|
|
||||||
android:id="@+id/player_progressbar"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@+id/player_title"
|
|
||||||
android:layout_toStartOf="@+id/player_progress_max"
|
|
||||||
android:layout_toEndOf="@+id/player_progress_current"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:max="0"
|
|
||||||
android:paddingTop="@dimen/normal_margin"
|
|
||||||
android:paddingBottom="@dimen/normal_margin" />
|
|
||||||
|
|
||||||
<com.simplemobiletools.commons.views.MyTextView
|
|
||||||
android:id="@+id/player_progress_max"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignTop="@+id/player_progressbar"
|
|
||||||
android:layout_alignBottom="@+id/player_progressbar"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:paddingStart="@dimen/medium_margin"
|
|
||||||
android:paddingEnd="@dimen/normal_margin"
|
|
||||||
android:text="00:00" />
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
android:id="@+id/player_controls"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_below="@+id/player_progressbar"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/previous_btn"
|
|
||||||
android:layout_width="@dimen/normal_icon_size"
|
|
||||||
android:layout_height="@dimen/normal_icon_size"
|
|
||||||
android:layout_alignTop="@+id/play_pause_btn"
|
|
||||||
android:layout_alignBottom="@+id/play_pause_btn"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_toStartOf="@+id/play_pause_btn"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/previous"
|
|
||||||
android:padding="@dimen/medium_margin"
|
|
||||||
android:src="@drawable/ic_previous_vector" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/play_pause_btn"
|
|
||||||
android:layout_width="@dimen/toggle_recording_button_size"
|
|
||||||
android:layout_height="@dimen/toggle_recording_button_size"
|
|
||||||
android:layout_centerHorizontal="true"
|
|
||||||
android:layout_marginStart="@dimen/player_button_margin"
|
|
||||||
android:layout_marginEnd="@dimen/player_button_margin"
|
|
||||||
android:layout_marginBottom="@dimen/big_margin"
|
|
||||||
android:background="@drawable/circle_background"
|
|
||||||
android:contentDescription="@string/playpause"
|
|
||||||
android:padding="@dimen/activity_margin"
|
|
||||||
android:src="@drawable/ic_play_vector" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/next_btn"
|
|
||||||
android:layout_width="@dimen/normal_icon_size"
|
|
||||||
android:layout_height="@dimen/normal_icon_size"
|
|
||||||
android:layout_alignTop="@+id/play_pause_btn"
|
|
||||||
android:layout_alignBottom="@+id/play_pause_btn"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_toEndOf="@+id/play_pause_btn"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/next"
|
|
||||||
android:padding="@dimen/medium_margin"
|
|
||||||
android:src="@drawable/ic_next_vector" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
||||||
</RelativeLayout>
|
|
||||||
</com.simplemobiletools.voicerecorder.fragments.PlayerFragment>
|
</com.simplemobiletools.voicerecorder.fragments.PlayerFragment>
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<RelativeLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/player_controls_wrapper"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:ignore="HardcodedText"
|
||||||
|
tools:showIn="@layout/fragment_player">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/player_divider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1px"
|
||||||
|
android:background="@color/divider_grey"
|
||||||
|
android:importantForAccessibility="no" />
|
||||||
|
|
||||||
|
<com.simplemobiletools.commons.views.MyTextView
|
||||||
|
android:id="@+id/player_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/medium_margin"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:padding="@dimen/activity_margin"
|
||||||
|
android:textSize="@dimen/big_text_size"
|
||||||
|
tools:text="Recording title" />
|
||||||
|
|
||||||
|
<com.simplemobiletools.commons.views.MyTextView
|
||||||
|
android:id="@+id/player_progress_current"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/player_controls"
|
||||||
|
android:layout_alignTop="@+id/player_progressbar"
|
||||||
|
android:layout_alignBottom="@+id/player_progressbar"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:paddingStart="@dimen/normal_margin"
|
||||||
|
android:paddingEnd="@dimen/medium_margin"
|
||||||
|
android:text="00:00" />
|
||||||
|
|
||||||
|
<com.simplemobiletools.commons.views.MySeekBar
|
||||||
|
android:id="@+id/player_progressbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/player_title"
|
||||||
|
android:layout_toStartOf="@+id/player_progress_max"
|
||||||
|
android:layout_toEndOf="@+id/player_progress_current"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:max="0"
|
||||||
|
android:paddingTop="@dimen/normal_margin"
|
||||||
|
android:paddingBottom="@dimen/normal_margin" />
|
||||||
|
|
||||||
|
<com.simplemobiletools.commons.views.MyTextView
|
||||||
|
android:id="@+id/player_progress_max"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignTop="@+id/player_progressbar"
|
||||||
|
android:layout_alignBottom="@+id/player_progressbar"
|
||||||
|
android:layout_alignParentEnd="true"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:paddingStart="@dimen/medium_margin"
|
||||||
|
android:paddingEnd="@dimen/normal_margin"
|
||||||
|
android:text="00:00" />
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/player_controls"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/player_progressbar"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/previous_btn"
|
||||||
|
android:layout_width="@dimen/normal_icon_size"
|
||||||
|
android:layout_height="@dimen/normal_icon_size"
|
||||||
|
android:layout_alignTop="@+id/play_pause_btn"
|
||||||
|
android:layout_alignBottom="@+id/play_pause_btn"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_toStartOf="@+id/play_pause_btn"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/previous"
|
||||||
|
android:padding="@dimen/medium_margin"
|
||||||
|
android:src="@drawable/ic_previous_vector" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/play_pause_btn"
|
||||||
|
android:layout_width="@dimen/toggle_recording_button_size"
|
||||||
|
android:layout_height="@dimen/toggle_recording_button_size"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginStart="@dimen/player_button_margin"
|
||||||
|
android:layout_marginEnd="@dimen/player_button_margin"
|
||||||
|
android:layout_marginBottom="@dimen/big_margin"
|
||||||
|
android:background="@drawable/circle_background"
|
||||||
|
android:contentDescription="@string/playpause"
|
||||||
|
android:padding="@dimen/activity_margin"
|
||||||
|
android:src="@drawable/ic_play_vector" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/next_btn"
|
||||||
|
android:layout_width="@dimen/normal_icon_size"
|
||||||
|
android:layout_height="@dimen/normal_icon_size"
|
||||||
|
android:layout_alignTop="@+id/play_pause_btn"
|
||||||
|
android:layout_alignBottom="@+id/play_pause_btn"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_toEndOf="@+id/play_pause_btn"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/next"
|
||||||
|
android:padding="@dimen/medium_margin"
|
||||||
|
android:src="@drawable/ic_next_vector" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
</RelativeLayout>
|
|
@ -18,6 +18,12 @@
|
||||||
android:icon="@drawable/ic_delete_vector"
|
android:icon="@drawable/ic_delete_vector"
|
||||||
android:showAsAction="always"
|
android:showAsAction="always"
|
||||||
android:title="@string/delete" />
|
android:title="@string/delete" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/cab_edit"
|
||||||
|
android:icon="@drawable/ic_edit_vector"
|
||||||
|
android:showAsAction="ifRoom"
|
||||||
|
android:title="@string/edit"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
<item
|
<item
|
||||||
android:id="@+id/cab_open_with"
|
android:id="@+id/cab_open_with"
|
||||||
android:showAsAction="never"
|
android:showAsAction="never"
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
tools:ignore="AlwaysShowAction">
|
||||||
|
<item
|
||||||
|
android:id="@+id/play"
|
||||||
|
android:icon="@drawable/ic_play_vector"
|
||||||
|
android:title="Play"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/cut"
|
||||||
|
android:icon="@drawable/ic_cut_vector"
|
||||||
|
android:title="Cut"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
<!-- <item-->
|
||||||
|
<!-- android:id="@+id/save"-->
|
||||||
|
<!-- android:icon="@drawable/ic_save_vector"-->
|
||||||
|
<!-- android:title="Save"-->
|
||||||
|
<!-- app:showAsAction="ifRoom" />-->
|
||||||
|
<item
|
||||||
|
android:id="@+id/reset"
|
||||||
|
android:icon="@drawable/ic_reset_vector"
|
||||||
|
android:title="Reset"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/clear"
|
||||||
|
android:icon="@drawable/ic_cross_vector"
|
||||||
|
android:title="Clear"
|
||||||
|
app:showAsAction="ifRoom" />
|
||||||
|
</menu>
|
|
@ -15,6 +15,11 @@ room = "2.6.0-alpha02"
|
||||||
simple-commons = "257a2ab069"
|
simple-commons = "257a2ab069"
|
||||||
#AudioRecordView
|
#AudioRecordView
|
||||||
audiorecordview = "1.0.4"
|
audiorecordview = "1.0.4"
|
||||||
|
#AudioTool
|
||||||
|
audiotool = "1.2.1"
|
||||||
|
amplituda = "2.2.2"
|
||||||
|
waveformseekbar = "5.0.1"
|
||||||
|
mobileffmpeg = "4.4"
|
||||||
#TAndroidLame
|
#TAndroidLame
|
||||||
tandroidlame = "1.1"
|
tandroidlame = "1.1"
|
||||||
#AutofitTextView
|
#AutofitTextView
|
||||||
|
@ -24,7 +29,7 @@ gradlePlugins-agp = "8.1.1"
|
||||||
#build
|
#build
|
||||||
app-build-compileSDKVersion = "34"
|
app-build-compileSDKVersion = "34"
|
||||||
app-build-targetSDK = "34"
|
app-build-targetSDK = "34"
|
||||||
app-build-minimumSDK = "23"
|
app-build-minimumSDK = "24"
|
||||||
app-build-javaVersion = "VERSION_17"
|
app-build-javaVersion = "VERSION_17"
|
||||||
app-build-kotlinJVMTarget = "17"
|
app-build-kotlinJVMTarget = "17"
|
||||||
#versioning
|
#versioning
|
||||||
|
@ -46,6 +51,11 @@ simple-tools-commons = { module = "com.github.SimpleMobileTools:Simple-Commons",
|
||||||
eventbus = { module = "org.greenrobot:eventbus", version.ref = "eventbus" }
|
eventbus = { module = "org.greenrobot:eventbus", version.ref = "eventbus" }
|
||||||
#AudioRecordView
|
#AudioRecordView
|
||||||
audiorecordview = { module = "com.github.Armen101:AudioRecordView", version.ref = "audiorecordview" }
|
audiorecordview = { module = "com.github.Armen101:AudioRecordView", version.ref = "audiorecordview" }
|
||||||
|
#AudioTool
|
||||||
|
audiotool = { module = "com.github.lincollincol:AudioTool", version.ref = "audiotool" }
|
||||||
|
amplituda = { module = "com.github.lincollincol:amplituda", version.ref = "amplituda" }
|
||||||
|
mobileffmpeg = { module = "com.arthenica:mobile-ffmpeg-full", version.ref = "mobileffmpeg" }
|
||||||
|
waveformseekbar = { module = "com.github.massoudss:waveformSeekBar", version.ref = "waveformseekbar" }
|
||||||
#TAndroidLame
|
#TAndroidLame
|
||||||
tandroidlame = { module = "com.github.naman14:TAndroidLame", version.ref = "tandroidlame" }
|
tandroidlame = { module = "com.github.naman14:TAndroidLame", version.ref = "tandroidlame" }
|
||||||
#AutofitTextView
|
#AutofitTextView
|
||||||
|
@ -55,6 +65,14 @@ room = [
|
||||||
"androidx-room-ktx",
|
"androidx-room-ktx",
|
||||||
"androidx-room-runtime",
|
"androidx-room-runtime",
|
||||||
]
|
]
|
||||||
|
audiotool = [
|
||||||
|
"audiotool",
|
||||||
|
"mobileffmpeg",
|
||||||
|
]
|
||||||
|
amplituda = [
|
||||||
|
"amplituda",
|
||||||
|
"waveformseekbar",
|
||||||
|
]
|
||||||
[plugins]
|
[plugins]
|
||||||
android = { id = "com.android.application", version.ref = "gradlePlugins-agp" }
|
android = { id = "com.android.application", version.ref = "gradlePlugins-agp" }
|
||||||
kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
|
|
Loading…
Reference in New Issue