mirror of
https://github.com/Myzel394/Alibi.git
synced 2025-06-18 23:05:26 +02:00
current stand
This commit is contained in:
parent
de30f681e8
commit
3d355df522
@ -1,7 +1,10 @@
|
||||
package app.myzel394.alibi.enums
|
||||
|
||||
enum class RecorderState {
|
||||
IDLE,
|
||||
STOPPED,
|
||||
RECORDING,
|
||||
PAUSED,
|
||||
|
||||
// Only used by the model to indicate that the service is not running
|
||||
IDLE
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package app.myzel394.alibi.services
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.media.AudioDeviceCallback
|
||||
import android.media.AudioDeviceInfo
|
||||
import android.media.AudioManager
|
||||
@ -9,6 +10,8 @@ import android.media.MediaRecorder.OnErrorListener
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
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
|
||||
@ -71,11 +74,10 @@ class AudioRecorderService :
|
||||
}
|
||||
|
||||
override suspend fun stop() {
|
||||
super.stop()
|
||||
|
||||
resetRecorder()
|
||||
selectedMicrophone = null
|
||||
unregisterMicrophoneListener()
|
||||
|
||||
super.stop()
|
||||
}
|
||||
|
||||
override fun resume() {
|
||||
@ -83,6 +85,19 @@ class AudioRecorderService :
|
||||
createAmplitudesTimer()
|
||||
}
|
||||
|
||||
override fun startForegroundService() {
|
||||
ServiceCompat.startForeground(
|
||||
this,
|
||||
NotificationHelper.RECORDER_CHANNEL_NOTIFICATION_ID,
|
||||
getNotificationHelper().buildStartingNotification(),
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
|
||||
} else {
|
||||
0
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// ==== Amplitude related ====
|
||||
private fun getAmplitudeAmount(): Int = amplitudesAmount
|
||||
|
||||
|
@ -57,6 +57,7 @@ abstract class IntervalRecorderService<S : IntervalRecorderService.Settings, I>
|
||||
|
||||
override suspend fun stop() {
|
||||
cycleTimer.shutdown()
|
||||
super.stop()
|
||||
}
|
||||
|
||||
fun clearAllRecordings() {
|
||||
|
@ -112,7 +112,7 @@ data class RecorderNotificationHelper(
|
||||
.addAction(
|
||||
R.drawable.ic_cancel,
|
||||
context.getString(R.string.ui_audioRecorder_action_delete_label),
|
||||
getNotificationChangeStateIntent(RecorderState.IDLE, 1),
|
||||
getNotificationChangeStateIntent(RecorderState.STOPPED, 1),
|
||||
)
|
||||
.addAction(
|
||||
R.drawable.ic_pause,
|
||||
|
@ -2,7 +2,6 @@ package app.myzel394.alibi.services
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.Binder
|
||||
@ -25,29 +24,60 @@ abstract class RecorderService : LifecycleService() {
|
||||
private val binder = RecorderBinder()
|
||||
|
||||
private var isPaused: Boolean = false
|
||||
|
||||
lateinit var recordingStart: LocalDateTime
|
||||
private set
|
||||
private lateinit var recordingTimeTimer: ScheduledExecutorService
|
||||
private var notificationDetails: RecorderNotificationHelper.NotificationDetails? = null
|
||||
|
||||
var state = RecorderState.IDLE
|
||||
private set
|
||||
|
||||
protected var _newState = RecorderState.IDLE
|
||||
var state = RecorderState.STOPPED
|
||||
private set
|
||||
|
||||
var onStateChange: ((RecorderState) -> Unit)? = null
|
||||
var onError: () -> Unit = {}
|
||||
var onRecordingTimeChange: ((Long) -> Unit)? = null
|
||||
|
||||
var recordingTime = 0L
|
||||
private set
|
||||
private lateinit var recordingTimeTimer: ScheduledExecutorService
|
||||
var onRecordingTimeChange: ((Long) -> Unit)? = null
|
||||
var notificationDetails: RecorderNotificationHelper.NotificationDetails? = null
|
||||
|
||||
protected abstract fun start()
|
||||
|
||||
protected abstract fun pause()
|
||||
|
||||
// TODO: Move pause / recording here
|
||||
protected abstract fun resume()
|
||||
protected abstract suspend fun stop()
|
||||
protected open suspend fun stop() {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
NotificationManagerCompat.from(this)
|
||||
.cancel(NotificationHelper.RECORDER_CHANNEL_NOTIFICATION_ID)
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
stopSelf()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
protected abstract fun startForegroundService()
|
||||
|
||||
fun startRecording() {
|
||||
recordingStart = LocalDateTime.now()
|
||||
|
||||
startForegroundService()
|
||||
changeState(RecorderState.RECORDING)
|
||||
start()
|
||||
}
|
||||
|
||||
suspend fun stopRecording() {
|
||||
changeState(RecorderState.STOPPED)
|
||||
stop()
|
||||
}
|
||||
|
||||
fun pauseRecording() {
|
||||
changeState(RecorderState.PAUSED)
|
||||
}
|
||||
|
||||
fun resumeRecording() {
|
||||
changeState(RecorderState.RECORDING)
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
super.onBind(intent)
|
||||
@ -68,7 +98,7 @@ abstract class RecorderService : LifecycleService() {
|
||||
"changeState" -> {
|
||||
val newState = intent.getStringExtra("newState")?.let {
|
||||
RecorderState.valueOf(it)
|
||||
} ?: RecorderState.IDLE
|
||||
} ?: RecorderState.STOPPED
|
||||
changeState(newState)
|
||||
}
|
||||
}
|
||||
@ -94,14 +124,9 @@ abstract class RecorderService : LifecycleService() {
|
||||
}
|
||||
}
|
||||
|
||||
protected fun _changeStateValue(newState: RecorderState) {
|
||||
state = newState
|
||||
|
||||
onStateChange?.invoke(newState)
|
||||
}
|
||||
|
||||
// Used to change the state of the service
|
||||
// will internally call start() / pause() / resume() / stop()
|
||||
// Immediately after creating the service make sure to call `changeState(RecorderState.RECORDING)`
|
||||
@SuppressLint("MissingPermission")
|
||||
fun changeState(newState: RecorderState) {
|
||||
if (state == newState) {
|
||||
@ -114,23 +139,24 @@ abstract class RecorderService : LifecycleService() {
|
||||
if (isPaused) {
|
||||
resume()
|
||||
isPaused = false
|
||||
} else {
|
||||
start()
|
||||
}
|
||||
// `start` is handled by `startRecording`
|
||||
|
||||
createRecordingTimeTimer()
|
||||
}
|
||||
|
||||
RecorderState.PAUSED -> {
|
||||
pause()
|
||||
isPaused = true
|
||||
|
||||
recordingTimeTimer.shutdown()
|
||||
pause()
|
||||
}
|
||||
|
||||
RecorderState.IDLE -> {
|
||||
RecorderState.STOPPED -> {
|
||||
recordingTimeTimer.shutdown()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
|
||||
// Update notification
|
||||
@ -148,48 +174,13 @@ abstract class RecorderService : LifecycleService() {
|
||||
)
|
||||
}
|
||||
|
||||
_changeStateValue(newState)
|
||||
onStateChange?.invoke(newState)
|
||||
}
|
||||
|
||||
// Must be immediately called after creating the service!
|
||||
fun startRecording() {
|
||||
recordingStart = LocalDateTime.now()
|
||||
|
||||
ServiceCompat.startForeground(
|
||||
this,
|
||||
NotificationHelper.RECORDER_CHANNEL_NOTIFICATION_ID,
|
||||
getNotificationHelper().buildStartingNotification(),
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
|
||||
} else {
|
||||
0
|
||||
},
|
||||
)
|
||||
|
||||
// Start
|
||||
changeState(RecorderState.RECORDING)
|
||||
}
|
||||
|
||||
suspend fun stopRecording() {
|
||||
_newState = RecorderState.IDLE
|
||||
stop()
|
||||
changeState(RecorderState.IDLE)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
NotificationManagerCompat.from(this)
|
||||
.cancel(NotificationHelper.RECORDER_CHANNEL_NOTIFICATION_ID)
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
private fun getNotificationHelper(): RecorderNotificationHelper {
|
||||
protected fun getNotificationHelper(): RecorderNotificationHelper {
|
||||
return RecorderNotificationHelper(this, notificationDetails)
|
||||
}
|
||||
|
||||
|
||||
private fun buildNotification(): Notification {
|
||||
val notificationHelper = getNotificationHelper()
|
||||
|
||||
|
@ -2,9 +2,10 @@ package app.myzel394.alibi.services
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.Build
|
||||
import android.util.Range
|
||||
import androidx.camera.core.Camera
|
||||
import androidx.camera.core.CameraControl
|
||||
import androidx.camera.core.CameraSelector
|
||||
import androidx.camera.core.TorchState
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
@ -15,7 +16,9 @@ import androidx.camera.video.Recorder
|
||||
import androidx.camera.video.Recording
|
||||
import androidx.camera.video.VideoCapture
|
||||
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
|
||||
@ -25,7 +28,6 @@ import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
@ -100,6 +102,19 @@ class VideoRecorderService :
|
||||
stopActiveRecording()
|
||||
}
|
||||
|
||||
override fun startForegroundService() {
|
||||
ServiceCompat.startForeground(
|
||||
this,
|
||||
NotificationHelper.RECORDER_CHANNEL_NOTIFICATION_ID,
|
||||
getNotificationHelper().buildStartingNotification(),
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
|
||||
} else {
|
||||
0
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
override fun startNewCycle() {
|
||||
super.startNewCycle()
|
||||
@ -109,7 +124,7 @@ class VideoRecorderService :
|
||||
val newRecording = prepareVideoRecording()
|
||||
|
||||
activeRecording = newRecording.start(ContextCompat.getMainExecutor(this)) { event ->
|
||||
if (event is VideoRecordEvent.Finalize && this@VideoRecorderService._newState == RecorderState.IDLE) {
|
||||
if (event is VideoRecordEvent.Finalize && this@VideoRecorderService.state == RecorderState.STOPPED) {
|
||||
_videoFinalizerListener.complete(Unit)
|
||||
}
|
||||
}
|
||||
|
@ -4,22 +4,19 @@ import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.net.Uri
|
||||
import android.os.IBinder
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableLongStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.ViewModel
|
||||
import app.myzel394.alibi.db.AppSettings
|
||||
import app.myzel394.alibi.enums.RecorderState
|
||||
import app.myzel394.alibi.helpers.BatchesFolder
|
||||
import app.myzel394.alibi.services.AudioRecorderService
|
||||
import app.myzel394.alibi.services.IntervalRecorderService
|
||||
import app.myzel394.alibi.services.RecorderNotificationHelper
|
||||
import app.myzel394.alibi.services.RecorderService
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
abstract class BaseRecorderModel<S : IntervalRecorderService.Settings, I, T : IntervalRecorderService<S, I>, B : BatchesFolder?> :
|
||||
@ -28,19 +25,19 @@ abstract class BaseRecorderModel<S : IntervalRecorderService.Settings, I, T : In
|
||||
|
||||
var recorderState by mutableStateOf(RecorderState.IDLE)
|
||||
protected set
|
||||
var recordingTime by mutableStateOf<Long?>(null)
|
||||
var recordingTime by mutableLongStateOf(0)
|
||||
protected set
|
||||
|
||||
open val isInRecording: Boolean
|
||||
get() = recorderState !== RecorderState.IDLE && recordingTime != null && recorderService != null
|
||||
get() = recorderService != null
|
||||
|
||||
val isPaused: Boolean
|
||||
get() = recorderState === RecorderState.PAUSED
|
||||
|
||||
val progress: Float
|
||||
get() = (recordingTime!! / recorderService!!.settings.maxDuration).toFloat()
|
||||
get() = (recordingTime / recorderService!!.settings.maxDuration).toFloat()
|
||||
|
||||
var recorderService: T? = null
|
||||
var recorderService by mutableStateOf<T?>(null)
|
||||
protected set
|
||||
|
||||
var onRecordingSave: () -> Unit = {}
|
||||
@ -80,14 +77,14 @@ abstract class BaseRecorderModel<S : IntervalRecorderService.Settings, I, T : In
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(arg0: ComponentName) {
|
||||
recorderService = null
|
||||
reset()
|
||||
}
|
||||
}
|
||||
|
||||
open fun reset() {
|
||||
recorderService = null
|
||||
recorderState = RecorderState.IDLE
|
||||
recordingTime = null
|
||||
recordingTime = 0
|
||||
}
|
||||
|
||||
protected open fun handleIntent(intent: Intent) = intent
|
||||
@ -99,6 +96,7 @@ abstract class BaseRecorderModel<S : IntervalRecorderService.Settings, I, T : In
|
||||
) {
|
||||
this.settings = settings
|
||||
|
||||
// Clean up
|
||||
runCatching {
|
||||
recorderService?.clearAllRecordings()
|
||||
context.unbindService(connection)
|
||||
@ -132,22 +130,25 @@ abstract class BaseRecorderModel<S : IntervalRecorderService.Settings, I, T : In
|
||||
}
|
||||
|
||||
suspend fun stopRecording(context: Context) {
|
||||
// TODO: Make modal on video only appear on long press and by default use back camera
|
||||
// TODO: Also show what camera is in use while recording
|
||||
recorderService!!.stopRecording()
|
||||
|
||||
val intent = Intent(context, intentClass)
|
||||
|
||||
unbindFromService(context)
|
||||
context.unbindService(connection)
|
||||
context.stopService(intent)
|
||||
}
|
||||
|
||||
fun pauseRecording() {
|
||||
recorderService!!.changeState(RecorderState.PAUSED)
|
||||
recorderService!!.pauseRecording()
|
||||
}
|
||||
|
||||
fun resumeRecording() {
|
||||
recorderService!!.changeState(RecorderState.RECORDING)
|
||||
recorderService!!.resumeRecording()
|
||||
}
|
||||
|
||||
// Bind functions used to manually bind to the service if the app
|
||||
// is closed and reopened for example
|
||||
fun bindToService(context: Context) {
|
||||
Intent(context, intentClass).also { intent ->
|
||||
context.bindService(intent, connection, 0)
|
||||
|
@ -113,6 +113,7 @@ fun RecorderScreen(
|
||||
videoRecorder = videoRecorder,
|
||||
appSettings = appSettings,
|
||||
onSaveLastRecording = {
|
||||
// TODO: Improve onSave!
|
||||
},
|
||||
showAudioRecorder = topBarVisible,
|
||||
onHideTopBar = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user