fix: Properly reconnect audio on app focus

This commit is contained in:
Myzel394 2023-12-16 22:13:38 +01:00
parent ff8ea3e1f2
commit cda0b7f195
No known key found for this signature in database
GPG Key ID: 79CC92F37B3E1A2B
10 changed files with 70 additions and 107 deletions

View File

@ -3,11 +3,13 @@ package app.myzel394.alibi.db
import android.content.Context
import android.media.MediaRecorder
import android.os.Build
import androidx.camera.video.FileOutputOptions
import androidx.camera.video.Quality
import androidx.camera.video.QualitySelector
import app.myzel394.alibi.R
import app.myzel394.alibi.helpers.AudioBatchesFolder
import app.myzel394.alibi.helpers.VideoBatchesFolder
import app.myzel394.alibi.services.VideoRecorderService
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import java.time.LocalDateTime
@ -260,6 +262,19 @@ data class AudioRecorderSettings(
return supportedFormats.contains(outputFormat)
}
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 getDefaultInstance(): AudioRecorderSettings = AudioRecorderSettings()
val EXAMPLE_MAX_DURATIONS = listOf(
@ -403,6 +418,9 @@ data class VideoRecorderSettings(
fun getMimeType() = "video/mp4"
val fileExtension
get() = "mp4"
companion object {
fun getDefaultInstance() = VideoRecorderSettings()

View File

@ -13,7 +13,6 @@ import android.os.Looper
import androidx.core.app.ServiceCompat
import app.myzel394.alibi.NotificationHelper
import app.myzel394.alibi.db.AppSettings
import app.myzel394.alibi.db.AudioRecorderSettings
import app.myzel394.alibi.db.RecordingInformation
import app.myzel394.alibi.enums.RecorderState
import app.myzel394.alibi.helpers.AudioBatchesFolder
@ -22,7 +21,7 @@ import app.myzel394.alibi.ui.utils.MicrophoneInfo
import java.lang.IllegalStateException
class AudioRecorderService :
IntervalRecorderService<AudioRecorderService.Settings, RecordingInformation>() {
IntervalRecorderService<RecordingInformation>() {
override var batchesFolder: BatchesFolder = AudioBatchesFolder.viaInternalFolder(this)
private val handler = Handler(Looper.getMainLooper())
@ -169,6 +168,8 @@ class AudioRecorderService :
} else {
MediaRecorder()
}.apply {
val audioSettings = settings.audioRecorderSettings
// 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)
@ -180,22 +181,25 @@ class AudioRecorderService :
when (batchesFolder.type) {
BatchesFolder.BatchType.INTERNAL -> {
setOutputFile(
batchesFolder.asInternalGetOutputPath(counter, settings.fileExtension)
batchesFolder.asInternalGetOutputPath(counter, audioSettings.fileExtension)
)
}
BatchesFolder.BatchType.CUSTOM -> {
setOutputFile(
batchesFolder.asCustomGetFileDescriptor(counter, settings.fileExtension)
batchesFolder.asCustomGetFileDescriptor(
counter,
audioSettings.fileExtension
)
)
}
}
setOutputFormat(settings.outputFormat)
setOutputFormat(audioSettings.getOutputFormat())
setAudioEncoder(settings.encoder)
setAudioEncodingBitRate(settings.bitRate)
setAudioSamplingRate(settings.samplingRate)
setAudioEncoder(audioSettings.getEncoder())
setAudioEncodingBitRate(audioSettings.bitRate)
setAudioSamplingRate(audioSettings.getSamplingRate())
setOnErrorListener(OnErrorListener { _, _, _ ->
onError()
})
@ -281,47 +285,8 @@ class AudioRecorderService :
folderPath = batchesFolder.exportFolderForSettings(),
recordingStart = recordingStart,
maxDuration = settings.maxDuration,
fileExtension = settings.fileExtension,
fileExtension = settings.audioRecorderSettings.fileExtension,
intervalDuration = settings.intervalDuration,
type = RecordingInformation.Type.AUDIO,
)
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(appSettings: AppSettings): Settings {
return Settings(
intervalDuration = appSettings.intervalDuration,
maxDuration = appSettings.maxDuration,
bitRate = appSettings.audioRecorderSettings.bitRate,
samplingRate = appSettings.audioRecorderSettings.getSamplingRate(),
outputFormat = appSettings.audioRecorderSettings.getOutputFormat(),
encoder = appSettings.audioRecorderSettings.getEncoder(),
)
}
}
}
}

View File

@ -1,16 +1,17 @@
package app.myzel394.alibi.services
import app.myzel394.alibi.db.AppSettings
import app.myzel394.alibi.helpers.BatchesFolder
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
abstract class IntervalRecorderService<S : IntervalRecorderService.Settings, I> :
abstract class IntervalRecorderService<I> :
RecorderService() {
protected var counter = 0L
private set
lateinit var settings: S
lateinit var settings: AppSettings
private lateinit var cycleTimer: ScheduledExecutorService

View File

@ -19,7 +19,6 @@ import androidx.camera.video.VideoRecordEvent
import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat
import app.myzel394.alibi.NotificationHelper
import app.myzel394.alibi.db.AppSettings
import app.myzel394.alibi.db.RecordingInformation
import app.myzel394.alibi.enums.RecorderState
import app.myzel394.alibi.helpers.BatchesFolder
@ -34,7 +33,7 @@ import kotlinx.coroutines.withTimeoutOrNull
import kotlin.properties.Delegates
class VideoRecorderService :
IntervalRecorderService<VideoRecorderService.Settings, RecordingInformation>() {
IntervalRecorderService<RecordingInformation>() {
override var batchesFolder: BatchesFolder = VideoBatchesFolder.viaInternalFolder(this)
private val job = SupervisorJob()
@ -149,18 +148,22 @@ class VideoRecorderService :
}
private fun buildRecorder() = Recorder.Builder()
.setQualitySelector(settings.quality)
.setQualitySelector(
settings.videoRecorderSettings.getQualitySelector()
?: QualitySelector.from(Quality.HIGHEST)
)
.apply {
if (settings.targetVideoBitRate != null) {
setTargetVideoEncodingBitRate(settings.targetVideoBitRate!!)
if (settings.videoRecorderSettings.targetedVideoBitRate != null) {
setTargetVideoEncodingBitRate(settings.videoRecorderSettings.targetedVideoBitRate!!)
}
}
.build()
private fun buildVideoCapture(recorder: Recorder) = VideoCapture.Builder(recorder)
.apply {
if (settings.targetFrameRate != null) {
setTargetFrameRate(Range(settings.targetFrameRate!!, settings.targetFrameRate!!))
val frameRate = settings.videoRecorderSettings.targetFrameRate
if (frameRate != null) {
setTargetFrameRate(Range(frameRate, frameRate))
}
}
.build()
@ -218,7 +221,7 @@ class VideoRecorderService :
@SuppressLint("MissingPermission")
private fun prepareVideoRecording() =
videoCapture!!.output
.prepareRecording(this, settings.getOutputOptions(this))
.prepareRecording(this, getOutputOptions())
.run {
if (enableAudio) {
return@run withAudioEnabled()
@ -231,49 +234,22 @@ class VideoRecorderService :
folderPath = batchesFolder.exportFolderForSettings(),
recordingStart = recordingStart,
maxDuration = settings.maxDuration,
fileExtension = settings.fileExtension,
fileExtension = settings.videoRecorderSettings.fileExtension,
intervalDuration = settings.intervalDuration,
type = RecordingInformation.Type.VIDEO,
)
companion object {
const val CAMERA_CLOSE_TIMEOUT = 20000L
fun getOutputOptions(): FileOutputOptions {
val fileName = "${counter}.${settings.videoRecorderSettings.fileExtension}"
val file = batchesFolder.getInternalFolder().resolve(fileName).apply {
createNewFile()
}
return FileOutputOptions.Builder(file).build()
}
data class Settings(
override val maxDuration: Long,
override val intervalDuration: Long,
val folder: String? = null,
val targetVideoBitRate: Int? = null,
val targetFrameRate: Int? = null,
val quality: QualitySelector = QualitySelector.from(Quality.HIGHEST),
) : IntervalRecorderService.Settings(
maxDuration = maxDuration,
intervalDuration = intervalDuration
) {
val fileExtension
get() = "mp4"
fun getOutputOptions(video: VideoRecorderService): FileOutputOptions {
val fileName = "${video.counter}.$fileExtension"
val file = video.batchesFolder.getInternalFolder().resolve(fileName).apply {
createNewFile()
}
return FileOutputOptions.Builder(file).build()
}
companion object {
fun from(appSettings: AppSettings) = Settings(
maxDuration = appSettings.maxDuration,
intervalDuration = appSettings.intervalDuration,
folder = appSettings.saveFolder,
targetVideoBitRate = appSettings.videoRecorderSettings.targetedVideoBitRate,
targetFrameRate = appSettings.videoRecorderSettings.targetFrameRate,
quality = appSettings.videoRecorderSettings.getQualitySelector()
?: QualitySelector.from(Quality.HIGHEST),
)
}
companion object {
const val CAMERA_CLOSE_TIMEOUT = 20000L
}
class CameraControl(

View File

@ -31,9 +31,8 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
typealias RecorderModel = BaseRecorderModel<
IntervalRecorderService.Settings,
RecordingInformation,
IntervalRecorderService<IntervalRecorderService.Settings, RecordingInformation>,
IntervalRecorderService<RecordingInformation>,
BatchesFolder?
>

View File

@ -56,7 +56,7 @@ fun AudioRecordingStatus(
recordingTime = audioRecorder.recordingTime,
progress = audioRecorder.progress,
recordingStart = audioRecorder.recordingStart,
maxDuration = audioRecorder.settings.maxDuration,
maxDuration = audioRecorder.settings!!.maxDuration,
)
MicrophoneStatus(audioRecorder)

View File

@ -110,7 +110,7 @@ fun VideoRecordingStatus(
recordingTime = videoRecorder.recordingTime,
progress = videoRecorder.progress,
recordingStart = videoRecorder.recordingStart,
maxDuration = videoRecorder.settings.maxDuration,
maxDuration = videoRecorder.settings!!.maxDuration,
)
}

View File

@ -13,7 +13,7 @@ import app.myzel394.alibi.services.AudioRecorderService
import app.myzel394.alibi.ui.utils.MicrophoneInfo
class AudioRecorderModel :
BaseRecorderModel<AudioRecorderService.Settings, RecordingInformation, AudioRecorderService, AudioBatchesFolder?>() {
BaseRecorderModel<RecordingInformation, AudioRecorderService, AudioBatchesFolder?>() {
override var batchesFolder: AudioBatchesFolder? = null
override val intentClass = AudioRecorderService::class.java
@ -46,8 +46,6 @@ class AudioRecorderModel :
amplitudes = amps
onAmplitudeChange()
}
service.settings =
AudioRecorderService.Settings.from(settings)
service.clearAllRecordings()
service.startRecording()

View File

@ -19,7 +19,7 @@ import app.myzel394.alibi.services.RecorderNotificationHelper
import app.myzel394.alibi.services.RecorderService
import kotlinx.serialization.json.Json
abstract class BaseRecorderModel<S : IntervalRecorderService.Settings, I, T : IntervalRecorderService<S, I>, B : BatchesFolder?> :
abstract class BaseRecorderModel<I, T : IntervalRecorderService<I>, B : BatchesFolder?> :
ViewModel() {
protected abstract val intentClass: Class<T>
@ -51,7 +51,7 @@ abstract class BaseRecorderModel<S : IntervalRecorderService.Settings, I, T : In
private var notificationDetails: RecorderNotificationHelper.NotificationDetails? = null
lateinit var settings: AppSettings
var settings: AppSettings? = null
protected set
protected abstract fun onServiceConnected(service: T)
@ -77,6 +77,14 @@ abstract class BaseRecorderModel<S : IntervalRecorderService.Settings, I, T : In
batchesFolder = recorder.batchesFolder as B
}
if (settings != null) {
// If `settings` is set, it means we started the recording, so it should be
// properly set on the service
recorder.settings = settings!!
} else {
settings = recorder.settings
}
// Rest should be initialized from the child class
onServiceConnected(recorder)
}

View File

@ -17,7 +17,7 @@ import app.myzel394.alibi.ui.utils.CameraInfo
import app.myzel394.alibi.ui.utils.PermissionHelper
class VideoRecorderModel :
BaseRecorderModel<VideoRecorderService.Settings, RecordingInformation, VideoRecorderService, VideoBatchesFolder?>() {
BaseRecorderModel<RecordingInformation, VideoRecorderService, VideoBatchesFolder?>() {
override var batchesFolder: VideoBatchesFolder? = null
override val intentClass = VideoRecorderService::class.java
@ -39,8 +39,6 @@ class VideoRecorderModel :
}
override fun onServiceConnected(service: VideoRecorderService) {
service.settings = VideoRecorderService.Settings.from(settings)
service.clearAllRecordings()
service.startRecording()