mirror of
https://github.com/Myzel394/Alibi.git
synced 2025-06-18 23:05:26 +02:00
chore: Move code for better readability
This commit is contained in:
parent
c40361aced
commit
65be9a1e3e
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user