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:name=".services.AudioRecorderService"
android:foregroundServiceType="microphone" /> android:foregroundServiceType="microphone" />
<service <service
android:name=".services.VideoService" android:name=".services.OldVideoService"
android:foregroundServiceType="camera|microphone" /> android:foregroundServiceType="camera|microphone" />
<!-- Change locale for Android <= 12 --> <!-- Change locale for Android <= 12 -->

View File

@ -10,13 +10,17 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import androidx.compose.material3.SnackbarDuration
import androidx.documentfile.provider.DocumentFile 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.enums.RecorderState
import app.myzel394.alibi.helpers.BatchesFolder import app.myzel394.alibi.helpers.BatchesFolder
import app.myzel394.alibi.ui.utils.MicrophoneInfo import app.myzel394.alibi.ui.utils.MicrophoneInfo
import java.lang.IllegalStateException import java.lang.IllegalStateException
class AudioRecorderService : IntervalRecorderService() { class AudioRecorderService :
IntervalRecorderService<AudioRecorderService.Settings, RecordingInformation>() {
var amplitudesAmount = 1000 var amplitudesAmount = 1000
var selectedMicrophone: MicrophoneInfo? = null var selectedMicrophone: MicrophoneInfo? = null
@ -27,6 +31,37 @@ class AudioRecorderService : IntervalRecorderService() {
var onMicrophoneDisconnected: () -> Unit = {} var onMicrophoneDisconnected: () -> Unit = {}
var onMicrophoneReconnected: () -> 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 /// Tell Android to use the correct bluetooth microphone, if any selected
private fun startAudioDevice() { private fun startAudioDevice() {
if (selectedMicrophone == null) { 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() { override fun startNewCycle() {
super.startNewCycle() super.startNewCycle()
@ -123,6 +166,7 @@ class AudioRecorderService : IntervalRecorderService() {
override fun start() { override fun start() {
super.start() super.start()
createAmplitudesTimer()
registerMicrophoneListener() registerMicrophoneListener()
} }
@ -140,9 +184,14 @@ class AudioRecorderService : IntervalRecorderService() {
unregisterMicrophoneListener() 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 { return try {
recorder!!.maxAmplitude recorder!!.maxAmplitude
} catch (error: IllegalStateException) { } catch (error: IllegalStateException) {
@ -213,4 +262,43 @@ class AudioRecorderService : IntervalRecorderService() {
audioManager.unregisterAudioDeviceCallback(audioDeviceCallback) 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.ScheduledExecutorService
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
abstract class IntervalRecorderService : ExtraRecorderInformationService() { abstract class IntervalRecorderService<S : IntervalRecorderService.Settings, I> :
private var job = SupervisorJob() RecorderService() {
private var scope = CoroutineScope(Dispatchers.IO + job)
protected var counter = 0L protected var counter = 0L
private set private set
lateinit var settings: Settings lateinit var settings: S
private lateinit var cycleTimer: ScheduledExecutorService private lateinit var cycleTimer: ScheduledExecutorService
@ -34,13 +32,7 @@ abstract class IntervalRecorderService : ExtraRecorderInformationService() {
var onCustomOutputFolderNotAccessible: () -> Unit = {} var onCustomOutputFolderNotAccessible: () -> Unit = {}
fun getRecordingInformation(): RecordingInformation = RecordingInformation( abstract fun getRecordingInformation(): I
folderPath = batchesFolder.exportFolderForSettings(),
recordingStart = recordingStart,
maxDuration = settings.maxDuration,
fileExtension = settings.fileExtension,
intervalDuration = settings.intervalDuration,
)
// Make overrideable // Make overrideable
open fun startNewCycle() { open fun startNewCycle() {
@ -62,8 +54,6 @@ abstract class IntervalRecorderService : ExtraRecorderInformationService() {
} }
override fun start() { override fun start() {
super.start()
batchesFolder.initFolders() batchesFolder.initFolders()
if (!batchesFolder.checkIfFolderIsAccessible()) { if (!batchesFolder.checkIfFolderIsAccessible()) {
batchesFolder = batchesFolder =
@ -81,10 +71,6 @@ abstract class IntervalRecorderService : ExtraRecorderInformationService() {
override fun resume() { override fun resume() {
createTimer() createTimer()
// We first want to start our timers, so the `ExtraRecorderInformationService` can fetch
// amplitudes
super.resume()
} }
override fun stop() { override fun stop() {
@ -96,45 +82,14 @@ abstract class IntervalRecorderService : ExtraRecorderInformationService() {
} }
private fun deleteOldRecordings() { private fun deleteOldRecordings() {
val timeMultiplier = settings!!.maxDuration / settings!!.intervalDuration val timeMultiplier = settings.maxDuration / settings.intervalDuration
val earliestCounter = counter - timeMultiplier val earliestCounter = counter - timeMultiplier
batchesFolder.deleteOldRecordings(earliestCounter) batchesFolder.deleteOldRecordings(earliestCounter)
} }
data class Settings( abstract class Settings(
val maxDuration: Long, open val maxDuration: Long,
val intervalDuration: Long, open 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,
)
}
}
}
} }

View File

@ -3,26 +3,16 @@ package app.myzel394.alibi.services
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ContentValues import android.content.ContentValues
import android.content.pm.ServiceInfo import android.content.pm.ServiceInfo
import android.graphics.SurfaceTexture
import android.hardware.Camera
import android.os.Build import android.os.Build
import android.provider.MediaStore 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.CameraSelector
import androidx.camera.core.Preview
import androidx.camera.core.Preview.SurfaceProvider
import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.video.MediaStoreOutputOptions import androidx.camera.video.MediaStoreOutputOptions
import androidx.camera.video.PendingRecording
import androidx.camera.video.Quality import androidx.camera.video.Quality
import androidx.camera.video.QualitySelector import androidx.camera.video.QualitySelector
import androidx.camera.video.Recorder import androidx.camera.video.Recorder
import androidx.camera.video.Recording import androidx.camera.video.Recording
import androidx.camera.video.VideoCapture.withOutput import androidx.camera.video.VideoCapture.withOutput
import androidx.camera.video.VideoRecordEvent
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@ -31,13 +21,15 @@ import app.myzel394.alibi.NotificationHelper
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class VideoService : LifecycleService() {
class VideoService : IntervalRecorderService() {
}
class OldVideoService : LifecycleService() {
private var job = SupervisorJob() private var job = SupervisorJob()
private var scope = CoroutineScope(Dispatchers.IO + job) 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 // Used to bind the lifecycle of cameras to the lifecycle owner
val cameraProvider = cameraProviderFuture.get() val cameraProvider = cameraProviderFuture.get()
val recorder = Recorder.Builder() val recorder = Recorder.Builder()
.setQualitySelector(QualitySelector.from(Quality.HIGHEST)) .setQualitySelector(QualitySelector.from(Quality.LOWEST))
.build() .build()
val videoCapture = withOutput(recorder) val videoCapture = withOutput(recorder)
// Select back camera as a default // Select back camera as a default
@ -98,7 +90,7 @@ class VideoService : LifecycleService() {
cameraProvider?.unbindAll() cameraProvider?.unbindAll()
// Bind use cases to camera // Bind use cases to camera
cameraProvider?.bindToLifecycle( cameraProvider?.bindToLifecycle(
this@VideoService, this@OldVideoService,
cameraSelector, cameraSelector,
videoCapture videoCapture
) )
@ -108,7 +100,7 @@ class VideoService : LifecycleService() {
cycleTimer = Executors.newSingleThreadScheduledExecutor().also { cycleTimer = Executors.newSingleThreadScheduledExecutor().also {
it.scheduleAtFixedRate( it.scheduleAtFixedRate(
{ {
val mainHandler = ContextCompat.getMainExecutor(this@VideoService) val mainHandler = ContextCompat.getMainExecutor(this@OldVideoService)
mainHandler.execute { mainHandler.execute {
runCatching { runCatching {
@ -116,11 +108,11 @@ class VideoService : LifecycleService() {
} }
val r = val r =
videoCapture.output.prepareRecording(this@VideoService, options) videoCapture.output.prepareRecording(this@OldVideoService, options)
.withAudioEnabled() .withAudioEnabled()
recording = recording =
r.start(ContextCompat.getMainExecutor(this@VideoService), {}) r.start(ContextCompat.getMainExecutor(this@OldVideoService), {})
} }
}, },
0, 0,

View File

@ -10,6 +10,7 @@ import android.os.Build
import android.os.IBinder import android.os.IBinder
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.app.ServiceCompat import androidx.core.app.ServiceCompat
import androidx.lifecycle.LifecycleService
import app.myzel394.alibi.NotificationHelper import app.myzel394.alibi.NotificationHelper
import app.myzel394.alibi.enums.RecorderState import app.myzel394.alibi.enums.RecorderState
import app.myzel394.alibi.ui.utils.PermissionHelper import app.myzel394.alibi.ui.utils.PermissionHelper
@ -20,7 +21,7 @@ import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
abstract class RecorderService : Service() { abstract class RecorderService : LifecycleService() {
private val binder = RecorderBinder() private val binder = RecorderBinder()
private var isPaused: Boolean = false private var isPaused: Boolean = false
@ -44,7 +45,10 @@ abstract class RecorderService : Service() {
protected abstract fun resume() protected abstract fun resume()
protected abstract fun stop() 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 { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
when (intent?.action) { when (intent?.action) {

View File

@ -3,22 +3,27 @@ package app.myzel394.alibi.ui.screens
import android.content.Intent import android.content.Intent
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import app.myzel394.alibi.services.VideoService import app.myzel394.alibi.services.OldVideoService
@Composable @Composable
fun POCVideo() { fun POCVideo() {
val context = LocalContext.current val context = LocalContext.current
Box(modifier = Modifier.fillMaxSize()) { Box(
modifier = Modifier
.fillMaxSize()
.padding(64.dp)
) {
Button(onClick = { Button(onClick = {
val intent = Intent(context, VideoService::class.java) val intent = Intent(context, OldVideoService::class.java)
ContextCompat.startForegroundService(context, intent) ContextCompat.startForegroundService(context, intent)
}) { }) {
Text("Start") Text("Start")