From d21580b0cb47106b9ad48f725cdae79ffe425d9b Mon Sep 17 00:00:00 2001 From: Myzel394 <50424412+Myzel394@users.noreply.github.com> Date: Sun, 26 Nov 2023 14:12:31 +0100 Subject: [PATCH] chore: Improvements --- app/src/main/AndroidManifest.xml | 2 +- .../alibi/services/AudioRecorderService.kt | 94 ++++++++++++++++++- .../ExtraRecorderInformationService.kt | 55 ----------- .../alibi/services/IntervalRecorderService.kt | 63 ++----------- .../{VideoService.kt => OldVideoService.kt} | 28 ++---- .../alibi/services/RecorderService.kt | 8 +- .../app/myzel394/alibi/ui/screens/POCVideo.kt | 13 ++- 7 files changed, 126 insertions(+), 137 deletions(-) delete mode 100644 app/src/main/java/app/myzel394/alibi/services/ExtraRecorderInformationService.kt rename app/src/main/java/app/myzel394/alibi/services/{VideoService.kt => OldVideoService.kt} (87%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index dbaa892..2518136 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -52,7 +52,7 @@ android:name=".services.AudioRecorderService" android:foregroundServiceType="microphone" /> diff --git a/app/src/main/java/app/myzel394/alibi/services/AudioRecorderService.kt b/app/src/main/java/app/myzel394/alibi/services/AudioRecorderService.kt index 79d04fa..dcbcd2b 100644 --- a/app/src/main/java/app/myzel394/alibi/services/AudioRecorderService.kt +++ b/app/src/main/java/app/myzel394/alibi/services/AudioRecorderService.kt @@ -10,13 +10,17 @@ import android.net.Uri import android.os.Build import android.os.Handler import android.os.Looper +import androidx.compose.material3.SnackbarDuration import androidx.documentfile.provider.DocumentFile +import app.myzel394.alibi.db.AudioRecorderSettings +import app.myzel394.alibi.db.RecordingInformation import app.myzel394.alibi.enums.RecorderState import app.myzel394.alibi.helpers.BatchesFolder import app.myzel394.alibi.ui.utils.MicrophoneInfo import java.lang.IllegalStateException -class AudioRecorderService : IntervalRecorderService() { +class AudioRecorderService : + IntervalRecorderService() { var amplitudesAmount = 1000 var selectedMicrophone: MicrophoneInfo? = null @@ -27,6 +31,37 @@ class AudioRecorderService : IntervalRecorderService() { var onMicrophoneDisconnected: () -> Unit = {} var onMicrophoneReconnected: () -> Unit = {} + var amplitudes = mutableListOf() + private set + + private val handler = Handler(Looper.getMainLooper()) + + var onAmplitudeChange: ((List) -> Unit)? = null + + private fun updateAmplitude() { + if (state !== RecorderState.RECORDING) { + return + } + + amplitudes.add(getAmplitude()) + onAmplitudeChange?.invoke(amplitudes) + + // Delete old amplitudes + if (amplitudes.size > getAmplitudeAmount()) { + // Should be more efficient than dropping the elements, getting a new list + // clearing old list and adding new elements to it + repeat(amplitudes.size - getAmplitudeAmount()) { + amplitudes.removeAt(0) + } + } + + handler.postDelayed(::updateAmplitude, 100) + } + + private fun createAmplitudesTimer() { + handler.postDelayed(::updateAmplitude, 100) + } + /// Tell Android to use the correct bluetooth microphone, if any selected private fun startAudioDevice() { if (selectedMicrophone == null) { @@ -102,6 +137,14 @@ class AudioRecorderService : IntervalRecorderService() { } } + override fun getRecordingInformation() = RecordingInformation( + folderPath = batchesFolder.exportFolderForSettings(), + recordingStart = recordingStart, + maxDuration = settings.maxDuration, + fileExtension = settings.fileExtension, + intervalDuration = settings.intervalDuration, + ) + override fun startNewCycle() { super.startNewCycle() @@ -123,6 +166,7 @@ class AudioRecorderService : IntervalRecorderService() { override fun start() { super.start() + createAmplitudesTimer() registerMicrophoneListener() } @@ -140,9 +184,14 @@ class AudioRecorderService : IntervalRecorderService() { unregisterMicrophoneListener() } - override fun getAmplitudeAmount(): Int = amplitudesAmount + override fun resume() { + super.resume() + createAmplitudesTimer() + } - override fun getAmplitude(): Int { + private fun getAmplitudeAmount(): Int = amplitudesAmount + + private fun getAmplitude(): Int { return try { recorder!!.maxAmplitude } catch (error: IllegalStateException) { @@ -213,4 +262,43 @@ class AudioRecorderService : IntervalRecorderService() { audioManager.unregisterAudioDeviceCallback(audioDeviceCallback) } + + data class Settings( + override val maxDuration: Long, + override val intervalDuration: Long, + val bitRate: Int, + val samplingRate: Int, + val outputFormat: Int, + val encoder: Int, + val folder: String? = null, + ) : IntervalRecorderService.Settings( + maxDuration = maxDuration, + intervalDuration = intervalDuration + ) { + val fileExtension: String + get() = when (outputFormat) { + MediaRecorder.OutputFormat.AAC_ADTS -> "aac" + MediaRecorder.OutputFormat.THREE_GPP -> "3gp" + MediaRecorder.OutputFormat.MPEG_4 -> "mp4" + MediaRecorder.OutputFormat.MPEG_2_TS -> "ts" + MediaRecorder.OutputFormat.WEBM -> "webm" + MediaRecorder.OutputFormat.AMR_NB -> "amr" + MediaRecorder.OutputFormat.AMR_WB -> "awb" + MediaRecorder.OutputFormat.OGG -> "ogg" + else -> "raw" + } + + companion object { + fun from(audioRecorderSettings: AudioRecorderSettings): IntervalRecorderService.Settings { + return Settings( + intervalDuration = audioRecorderSettings.intervalDuration, + bitRate = audioRecorderSettings.bitRate, + samplingRate = audioRecorderSettings.getSamplingRate(), + outputFormat = audioRecorderSettings.getOutputFormat(), + encoder = audioRecorderSettings.getEncoder(), + maxDuration = audioRecorderSettings.maxDuration, + ) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/services/ExtraRecorderInformationService.kt b/app/src/main/java/app/myzel394/alibi/services/ExtraRecorderInformationService.kt deleted file mode 100644 index 013dc52..0000000 --- a/app/src/main/java/app/myzel394/alibi/services/ExtraRecorderInformationService.kt +++ /dev/null @@ -1,55 +0,0 @@ -package app.myzel394.alibi.services - -import android.os.Handler -import android.os.Looper -import app.myzel394.alibi.enums.RecorderState -import java.util.Timer -import java.util.TimerTask -import java.util.concurrent.Executors -import java.util.concurrent.ScheduledExecutorService -import java.util.concurrent.TimeUnit - -abstract class ExtraRecorderInformationService : RecorderService() { - abstract fun getAmplitudeAmount(): Int - abstract fun getAmplitude(): Int - - var amplitudes = mutableListOf() - private set - - private val handler = Handler(Looper.getMainLooper()) - - var onAmplitudeChange: ((List) -> Unit)? = null - - private fun updateAmplitude() { - if (state !== RecorderState.RECORDING) { - return - } - - amplitudes.add(getAmplitude()) - onAmplitudeChange?.invoke(amplitudes) - - // Delete old amplitudes - if (amplitudes.size > getAmplitudeAmount()) { - // Should be more efficient than dropping the elements, getting a new list - // clearing old list and adding new elements to it - repeat(amplitudes.size - getAmplitudeAmount()) { - amplitudes.removeAt(0) - } - } - - handler.postDelayed(::updateAmplitude, 100) - } - - private fun createAmplitudesTimer() { - handler.postDelayed(::updateAmplitude, 100) - } - - override fun start() { - createAmplitudesTimer() - } - - override fun resume() { - createAmplitudesTimer() - } - -} \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt b/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt index cc6ac55..8bcaede 100644 --- a/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt +++ b/app/src/main/java/app/myzel394/alibi/services/IntervalRecorderService.kt @@ -19,14 +19,12 @@ import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.TimeUnit -abstract class IntervalRecorderService : ExtraRecorderInformationService() { - private var job = SupervisorJob() - private var scope = CoroutineScope(Dispatchers.IO + job) - +abstract class IntervalRecorderService : + RecorderService() { protected var counter = 0L private set - lateinit var settings: Settings + lateinit var settings: S private lateinit var cycleTimer: ScheduledExecutorService @@ -34,13 +32,7 @@ abstract class IntervalRecorderService : ExtraRecorderInformationService() { var onCustomOutputFolderNotAccessible: () -> Unit = {} - fun getRecordingInformation(): RecordingInformation = RecordingInformation( - folderPath = batchesFolder.exportFolderForSettings(), - recordingStart = recordingStart, - maxDuration = settings.maxDuration, - fileExtension = settings.fileExtension, - intervalDuration = settings.intervalDuration, - ) + abstract fun getRecordingInformation(): I // Make overrideable open fun startNewCycle() { @@ -62,8 +54,6 @@ abstract class IntervalRecorderService : ExtraRecorderInformationService() { } override fun start() { - super.start() - batchesFolder.initFolders() if (!batchesFolder.checkIfFolderIsAccessible()) { batchesFolder = @@ -81,10 +71,6 @@ abstract class IntervalRecorderService : ExtraRecorderInformationService() { override fun resume() { createTimer() - - // We first want to start our timers, so the `ExtraRecorderInformationService` can fetch - // amplitudes - super.resume() } override fun stop() { @@ -96,45 +82,14 @@ abstract class IntervalRecorderService : ExtraRecorderInformationService() { } private fun deleteOldRecordings() { - val timeMultiplier = settings!!.maxDuration / settings!!.intervalDuration + val timeMultiplier = settings.maxDuration / settings.intervalDuration val earliestCounter = counter - timeMultiplier batchesFolder.deleteOldRecordings(earliestCounter) } - data class Settings( - val maxDuration: Long, - val intervalDuration: Long, - val bitRate: Int, - val samplingRate: Int, - val outputFormat: Int, - val encoder: Int, - val folder: String? = null, - ) { - val fileExtension: String - get() = when (outputFormat) { - MediaRecorder.OutputFormat.AAC_ADTS -> "aac" - MediaRecorder.OutputFormat.THREE_GPP -> "3gp" - MediaRecorder.OutputFormat.MPEG_4 -> "mp4" - MediaRecorder.OutputFormat.MPEG_2_TS -> "ts" - MediaRecorder.OutputFormat.WEBM -> "webm" - MediaRecorder.OutputFormat.AMR_NB -> "amr" - MediaRecorder.OutputFormat.AMR_WB -> "awb" - MediaRecorder.OutputFormat.OGG -> "ogg" - else -> "raw" - } - - companion object { - fun from(audioRecorderSettings: AudioRecorderSettings): Settings { - return Settings( - intervalDuration = audioRecorderSettings.intervalDuration, - bitRate = audioRecorderSettings.bitRate, - samplingRate = audioRecorderSettings.getSamplingRate(), - outputFormat = audioRecorderSettings.getOutputFormat(), - encoder = audioRecorderSettings.getEncoder(), - maxDuration = audioRecorderSettings.maxDuration, - ) - } - } - } + abstract class Settings( + open val maxDuration: Long, + open val intervalDuration: Long, + ) } \ No newline at end of file diff --git a/app/src/main/java/app/myzel394/alibi/services/VideoService.kt b/app/src/main/java/app/myzel394/alibi/services/OldVideoService.kt similarity index 87% rename from app/src/main/java/app/myzel394/alibi/services/VideoService.kt rename to app/src/main/java/app/myzel394/alibi/services/OldVideoService.kt index c48e66d..2d109a9 100644 --- a/app/src/main/java/app/myzel394/alibi/services/VideoService.kt +++ b/app/src/main/java/app/myzel394/alibi/services/OldVideoService.kt @@ -3,26 +3,16 @@ package app.myzel394.alibi.services import android.annotation.SuppressLint import android.content.ContentValues import android.content.pm.ServiceInfo -import android.graphics.SurfaceTexture -import android.hardware.Camera import android.os.Build import android.provider.MediaStore -import android.view.Surface -import android.view.TextureView -import android.view.ViewGroup -import androidx.camera.core.CameraProvider import androidx.camera.core.CameraSelector -import androidx.camera.core.Preview -import androidx.camera.core.Preview.SurfaceProvider import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.video.MediaStoreOutputOptions -import androidx.camera.video.PendingRecording import androidx.camera.video.Quality import androidx.camera.video.QualitySelector import androidx.camera.video.Recorder import androidx.camera.video.Recording import androidx.camera.video.VideoCapture.withOutput -import androidx.camera.video.VideoRecordEvent import androidx.core.app.NotificationCompat import androidx.core.app.ServiceCompat import androidx.core.content.ContextCompat @@ -31,13 +21,15 @@ import app.myzel394.alibi.NotificationHelper import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.TimeUnit -class VideoService : LifecycleService() { + +class VideoService : IntervalRecorderService() { +} + +class OldVideoService : LifecycleService() { private var job = SupervisorJob() private var scope = CoroutineScope(Dispatchers.IO + job) @@ -88,7 +80,7 @@ class VideoService : LifecycleService() { // Used to bind the lifecycle of cameras to the lifecycle owner val cameraProvider = cameraProviderFuture.get() val recorder = Recorder.Builder() - .setQualitySelector(QualitySelector.from(Quality.HIGHEST)) + .setQualitySelector(QualitySelector.from(Quality.LOWEST)) .build() val videoCapture = withOutput(recorder) // Select back camera as a default @@ -98,7 +90,7 @@ class VideoService : LifecycleService() { cameraProvider?.unbindAll() // Bind use cases to camera cameraProvider?.bindToLifecycle( - this@VideoService, + this@OldVideoService, cameraSelector, videoCapture ) @@ -108,7 +100,7 @@ class VideoService : LifecycleService() { cycleTimer = Executors.newSingleThreadScheduledExecutor().also { it.scheduleAtFixedRate( { - val mainHandler = ContextCompat.getMainExecutor(this@VideoService) + val mainHandler = ContextCompat.getMainExecutor(this@OldVideoService) mainHandler.execute { runCatching { @@ -116,11 +108,11 @@ class VideoService : LifecycleService() { } val r = - videoCapture.output.prepareRecording(this@VideoService, options) + videoCapture.output.prepareRecording(this@OldVideoService, options) .withAudioEnabled() recording = - r.start(ContextCompat.getMainExecutor(this@VideoService), {}) + r.start(ContextCompat.getMainExecutor(this@OldVideoService), {}) } }, 0, diff --git a/app/src/main/java/app/myzel394/alibi/services/RecorderService.kt b/app/src/main/java/app/myzel394/alibi/services/RecorderService.kt index 02b5797..5648acb 100644 --- a/app/src/main/java/app/myzel394/alibi/services/RecorderService.kt +++ b/app/src/main/java/app/myzel394/alibi/services/RecorderService.kt @@ -10,6 +10,7 @@ import android.os.Build import android.os.IBinder import androidx.core.app.NotificationManagerCompat import androidx.core.app.ServiceCompat +import androidx.lifecycle.LifecycleService import app.myzel394.alibi.NotificationHelper import app.myzel394.alibi.enums.RecorderState import app.myzel394.alibi.ui.utils.PermissionHelper @@ -20,7 +21,7 @@ import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.TimeUnit -abstract class RecorderService : Service() { +abstract class RecorderService : LifecycleService() { private val binder = RecorderBinder() private var isPaused: Boolean = false @@ -44,7 +45,10 @@ abstract class RecorderService : Service() { protected abstract fun resume() protected abstract fun stop() - override fun onBind(p0: Intent?): IBinder? = binder + override fun onBind(intent: Intent): IBinder? { + super.onBind(intent) + return binder + } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { when (intent?.action) { diff --git a/app/src/main/java/app/myzel394/alibi/ui/screens/POCVideo.kt b/app/src/main/java/app/myzel394/alibi/ui/screens/POCVideo.kt index a7cae21..fc42e09 100644 --- a/app/src/main/java/app/myzel394/alibi/ui/screens/POCVideo.kt +++ b/app/src/main/java/app/myzel394/alibi/ui/screens/POCVideo.kt @@ -3,22 +3,27 @@ package app.myzel394.alibi.ui.screens import android.content.Intent import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat -import app.myzel394.alibi.services.VideoService +import app.myzel394.alibi.services.OldVideoService @Composable fun POCVideo() { val context = LocalContext.current - Box(modifier = Modifier.fillMaxSize()) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(64.dp) + ) { Button(onClick = { - val intent = Intent(context, VideoService::class.java) + val intent = Intent(context, OldVideoService::class.java) ContextCompat.startForegroundService(context, intent) }) { Text("Start")