mirror of
https://github.com/Myzel394/Alibi.git
synced 2025-06-18 23:05:26 +02:00
fix: Properly reconnect audio on app focus
This commit is contained in:
parent
ff8ea3e1f2
commit
cda0b7f195
@ -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()
|
||||
|
||||
|
@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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?
|
||||
>
|
||||
|
||||
|
@ -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)
|
||||
|
@ -110,7 +110,7 @@ fun VideoRecordingStatus(
|
||||
recordingTime = videoRecorder.recordingTime,
|
||||
progress = videoRecorder.progress,
|
||||
recordingStart = videoRecorder.recordingStart,
|
||||
maxDuration = videoRecorder.settings.maxDuration,
|
||||
maxDuration = videoRecorder.settings!!.maxDuration,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user