moving the actual recording into the service

This commit is contained in:
tibbi
2020-03-30 15:16:44 +02:00
parent 8255def4cb
commit 9cea12a1e2
3 changed files with 133 additions and 126 deletions

View File

@ -38,6 +38,6 @@ android {
} }
dependencies { dependencies {
implementation 'com.simplemobiletools:commons:5.24.3' implementation 'com.simplemobiletools:commons:5.24.4'
implementation 'org.greenrobot:eventbus:3.2.0' implementation 'org.greenrobot:eventbus:3.2.0'
} }

View File

@ -1,20 +1,13 @@
package com.simplemobiletools.voicerecorder.activities package com.simplemobiletools.voicerecorder.activities
import android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Intent import android.content.Intent
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.media.MediaRecorder
import android.media.MediaScannerConnection
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.widget.Toast
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.PERMISSION_RECORD_AUDIO import com.simplemobiletools.commons.helpers.PERMISSION_RECORD_AUDIO
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.helpers.isQPlus import com.simplemobiletools.commons.helpers.isQPlus
import com.simplemobiletools.commons.models.FAQItem import com.simplemobiletools.commons.models.FAQItem
import com.simplemobiletools.voicerecorder.BuildConfig import com.simplemobiletools.voicerecorder.BuildConfig
@ -25,17 +18,11 @@ import com.simplemobiletools.voicerecorder.services.RecorderService
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import java.io.File import org.greenrobot.eventbus.ThreadMode
import java.io.IOException
import java.util.*
class MainActivity : SimpleActivity() { class MainActivity : SimpleActivity() {
private var isRecording = false private var isRecording = false
private var recorder: MediaRecorder? = null
private var bus: EventBus? = null private var bus: EventBus? = null
private var currFilePath = ""
private var duration = 0
private var timer = Timer()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -63,12 +50,6 @@ class MainActivity : SimpleActivity() {
} }
} }
override fun onStop() {
super.onStop()
recorder?.release()
recorder = null
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
bus?.unregister(this) bus?.unregister(this)
@ -106,8 +87,7 @@ class MainActivity : SimpleActivity() {
bus = EventBus.getDefault() bus = EventBus.getDefault()
bus!!.register(this) bus!!.register(this)
duration = 0 updateRecordingDuration(0)
updateRecordingDuration()
toggle_recording_button.setOnClickListener { toggle_recording_button.setOnClickListener {
toggleRecording() toggleRecording()
} }
@ -129,116 +109,25 @@ class MainActivity : SimpleActivity() {
} }
} }
private fun updateRecordingDuration() { private fun updateRecordingDuration(duration: Int) {
recording_duration.text = duration.getFormattedDuration() recording_duration.text = duration.getFormattedDuration()
} }
// mp4 output format with aac encoding should produce good enough mp3 files according to https://stackoverflow.com/a/33054794/1967672
private fun startRecording() { private fun startRecording() {
val baseFolder = if (isQPlus()) { Intent(this@MainActivity, RecorderService::class.java).apply {
cacheDir startService(this)
} else {
val defaultFolder = File("$internalStoragePath/${getString(R.string.app_name)}")
if (!defaultFolder.exists()) {
defaultFolder.mkdir()
}
defaultFolder.absolutePath
}
currFilePath = "$baseFolder/${getCurrentFormattedDateTime()}.mp3"
recorder = MediaRecorder().apply {
setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
setOutputFile(currFilePath)
try {
prepare()
start()
duration = 0
timer = Timer()
timer.scheduleAtFixedRate(getTimerTask(), 1000, 1000)
Intent(this@MainActivity, RecorderService::class.java).apply {
startService(this)
}
} catch (e: IOException) {
showErrorToast(e)
}
} }
} }
private fun stopRecording() { private fun stopRecording() {
timer.cancel()
Intent(this@MainActivity, RecorderService::class.java).apply { Intent(this@MainActivity, RecorderService::class.java).apply {
stopService(this) stopService(this)
} }
recorder?.apply {
stop()
release()
ensureBackgroundThread {
if (isQPlus()) {
addFileInNewMediaStore()
} else {
addFileInLegacyMediaStore()
}
}
}
recorder = null
} }
@Subscribe @Subscribe(threadMode = ThreadMode.MAIN)
fun gotDurationEvent(event: Events.RecordingDuration) { fun gotDurationEvent(event: Events.RecordingDuration) {
updateRecordingDuration(event.duration)
}
@SuppressLint("InlinedApi")
private fun addFileInNewMediaStore() {
val audioCollection = MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val storeFilename = currFilePath.getFilenameFromPath()
val newSongDetails = ContentValues().apply {
put(MediaStore.Audio.Media.DISPLAY_NAME, storeFilename)
put(MediaStore.Audio.Media.TITLE, storeFilename)
put(MediaStore.Audio.Media.MIME_TYPE, storeFilename.getMimeType())
}
val newUri = contentResolver.insert(audioCollection, newSongDetails)
if (newUri == null) {
toast(R.string.unknown_error_occurred)
return
}
val outputStream = contentResolver.openOutputStream(newUri)
val inputStream = getFileInputStreamSync(currFilePath)
inputStream!!.copyTo(outputStream!!, DEFAULT_BUFFER_SIZE)
recordingSavedSuccessfully(true)
}
private fun addFileInLegacyMediaStore() {
MediaScannerConnection.scanFile(
this,
arrayOf(currFilePath),
arrayOf(currFilePath.getMimeType())
) { _, _ -> recordingSavedSuccessfully(false) }
}
private fun recordingSavedSuccessfully(showFilenameOnly: Boolean) {
val title = if (showFilenameOnly) currFilePath.getFilenameFromPath() else currFilePath
val msg = String.format(getString(R.string.recording_saved_successfully), title)
toast(msg, Toast.LENGTH_LONG)
}
private fun getTimerTask() = object : TimerTask() {
override fun run() {
duration++
runOnUiThread {
updateRecordingDuration()
}
}
} }
private fun getToggleButtonIcon(): Drawable { private fun getToggleButtonIcon(): Drawable {

View File

@ -1,37 +1,155 @@
package com.simplemobiletools.voicerecorder.services package com.simplemobiletools.voicerecorder.services
import android.annotation.SuppressLint
import android.annotation.TargetApi import android.annotation.TargetApi
import android.app.* import android.app.*
import android.content.ContentValues
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.media.MediaRecorder
import android.media.MediaScannerConnection
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.provider.MediaStore
import android.widget.Toast
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import com.simplemobiletools.commons.extensions.getLaunchIntent import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.helpers.isOreoPlus import com.simplemobiletools.commons.helpers.isOreoPlus
import com.simplemobiletools.commons.helpers.isQPlus
import com.simplemobiletools.voicerecorder.R import com.simplemobiletools.voicerecorder.R
import com.simplemobiletools.voicerecorder.activities.SplashActivity import com.simplemobiletools.voicerecorder.activities.SplashActivity
import com.simplemobiletools.voicerecorder.helpers.GET_DURATION import com.simplemobiletools.voicerecorder.helpers.GET_DURATION
import com.simplemobiletools.voicerecorder.helpers.RECORDER_RUNNING_NOTIF_ID import com.simplemobiletools.voicerecorder.helpers.RECORDER_RUNNING_NOTIF_ID
import com.simplemobiletools.voicerecorder.models.Events import com.simplemobiletools.voicerecorder.models.Events
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import java.io.File
import java.io.IOException
import java.util.*
class RecorderService : Service() { class RecorderService : Service() {
private var currFilePath = ""
private var duration = 0
private var timer = Timer()
private var recorder: MediaRecorder? = null
override fun onBind(intent: Intent?): IBinder? = null override fun onBind(intent: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId) super.onStartCommand(intent, flags, startId)
val action = intent.action when (intent.action) {
if (action == GET_DURATION) { GET_DURATION -> broadcastDuration()
broadcastDuration() else -> startRecording()
} else {
startForeground(RECORDER_RUNNING_NOTIF_ID, showNotification())
} }
return START_NOT_STICKY return START_NOT_STICKY
} }
override fun onDestroy() {
super.onDestroy()
stopRecording()
recorder?.release()
recorder = null
}
// mp4 output format with aac encoding should produce good enough mp3 files according to https://stackoverflow.com/a/33054794/1967672
private fun startRecording() {
startForeground(RECORDER_RUNNING_NOTIF_ID, showNotification())
duration = 0
broadcastDuration()
val baseFolder = if (isQPlus()) {
cacheDir
} else {
val defaultFolder = File("$internalStoragePath/${getString(R.string.app_name)}")
if (!defaultFolder.exists()) {
defaultFolder.mkdir()
}
defaultFolder.absolutePath
}
currFilePath = "$baseFolder/${getCurrentFormattedDateTime()}.mp3"
recorder = MediaRecorder().apply {
setAudioSource(MediaRecorder.AudioSource.CAMCORDER)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
setOutputFile(currFilePath)
try {
prepare()
start()
duration = 0
timer = Timer()
timer.scheduleAtFixedRate(getTimerTask(), 1000, 1000)
} catch (e: IOException) {
showErrorToast(e)
}
}
}
private fun stopRecording() {
timer.cancel()
recorder?.apply {
stop()
release()
ensureBackgroundThread {
if (isQPlus()) {
addFileInNewMediaStore()
} else {
addFileInLegacyMediaStore()
}
}
}
recorder = null
}
@SuppressLint("InlinedApi")
private fun addFileInNewMediaStore() {
val audioCollection = MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val storeFilename = currFilePath.getFilenameFromPath()
val newSongDetails = ContentValues().apply {
put(MediaStore.Audio.Media.DISPLAY_NAME, storeFilename)
put(MediaStore.Audio.Media.TITLE, storeFilename)
put(MediaStore.Audio.Media.MIME_TYPE, storeFilename.getMimeType())
}
val newUri = contentResolver.insert(audioCollection, newSongDetails)
if (newUri == null) {
toast(R.string.unknown_error_occurred)
return
}
val outputStream = contentResolver.openOutputStream(newUri)
val inputStream = getFileInputStreamSync(currFilePath)
inputStream!!.copyTo(outputStream!!, DEFAULT_BUFFER_SIZE)
recordingSavedSuccessfully(true)
}
private fun addFileInLegacyMediaStore() {
MediaScannerConnection.scanFile(
this,
arrayOf(currFilePath),
arrayOf(currFilePath.getMimeType())
) { _, _ -> recordingSavedSuccessfully(false) }
}
private fun recordingSavedSuccessfully(showFilenameOnly: Boolean) {
val title = if (showFilenameOnly) currFilePath.getFilenameFromPath() else currFilePath
val msg = String.format(getString(R.string.recording_saved_successfully), title)
toast(msg, Toast.LENGTH_LONG)
}
private fun getTimerTask() = object : TimerTask() {
override fun run() {
duration++
broadcastDuration()
}
}
@TargetApi(Build.VERSION_CODES.O) @TargetApi(Build.VERSION_CODES.O)
private fun showNotification(): Notification { private fun showNotification(): Notification {
val channelId = "simple_recorder" val channelId = "simple_recorder"
@ -66,6 +184,6 @@ class RecorderService : Service() {
} }
private fun broadcastDuration() { private fun broadcastDuration() {
EventBus.getDefault().post(Events.RecordingDuration(3)) EventBus.getDefault().post(Events.RecordingDuration(duration))
} }
} }