mirror of
https://github.com/Myzel394/Alibi.git
synced 2025-06-18 23:05:26 +02:00
refactor: Migrate foreground services to new architecture
This commit is contained in:
parent
bc42f35eba
commit
0bfed18314
@ -37,7 +37,7 @@
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<service android:name=".services.RecorderService" android:foregroundServiceType="microphone" />
|
||||
<service android:name=".services.AudioRecorderService" android:foregroundServiceType="microphone|camera" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
@ -4,11 +4,13 @@ import android.media.MediaRecorder
|
||||
import android.os.Build
|
||||
|
||||
class AudioRecorderService: IntervalRecorderService() {
|
||||
var amplitudesAmount = 1000
|
||||
|
||||
var recorder: MediaRecorder? = null
|
||||
private set
|
||||
|
||||
val filePath: String
|
||||
get() = "$folder/$counter.${settings.fileExtension}"
|
||||
get() = "$folder/$counter.${settings!!.fileExtension}"
|
||||
|
||||
private fun createRecorder(): MediaRecorder {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
@ -18,10 +20,10 @@ class AudioRecorderService: IntervalRecorderService() {
|
||||
}.apply {
|
||||
setAudioSource(MediaRecorder.AudioSource.MIC)
|
||||
setOutputFile(filePath)
|
||||
setOutputFormat(settings.outputFormat)
|
||||
setAudioEncoder(settings.encoder)
|
||||
setAudioEncodingBitRate(settings.bitRate)
|
||||
setAudioSamplingRate(settings.samplingRate)
|
||||
setOutputFormat(settings!!.outputFormat)
|
||||
setAudioEncoder(settings!!.encoder)
|
||||
setAudioEncodingBitRate(settings!!.bitRate)
|
||||
setAudioSamplingRate(settings!!.samplingRate)
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,9 +49,13 @@ class AudioRecorderService: IntervalRecorderService() {
|
||||
recorder = newRecorder
|
||||
}
|
||||
|
||||
override fun getAmplitudeAmount(): Int {
|
||||
return 100
|
||||
override fun stop() {
|
||||
super.stop()
|
||||
|
||||
resetRecorder()
|
||||
}
|
||||
|
||||
override fun getAmplitudeAmount(): Int = amplitudesAmount
|
||||
|
||||
override fun getAmplitude(): Int = recorder?.maxAmplitude ?: 0
|
||||
}
|
@ -2,6 +2,7 @@ package app.myzel394.alibi.services
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import app.myzel394.alibi.enums.RecorderState
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
import java.util.concurrent.Executors
|
||||
@ -12,12 +13,12 @@ abstract class ExtraRecorderInformationService: RecorderService() {
|
||||
abstract fun getAmplitudeAmount(): Int
|
||||
abstract fun getAmplitude(): Int
|
||||
|
||||
private var recordingTime = 0L
|
||||
var recordingTime = 0L
|
||||
private set
|
||||
private lateinit var recordingTimeTimer: ScheduledExecutorService
|
||||
|
||||
var amplitudes = mutableListOf<Int>()
|
||||
private set
|
||||
private lateinit var amplitudesTimer: Timer
|
||||
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
@ -29,6 +30,7 @@ abstract class ExtraRecorderInformationService: RecorderService() {
|
||||
it.scheduleAtFixedRate(
|
||||
{
|
||||
recordingTime += 1000
|
||||
onRecordingTimeChange?.invoke(recordingTime)
|
||||
},
|
||||
0,
|
||||
1000,
|
||||
@ -38,7 +40,12 @@ abstract class ExtraRecorderInformationService: RecorderService() {
|
||||
}
|
||||
|
||||
private fun updateAmplitude() {
|
||||
if (state !== RecorderState.RECORDING) {
|
||||
return
|
||||
}
|
||||
|
||||
amplitudes.add(getAmplitude())
|
||||
onAmplitudeChange?.invoke(amplitudes)
|
||||
|
||||
// Delete old amplitudes
|
||||
if (amplitudes.size > getAmplitudeAmount()) {
|
||||
@ -49,17 +56,7 @@ abstract class ExtraRecorderInformationService: RecorderService() {
|
||||
}
|
||||
|
||||
private fun createAmplitudesTimer() {
|
||||
amplitudesTimer = Timer().also {
|
||||
it.scheduleAtFixedRate(
|
||||
object: TimerTask() {
|
||||
override fun run() {
|
||||
updateAmplitude()
|
||||
}
|
||||
},
|
||||
0,
|
||||
100,
|
||||
)
|
||||
}
|
||||
handler.postDelayed(::updateAmplitude, 100)
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
@ -69,7 +66,6 @@ abstract class ExtraRecorderInformationService: RecorderService() {
|
||||
|
||||
override fun pause() {
|
||||
recordingTimeTimer.shutdown()
|
||||
amplitudesTimer.cancel()
|
||||
}
|
||||
|
||||
override fun resume() {
|
||||
@ -79,7 +75,6 @@ abstract class ExtraRecorderInformationService: RecorderService() {
|
||||
|
||||
override fun stop() {
|
||||
recordingTimeTimer.shutdown()
|
||||
amplitudesTimer.cancel()
|
||||
}
|
||||
|
||||
}
|
@ -1,21 +1,33 @@
|
||||
package app.myzel394.alibi.services
|
||||
|
||||
import android.content.Context
|
||||
import android.media.MediaRecorder
|
||||
import app.myzel394.alibi.dataStore
|
||||
import app.myzel394.alibi.db.AudioRecorderSettings
|
||||
import app.myzel394.alibi.db.LastRecording
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.time.LocalDateTime
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
abstract class IntervalRecorderService: ExtraRecorderInformationService() {
|
||||
private var job = SupervisorJob()
|
||||
private var scope = CoroutineScope(Dispatchers.IO + job)
|
||||
|
||||
protected var counter = 0
|
||||
private set
|
||||
protected lateinit var folder: File
|
||||
lateinit var settings: Settings
|
||||
var settings: Settings? = null
|
||||
protected set
|
||||
|
||||
private lateinit var cycleTimer: ScheduledExecutorService
|
||||
@ -23,10 +35,10 @@ abstract class IntervalRecorderService: ExtraRecorderInformationService() {
|
||||
fun createLastRecording(): LastRecording = LastRecording(
|
||||
folderPath = folder.absolutePath,
|
||||
recordingStart = recordingStart,
|
||||
maxDuration = settings.maxDuration,
|
||||
fileExtension = settings.fileExtension,
|
||||
intervalDuration = settings.intervalDuration,
|
||||
forceExactMaxDuration = settings.forceExactMaxDuration,
|
||||
maxDuration = settings!!.maxDuration,
|
||||
fileExtension = settings!!.fileExtension,
|
||||
intervalDuration = settings!!.intervalDuration,
|
||||
forceExactMaxDuration = settings!!.forceExactMaxDuration,
|
||||
)
|
||||
|
||||
// Make overrideable
|
||||
@ -41,32 +53,53 @@ abstract class IntervalRecorderService: ExtraRecorderInformationService() {
|
||||
startNewCycle()
|
||||
},
|
||||
0,
|
||||
settings.intervalDuration,
|
||||
settings!!.intervalDuration,
|
||||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getRandomFileFolder(): String {
|
||||
// uuid
|
||||
val folder = UUID.randomUUID().toString()
|
||||
|
||||
return "${externalCacheDir!!.absolutePath}/$folder"
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
super.start()
|
||||
|
||||
folder = File(getRandomFileFolder())
|
||||
folder.mkdirs()
|
||||
|
||||
createTimer()
|
||||
scope.launch {
|
||||
dataStore.data.collectLatest { preferenceSettings ->
|
||||
if (settings == null) {
|
||||
settings = Settings.from(preferenceSettings.audioRecorderSettings)
|
||||
|
||||
createTimer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun pause() {
|
||||
super.pause()
|
||||
cycleTimer.shutdown()
|
||||
}
|
||||
|
||||
override fun resume() {
|
||||
super.resume()
|
||||
createTimer()
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
super.stop()
|
||||
cycleTimer.shutdown()
|
||||
}
|
||||
|
||||
private fun deleteOldRecordings() {
|
||||
val timeMultiplier = settings.maxDuration / settings.intervalDuration
|
||||
val timeMultiplier = settings!!.maxDuration / settings!!.intervalDuration
|
||||
val earliestCounter = counter - timeMultiplier
|
||||
|
||||
folder.listFiles()?.forEach { file ->
|
||||
|
@ -26,7 +26,7 @@ abstract class RecorderService: Service() {
|
||||
lateinit var recordingStart: LocalDateTime
|
||||
private set
|
||||
|
||||
var state = RecorderState.RECORDING
|
||||
var state = RecorderState.IDLE
|
||||
private set
|
||||
|
||||
var onStateChange: ((RecorderState) -> Unit)? = null
|
||||
@ -60,28 +60,34 @@ abstract class RecorderService: Service() {
|
||||
pause()
|
||||
isPaused = true
|
||||
}
|
||||
else -> throw IllegalStateException("$newState is not a valid state. Destroy or recreate the service instead.")
|
||||
RecorderState.IDLE -> stop()
|
||||
}
|
||||
|
||||
state = newState
|
||||
onStateChange?.invoke(newState)
|
||||
}
|
||||
|
||||
// Must be called immediately after the service is created
|
||||
fun startRecording() {
|
||||
recordingStart = LocalDateTime.now()
|
||||
|
||||
val notification = buildNotification()
|
||||
startForeground(NotificationHelper.RECORDER_CHANNEL_NOTIFICATION_ID, notification)
|
||||
|
||||
// Start
|
||||
changeState(RecorderState.RECORDING)
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
val notification = buildNotification()
|
||||
|
||||
startForeground(NotificationHelper.RECORDER_CHANNEL_NOTIFICATION_ID, notification)
|
||||
|
||||
recordingStart = LocalDateTime.now()
|
||||
start()
|
||||
startRecording()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
stop()
|
||||
changeState(RecorderState.IDLE)
|
||||
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
stopSelf()
|
||||
|
@ -11,11 +11,13 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import app.myzel394.alibi.dataStore
|
||||
import app.myzel394.alibi.ui.enums.Screen
|
||||
import app.myzel394.alibi.ui.models.AudioRecorderModel
|
||||
import app.myzel394.alibi.ui.screens.AudioRecorder
|
||||
import app.myzel394.alibi.ui.screens.SettingsScreen
|
||||
import app.myzel394.alibi.ui.screens.WelcomeScreen
|
||||
@ -23,7 +25,9 @@ import app.myzel394.alibi.ui.screens.WelcomeScreen
|
||||
const val SCALE_IN = 1.25f
|
||||
|
||||
@Composable
|
||||
fun Navigation() {
|
||||
fun Navigation(
|
||||
audioRecorder: AudioRecorderModel = viewModel()
|
||||
) {
|
||||
val navController = rememberNavController()
|
||||
val context = LocalContext.current
|
||||
val settings = context
|
||||
@ -32,6 +36,8 @@ fun Navigation() {
|
||||
.collectAsState(initial = null)
|
||||
.value ?: return
|
||||
|
||||
audioRecorder.BindToService(context)
|
||||
|
||||
NavHost(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colorScheme.background),
|
||||
@ -53,7 +59,10 @@ fun Navigation() {
|
||||
scaleOut(targetScale = SCALE_IN) + fadeOut(tween(durationMillis = 150))
|
||||
}
|
||||
) {
|
||||
AudioRecorder(navController = navController)
|
||||
AudioRecorder(
|
||||
navController = navController,
|
||||
audioRecorder = audioRecorder,
|
||||
)
|
||||
}
|
||||
composable(
|
||||
Screen.Settings.route,
|
||||
@ -64,7 +73,10 @@ fun Navigation() {
|
||||
scaleOut(targetScale = 1 / SCALE_IN) + fadeOut(tween(durationMillis = 150))
|
||||
}
|
||||
) {
|
||||
SettingsScreen(navController = navController)
|
||||
SettingsScreen(
|
||||
navController = navController,
|
||||
audioRecorder = audioRecorder,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.myzel394.alibi.services.RecorderService
|
||||
import app.myzel394.alibi.ui.MAX_AMPLITUDE
|
||||
import app.myzel394.alibi.ui.models.AudioRecorderModel
|
||||
import app.myzel394.alibi.ui.utils.clamp
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.ceil
|
||||
@ -34,10 +35,10 @@ private const val GROW_END = BOX_DIFF * 4
|
||||
|
||||
@Composable
|
||||
fun RealtimeAudioVisualizer(
|
||||
service: RecorderService,
|
||||
audioRecorder: AudioRecorderModel,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val amplitudes = service.amplitudes
|
||||
val amplitudes = audioRecorder.amplitudes!!
|
||||
val primary = MaterialTheme.colorScheme.primary
|
||||
val primaryMuted = primary.copy(alpha = 0.3f)
|
||||
|
||||
@ -47,7 +48,7 @@ fun RealtimeAudioVisualizer(
|
||||
val animationProgress = remember { Animatable(0f) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
service.setOnAmplitudeUpdateListener {
|
||||
audioRecorder.onAmplitudeChange = {
|
||||
scope.launch {
|
||||
animationProgress.snapTo(0f)
|
||||
animationProgress.animateTo(
|
||||
@ -66,7 +67,7 @@ fun RealtimeAudioVisualizer(
|
||||
|
||||
LaunchedEffect(screenWidth) {
|
||||
// Add 1 to allow the visualizer to overflow the screen
|
||||
service.maxAmplitudes = ceil(screenWidth.toInt() / BOX_DIFF).toInt() + 1
|
||||
audioRecorder.setMaxAmplitudesAmount(ceil(screenWidth.toInt() / BOX_DIFF).toInt() + 1)
|
||||
}
|
||||
|
||||
Canvas(
|
||||
|
@ -88,13 +88,9 @@ fun SaveRecordingButton(
|
||||
.then(modifier),
|
||||
onClick = {
|
||||
isProcessingAudio = true
|
||||
RecorderService.stopService(context)
|
||||
|
||||
scope.launch {
|
||||
try {
|
||||
val file = service.concatenateFiles()
|
||||
|
||||
onSaveFile(file)
|
||||
} catch (error: Exception) {
|
||||
Log.getStackTraceString(error)
|
||||
} finally {
|
||||
|
@ -47,6 +47,7 @@ import app.myzel394.alibi.ui.components.AudioRecorder.atoms.ConfirmDeletionDialo
|
||||
import app.myzel394.alibi.ui.components.AudioRecorder.atoms.RealtimeAudioVisualizer
|
||||
import app.myzel394.alibi.ui.components.AudioRecorder.atoms.SaveRecordingButton
|
||||
import app.myzel394.alibi.ui.components.atoms.Pulsating
|
||||
import app.myzel394.alibi.ui.models.AudioRecorderModel
|
||||
import app.myzel394.alibi.ui.utils.KeepScreenOn
|
||||
import app.myzel394.alibi.ui.utils.formatDuration
|
||||
import kotlinx.coroutines.delay
|
||||
@ -57,15 +58,12 @@ import java.time.ZoneId
|
||||
|
||||
@Composable
|
||||
fun RecordingStatus(
|
||||
service: RecorderService,
|
||||
onSaveFile: (File) -> Unit,
|
||||
audioRecorder: AudioRecorderModel,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
var now by remember { mutableStateOf(LocalDateTime.now()) }
|
||||
|
||||
val progress = service.recordingTime.value!! / (service.settings!!.maxDuration / 1000f)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
while (true) {
|
||||
now = LocalDateTime.now()
|
||||
@ -74,7 +72,7 @@ fun RecordingStatus(
|
||||
}
|
||||
|
||||
// Only show animation when the recording has just started
|
||||
val recordingJustStarted = service.recordingTime.value!! < 1
|
||||
val recordingJustStarted = audioRecorder.recordingTime!! <= 1000L
|
||||
var progressVisible by remember { mutableStateOf(!recordingJustStarted) }
|
||||
LaunchedEffect(Unit) {
|
||||
progressVisible = true
|
||||
@ -89,7 +87,7 @@ fun RecordingStatus(
|
||||
verticalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Box {}
|
||||
RealtimeAudioVisualizer(service = service)
|
||||
RealtimeAudioVisualizer(audioRecorder = audioRecorder)
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
@ -97,7 +95,7 @@ fun RecordingStatus(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
val distance = Duration.between(service.recordingStart!!, now).toMillis()
|
||||
val distance = Duration.between(audioRecorder.recorderService!!.recordingStart, now).toMillis()
|
||||
|
||||
Pulsating {
|
||||
Box(
|
||||
@ -121,7 +119,7 @@ fun RecordingStatus(
|
||||
)
|
||||
) {
|
||||
LinearProgressIndicator(
|
||||
progress = progress,
|
||||
progress = audioRecorder.progress,
|
||||
modifier = Modifier
|
||||
.width(300.dp)
|
||||
)
|
||||
@ -137,8 +135,7 @@ fun RecordingStatus(
|
||||
},
|
||||
onConfirm = {
|
||||
showDeleteDialog = false
|
||||
RecorderService.stopService(context)
|
||||
service.reset()
|
||||
audioRecorder.stopRecording(context)
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -163,11 +160,5 @@ fun RecordingStatus(
|
||||
}
|
||||
}
|
||||
val alpha by animateFloatAsState(if (progressVisible) 1f else 0f, tween(1000))
|
||||
SaveRecordingButton(
|
||||
modifier = Modifier
|
||||
.alpha(alpha),
|
||||
service = service,
|
||||
onSaveFile = onSaveFile,
|
||||
)
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.autoSaver
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@ -52,15 +53,14 @@ import app.myzel394.alibi.ui.BIG_PRIMARY_BUTTON_SIZE
|
||||
import app.myzel394.alibi.ui.components.AudioRecorder.atoms.AudioVisualizer
|
||||
import app.myzel394.alibi.ui.components.AudioRecorder.atoms.SaveRecordingButton
|
||||
import app.myzel394.alibi.ui.components.atoms.PermissionRequester
|
||||
import app.myzel394.alibi.ui.models.AudioRecorderModel
|
||||
import app.myzel394.alibi.ui.utils.rememberFileSaverDialog
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.FormatStyle
|
||||
|
||||
@Composable
|
||||
fun StartRecording(
|
||||
connection: ServiceConnection,
|
||||
service: RecorderService? = null,
|
||||
onStart: () -> Unit,
|
||||
audioRecorder: AudioRecorderModel,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val saveFile = rememberFileSaverDialog("audio/*")
|
||||
@ -80,17 +80,7 @@ fun StartRecording(
|
||||
)
|
||||
},
|
||||
onPermissionAvailable = {
|
||||
RecorderService.startService(context, connection)
|
||||
|
||||
if (service == null) {
|
||||
onStart()
|
||||
} else {
|
||||
// To avoid any leaks from the previous recording, we need to wait until it
|
||||
// fully started
|
||||
service.setOnStartedListener {
|
||||
onStart()
|
||||
}
|
||||
}
|
||||
audioRecorder.startRecording(context)
|
||||
},
|
||||
) { trigger ->
|
||||
val label = stringResource(R.string.ui_audioRecorder_action_start_label)
|
||||
@ -140,21 +130,12 @@ fun StartRecording(
|
||||
.fillMaxWidth(),
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
if (service?.hasRecordingAvailable == true)
|
||||
if (false)
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Bottom,
|
||||
) {
|
||||
SaveRecordingButton(
|
||||
service = service,
|
||||
onSaveFile = saveFile,
|
||||
label = stringResource(
|
||||
R.string.ui_audioRecorder_action_saveOldRecording_label,
|
||||
DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).format(service.recordingStart),
|
||||
),
|
||||
|
||||
)
|
||||
}
|
||||
else
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
@ -5,6 +5,8 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
@ -19,11 +21,20 @@ class AudioRecorderModel: ViewModel() {
|
||||
private set
|
||||
var recordingTime by mutableStateOf<Long?>(null)
|
||||
private set
|
||||
var amplitudes by mutableStateOf<List<Int>?>(null)
|
||||
var amplitudes by mutableStateOf<List<Int>>(emptyList())
|
||||
private set
|
||||
|
||||
var onAmplitudeChange: () -> Unit = {}
|
||||
|
||||
val isInRecording: Boolean
|
||||
get() = recorderState !== RecorderState.IDLE && recordingTime != null
|
||||
|
||||
val progress: Float
|
||||
get() = (recordingTime!! / recorderService!!.settings!!.maxDuration).toFloat()
|
||||
|
||||
private var intent: Intent? = null
|
||||
private var recorderService: RecorderService? = null
|
||||
var recorderService: AudioRecorderService? = null
|
||||
private set
|
||||
|
||||
private val connection = object : ServiceConnection {
|
||||
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||
@ -36,8 +47,12 @@ class AudioRecorderModel: ViewModel() {
|
||||
}
|
||||
recorder.onAmplitudeChange = { amps ->
|
||||
amplitudes = amps
|
||||
onAmplitudeChange()
|
||||
}
|
||||
}
|
||||
recorderState = recorderService!!.state
|
||||
recordingTime = recorderService!!.recordingTime
|
||||
amplitudes = recorderService!!.amplitudes
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(arg0: ComponentName) {
|
||||
@ -49,7 +64,7 @@ class AudioRecorderModel: ViewModel() {
|
||||
fun reset() {
|
||||
recorderState = RecorderState.IDLE
|
||||
recordingTime = null
|
||||
amplitudes = null
|
||||
amplitudes = emptyList()
|
||||
}
|
||||
|
||||
fun startRecording(context: Context) {
|
||||
@ -57,13 +72,30 @@ class AudioRecorderModel: ViewModel() {
|
||||
context.unbindService(connection)
|
||||
}
|
||||
|
||||
val intent = Intent(context, AudioRecorderService::class.java)
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
context.bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||
intent = Intent(context, AudioRecorderService::class.java)
|
||||
ContextCompat.startForegroundService(context, intent!!)
|
||||
context.bindService(intent!!, connection, Context.BIND_AUTO_CREATE)
|
||||
}
|
||||
|
||||
fun stopRecording(context: Context) {
|
||||
context.stopService(intent)
|
||||
context.unbindService(connection)
|
||||
runCatching {
|
||||
context.unbindService(connection)
|
||||
context.stopService(intent)
|
||||
}
|
||||
|
||||
reset()
|
||||
}
|
||||
|
||||
fun setMaxAmplitudesAmount(amount: Int) {
|
||||
recorderService?.amplitudesAmount = amount
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun BindToService(context: Context) {
|
||||
LaunchedEffect(Unit) {
|
||||
Intent(context, AudioRecorderService::class.java).also { intent ->
|
||||
context.bindService(intent, connection, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -14,26 +14,25 @@ import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import app.myzel394.alibi.services.bindToRecorderService
|
||||
import app.myzel394.alibi.ui.components.AudioRecorder.molecules.RecordingStatus
|
||||
import app.myzel394.alibi.ui.components.AudioRecorder.molecules.StartRecording
|
||||
import app.myzel394.alibi.ui.enums.Screen
|
||||
import app.myzel394.alibi.ui.utils.rememberFileSaverDialog
|
||||
import app.myzel394.alibi.R
|
||||
import app.myzel394.alibi.ui.models.AudioRecorderModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AudioRecorder(
|
||||
navController: NavController,
|
||||
audioRecorder: AudioRecorderModel
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val saveFile = rememberFileSaverDialog("audio/aac")
|
||||
val (connection, service) = bindToRecorderService()
|
||||
|
||||
var showRecorderStatus by remember {
|
||||
mutableStateOf(service?.isRecording ?: false)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@ -61,22 +60,12 @@ fun AudioRecorder(
|
||||
.fillMaxSize()
|
||||
.padding(padding),
|
||||
) {
|
||||
if (showRecorderStatus && service?.recordingTime?.value != null)
|
||||
if (audioRecorder.isInRecording)
|
||||
RecordingStatus(
|
||||
service = service,
|
||||
onSaveFile = {
|
||||
saveFile(it)
|
||||
showRecorderStatus = false
|
||||
}
|
||||
audioRecorder = audioRecorder,
|
||||
)
|
||||
else
|
||||
StartRecording(
|
||||
connection = connection,
|
||||
service = service,
|
||||
onStart = {
|
||||
showRecorderStatus = true
|
||||
}
|
||||
)
|
||||
StartRecording(audioRecorder = audioRecorder)
|
||||
}
|
||||
}
|
||||
}
|
@ -32,7 +32,6 @@ import androidx.navigation.NavController
|
||||
import app.myzel394.alibi.R
|
||||
import app.myzel394.alibi.dataStore
|
||||
import app.myzel394.alibi.db.AppSettings
|
||||
import app.myzel394.alibi.services.bindToRecorderService
|
||||
import app.myzel394.alibi.ui.components.SettingsScreen.atoms.BitrateTile
|
||||
import app.myzel394.alibi.ui.components.SettingsScreen.atoms.EncoderTile
|
||||
import app.myzel394.alibi.ui.components.SettingsScreen.atoms.ForceExactMaxDurationTile
|
||||
@ -43,15 +42,15 @@ import app.myzel394.alibi.ui.components.SettingsScreen.atoms.SamplingRateTile
|
||||
import app.myzel394.alibi.ui.components.atoms.GlobalSwitch
|
||||
import app.myzel394.alibi.ui.components.atoms.MessageBox
|
||||
import app.myzel394.alibi.ui.components.atoms.MessageType
|
||||
import app.myzel394.alibi.ui.models.AudioRecorderModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
navController: NavController
|
||||
navController: NavController,
|
||||
audioRecorder: AudioRecorderModel,
|
||||
) {
|
||||
val (_, service) = bindToRecorderService()
|
||||
val isRecording = service?.isRecording ?: false
|
||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
|
||||
rememberTopAppBarState()
|
||||
)
|
||||
@ -91,7 +90,7 @@ fun SettingsScreen(
|
||||
.value
|
||||
|
||||
// Show alert
|
||||
if (isRecording)
|
||||
if (audioRecorder.isInRecording)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
|
Loading…
x
Reference in New Issue
Block a user