chore: Improvements

This commit is contained in:
Myzel394 2023-11-26 14:12:31 +01:00
parent f6bdd1b345
commit d21580b0cb
No known key found for this signature in database
GPG Key ID: 79CC92F37B3E1A2B
7 changed files with 126 additions and 137 deletions

View File

@ -52,7 +52,7 @@
android:name=".services.AudioRecorderService"
android:foregroundServiceType="microphone" />
<service
android:name=".services.VideoService"
android:name=".services.OldVideoService"
android:foregroundServiceType="camera|microphone" />
<!-- Change locale for Android <= 12 -->

View File

@ -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<AudioRecorderService.Settings, RecordingInformation>() {
var amplitudesAmount = 1000
var selectedMicrophone: MicrophoneInfo? = null
@ -27,6 +31,37 @@ class AudioRecorderService : IntervalRecorderService() {
var onMicrophoneDisconnected: () -> Unit = {}
var onMicrophoneReconnected: () -> Unit = {}
var amplitudes = mutableListOf<Int>()
private set
private val handler = Handler(Looper.getMainLooper())
var onAmplitudeChange: ((List<Int>) -> 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,
)
}
}
}
}

View File

@ -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<Int>()
private set
private val handler = Handler(Looper.getMainLooper())
var onAmplitudeChange: ((List<Int>) -> 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()
}
}

View File

@ -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<S : IntervalRecorderService.Settings, I> :
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,
)
}

View File

@ -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,

View File

@ -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) {

View File

@ -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")