mirror of
https://github.com/SimpleMobileTools/Simple-Voice-Recorder.git
synced 2025-06-05 21:59:31 +02:00
moving the actual recording into the service
This commit is contained in:
@ -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'
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user