chore: Move code for better readability

This commit is contained in:
Myzel394 2023-12-01 00:29:41 +01:00
parent c40361aced
commit 65be9a1e3e
No known key found for this signature in database
GPG Key ID: 79CC92F37B3E1A2B
2 changed files with 198 additions and 168 deletions

View File

@ -21,130 +21,23 @@ class AudioRecorderService :
IntervalRecorderService<AudioRecorderService.Settings, RecordingInformation>() {
override var batchesFolder: BatchesFolder = AudioBatchesFolder.viaInternalFolder(this)
private val handler = Handler(Looper.getMainLooper())
var amplitudes = mutableListOf<Int>()
private set
var amplitudesAmount = 1000
var selectedMicrophone: MicrophoneInfo? = null
var recorder: MediaRecorder? = null
private set
// Callbacks
var onSelectedMicrophoneChange: (MicrophoneInfo?) -> Unit = {}
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) {
return
}
val audioManger = getSystemService(AUDIO_SERVICE)!! as AudioManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
audioManger.setCommunicationDevice(selectedMicrophone!!.deviceInfo)
} else {
audioManger.startBluetoothSco()
}
}
private fun clearAudioDevice() {
val audioManger = getSystemService(AUDIO_SERVICE)!! as AudioManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
audioManger.clearCommunicationDevice()
} else {
audioManger.stopBluetoothSco()
}
}
private fun createRecorder(): MediaRecorder {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
MediaRecorder(this)
} else {
MediaRecorder()
}.apply {
// Audio Source is kinda strange, here are my experimental findings using a Pixel 7 Pro
// and Redmi Buds 3 Pro:
// - MIC: Uses the bottom microphone of the phone (17)
// - CAMCORDER: Uses the top microphone of the phone (2)
// - VOICE_COMMUNICATION: Uses the bottom microphone of the phone (17)
// - DEFAULT: Uses the bottom microphone of the phone (17)
setAudioSource(MediaRecorder.AudioSource.MIC)
when (batchesFolder.type) {
BatchesFolder.BatchType.INTERNAL -> {
setOutputFile(
batchesFolder.asInternalGetOutputPath(counter, settings.fileExtension)
)
}
BatchesFolder.BatchType.CUSTOM -> {
setOutputFile(
batchesFolder.asCustomGetFileDescriptor(counter, settings.fileExtension)
)
}
}
setOutputFormat(settings.outputFormat)
setAudioEncoder(settings.encoder)
setAudioEncodingBitRate(settings.bitRate)
setAudioSamplingRate(settings.samplingRate)
setOnErrorListener(OnErrorListener { _, _, _ ->
onError()
})
}
}
private fun resetRecorder() {
runCatching {
recorder?.let {
it.stop()
it.release()
}
clearAudioDevice()
batchesFolder.cleanup()
}
}
override fun getRecordingInformation() = RecordingInformation(
folderPath = batchesFolder.exportFolderForSettings(),
recordingStart = recordingStart,
maxDuration = settings.maxDuration,
fileExtension = settings.fileExtension,
intervalDuration = settings.intervalDuration,
type = RecordingInformation.Type.AUDIO,
)
override fun startNewCycle() {
super.startNewCycle()
@ -189,6 +82,7 @@ class AudioRecorderService :
createAmplitudesTimer()
}
// ==== Amplitude related ====
private fun getAmplitudeAmount(): Int = amplitudesAmount
private fun getAmplitude(): Int {
@ -201,6 +95,109 @@ class AudioRecorderService :
}
}
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)
}
// ==== Audio device related ====
/// Tell Android to use the correct bluetooth microphone, if any selected
private fun startAudioDevice() {
if (selectedMicrophone == null) {
return
}
val audioManger = getSystemService(AUDIO_SERVICE)!! as AudioManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
audioManger.setCommunicationDevice(selectedMicrophone!!.deviceInfo)
} else {
audioManger.startBluetoothSco()
}
}
private fun clearAudioDevice() {
val audioManger = getSystemService(AUDIO_SERVICE)!! as AudioManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
audioManger.clearCommunicationDevice()
} else {
audioManger.stopBluetoothSco()
}
}
// ==== Actual recording related ====
private fun createRecorder(): MediaRecorder {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
MediaRecorder(this)
} else {
MediaRecorder()
}.apply {
// Audio Source is kinda strange, here are my experimental findings using a Pixel 7 Pro
// and Redmi Buds 3 Pro:
// - MIC: Uses the bottom microphone of the phone (17)
// - CAMCORDER: Uses the top microphone of the phone (2)
// - VOICE_COMMUNICATION: Uses the bottom microphone of the phone (17)
// - DEFAULT: Uses the bottom microphone of the phone (17)
setAudioSource(MediaRecorder.AudioSource.MIC)
when (batchesFolder.type) {
BatchesFolder.BatchType.INTERNAL -> {
setOutputFile(
batchesFolder.asInternalGetOutputPath(counter, settings.fileExtension)
)
}
BatchesFolder.BatchType.CUSTOM -> {
setOutputFile(
batchesFolder.asCustomGetFileDescriptor(counter, settings.fileExtension)
)
}
}
setOutputFormat(settings.outputFormat)
setAudioEncoder(settings.encoder)
setAudioEncodingBitRate(settings.bitRate)
setAudioSamplingRate(settings.samplingRate)
setOnErrorListener(OnErrorListener { _, _, _ ->
onError()
})
}
}
// ==== Microphone related ====
private fun resetRecorder() {
runCatching {
recorder?.let {
it.stop()
it.release()
}
clearAudioDevice()
batchesFolder.cleanup()
}
}
fun changeMicrophone(microphone: MicrophoneInfo?) {
selectedMicrophone = microphone
onSelectedMicrophoneChange(microphone)
@ -263,6 +260,16 @@ class AudioRecorderService :
audioManager.unregisterAudioDeviceCallback(audioDeviceCallback)
}
// ==== Settings ====
override fun getRecordingInformation() = RecordingInformation(
folderPath = batchesFolder.exportFolderForSettings(),
recordingStart = recordingStart,
maxDuration = settings.maxDuration,
fileExtension = settings.fileExtension,
intervalDuration = settings.intervalDuration,
type = RecordingInformation.Type.AUDIO,
)
data class Settings(
override val maxDuration: Long,
override val intervalDuration: Long,

View File

@ -3,7 +3,9 @@ package app.myzel394.alibi.services
import android.annotation.SuppressLint
import android.util.Range
import androidx.camera.core.Camera
import androidx.camera.core.CameraInfo
import androidx.camera.core.CameraSelector
import androidx.camera.core.TorchState
import androidx.camera.core.impl.CameraConfig
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.video.FileOutputOptions
@ -47,6 +49,64 @@ class VideoRecorderService :
private var selectedCamera: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
var cameraControl: CameraControl? = null
private set
override fun start() {
super.start()
scope.launch {
openCamera()
}
}
override suspend fun stop() {
super.stop()
stopActiveRecording()
withTimeoutOrNull(CAMERA_CLOSE_TIMEOUT) {
// Camera can only be closed after the recording has been finalized
_cameraClosedListener.await()
}
closeCamera()
}
override fun pause() {
super.pause()
stopActiveRecording()
}
@SuppressLint("MissingPermission")
override fun startNewCycle() {
super.startNewCycle()
fun action() {
activeRecording?.stop()
val newRecording = prepareVideoRecording()
activeRecording = newRecording.start(ContextCompat.getMainExecutor(this)) { event ->
if (event is VideoRecordEvent.Finalize) {
_cameraClosedListener.complete(Unit)
}
}
}
if (_cameraAvailableListener.isCompleted) {
action()
} else {
// Race condition of `startNewCycle` being called before `invpkeOnCompletion`
// has been called can be ignored, as the camera usually opens within 5 seconds
// and the interval can't be set shorter than 10 seconds.
_cameraAvailableListener.invokeOnCompletion {
action()
}
}
}
// Runs a function in the main thread
private fun runOnMain(callback: () -> Unit) {
val mainHandler = ContextCompat.getMainExecutor(this)
@ -88,6 +148,7 @@ class VideoRecorderService :
selectedCamera,
videoCapture
)
cameraControl = CameraControl(camera!!)
_cameraAvailableListener.complete(Unit)
}
@ -108,33 +169,6 @@ class VideoRecorderService :
camera = null
}
override fun start() {
super.start()
scope.launch {
openCamera()
}
}
override suspend fun stop() {
super.stop()
stopActiveRecording()
withTimeoutOrNull(CAMERA_CLOSE_TIMEOUT) {
// Camera can only be closed after the recording has been finalized
_cameraClosedListener.await()
}
closeCamera()
}
override fun pause() {
super.pause()
stopActiveRecording()
}
// `resume` override not needed as `startNewCycle` is called by `IntervalRecorderService`
private fun stopActiveRecording() {
@ -147,33 +181,6 @@ class VideoRecorderService :
.prepareRecording(this, settings.getOutputOptions(this))
.withAudioEnabled()
@SuppressLint("MissingPermission")
override fun startNewCycle() {
super.startNewCycle()
fun action() {
activeRecording?.stop()
val newRecording = prepareVideoRecording()
activeRecording = newRecording.start(ContextCompat.getMainExecutor(this)) { event ->
if (event is VideoRecordEvent.Finalize) {
_cameraClosedListener.complete(Unit)
}
}
}
if (_cameraAvailableListener.isCompleted) {
action()
} else {
// Race condition of `startNewCycle` being called before `invpkeOnCompletion`
// has been called can be ignored, as the camera usually opens within 5 seconds
// and the interval can't be set shorter than 10 seconds.
_cameraAvailableListener.invokeOnCompletion {
action()
}
}
}
override fun getRecordingInformation(): RecordingInformation = RecordingInformation(
folderPath = batchesFolder.exportFolderForSettings(),
recordingStart = recordingStart,
@ -222,4 +229,20 @@ class VideoRecorderService :
)
}
}
class CameraControl(
private val camera: Camera,
) {
fun enableTorch() {
camera.cameraControl.enableTorch(true)
}
fun disableTorch() {
camera.cameraControl.enableTorch(false)
}
fun isTorchEnabled(): Boolean {
return camera.cameraInfo.torchState.value == TorchState.ON
}
}
}