mirror of
https://github.com/Myzel394/Alibi.git
synced 2025-06-18 23:05:26 +02:00
chore: Improvements
This commit is contained in:
parent
f6bdd1b345
commit
d21580b0cb
@ -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 -->
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
||||
}
|
@ -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,
|
||||
)
|
||||
}
|
@ -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,
|
@ -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) {
|
||||
|
@ -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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user