mirror of
https://github.com/Myzel394/Alibi.git
synced 2025-06-18 23:05:26 +02:00
feat: Use internal directory for saving files
This commit is contained in:
parent
517516518f
commit
9dc1c05d69
@ -2,7 +2,6 @@ package app.myzel394.alibi.db
|
|||||||
|
|
||||||
import android.media.MediaRecorder
|
import android.media.MediaRecorder
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
|
||||||
import app.myzel394.alibi.R
|
import app.myzel394.alibi.R
|
||||||
import com.arthenica.ffmpegkit.FFmpegKit
|
import com.arthenica.ffmpegkit.FFmpegKit
|
||||||
import com.arthenica.ffmpegkit.ReturnCode
|
import com.arthenica.ffmpegkit.ReturnCode
|
||||||
@ -63,7 +62,7 @@ data class AppSettings(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class LastRecording(
|
data class RecordingInformation(
|
||||||
val folderPath: String,
|
val folderPath: String,
|
||||||
@Serializable(with = LocalDateTimeSerializer::class)
|
@Serializable(with = LocalDateTimeSerializer::class)
|
||||||
val recordingStart: LocalDateTime,
|
val recordingStart: LocalDateTime,
|
||||||
@ -72,91 +71,8 @@ data class LastRecording(
|
|||||||
val fileExtension: String,
|
val fileExtension: String,
|
||||||
val forceExactMaxDuration: Boolean,
|
val forceExactMaxDuration: Boolean,
|
||||||
) {
|
) {
|
||||||
val fileFolder: File
|
val hasRecordingsAvailable
|
||||||
get() = File(folderPath)
|
get() = File(folderPath).listFiles()?.isNotEmpty() ?: false
|
||||||
|
|
||||||
val filePaths: List<File>
|
|
||||||
get() =
|
|
||||||
File(folderPath).listFiles()?.filter {
|
|
||||||
val name = it.nameWithoutExtension
|
|
||||||
|
|
||||||
name.toIntOrNull() != null
|
|
||||||
}?.toList() ?: emptyList()
|
|
||||||
|
|
||||||
val hasRecordingAvailable: Boolean
|
|
||||||
get() = filePaths.isNotEmpty()
|
|
||||||
|
|
||||||
private fun stripConcatenatedFileToExactDuration(
|
|
||||||
outputFile: File
|
|
||||||
) {
|
|
||||||
// Move the concatenated file to a temporary file
|
|
||||||
val rawFile = File("$folderPath/${outputFile.nameWithoutExtension}-raw.${fileExtension}")
|
|
||||||
outputFile.renameTo(rawFile)
|
|
||||||
|
|
||||||
val command = "-sseof ${maxDuration / -1000} -i $rawFile -y $outputFile"
|
|
||||||
|
|
||||||
val session = FFmpegKit.execute(command)
|
|
||||||
|
|
||||||
if (!ReturnCode.isSuccess(session.returnCode)) {
|
|
||||||
Log.d(
|
|
||||||
"Audio Concatenation",
|
|
||||||
String.format(
|
|
||||||
"Command failed with state %s and rc %s.%s",
|
|
||||||
session.getState(),
|
|
||||||
session.getReturnCode(),
|
|
||||||
session.getFailStackTrace()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
throw Exception("Failed to strip concatenated audio")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun concatenateFiles(forceConcatenation: Boolean = false): File {
|
|
||||||
val paths = filePaths.joinToString("|")
|
|
||||||
val fileName = recordingStart
|
|
||||||
.format(ISO_DATE_TIME)
|
|
||||||
.toString()
|
|
||||||
.replace(":", "-")
|
|
||||||
.replace(".", "_")
|
|
||||||
val outputFile = File("$fileFolder/$fileName.${fileExtension}")
|
|
||||||
|
|
||||||
if (outputFile.exists() && !forceConcatenation) {
|
|
||||||
return outputFile
|
|
||||||
}
|
|
||||||
|
|
||||||
val command = "-i 'concat:$paths' -y" +
|
|
||||||
" -acodec copy" +
|
|
||||||
" -metadata title='$fileName' " +
|
|
||||||
" -metadata date='${recordingStart.format(ISO_DATE_TIME)}'" +
|
|
||||||
" -metadata batch_count='${filePaths.size}'" +
|
|
||||||
" -metadata batch_duration='${intervalDuration}'" +
|
|
||||||
" -metadata max_duration='${maxDuration}'" +
|
|
||||||
" $outputFile"
|
|
||||||
|
|
||||||
val session = FFmpegKit.execute(command)
|
|
||||||
|
|
||||||
if (!ReturnCode.isSuccess(session.returnCode)) {
|
|
||||||
Log.d(
|
|
||||||
"Audio Concatenation",
|
|
||||||
String.format(
|
|
||||||
"Command failed with state %s and rc %s.%s",
|
|
||||||
session.getState(),
|
|
||||||
session.getReturnCode(),
|
|
||||||
session.getFailStackTrace()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
throw Exception("Failed to concatenate audios")
|
|
||||||
}
|
|
||||||
|
|
||||||
val minRequiredForPossibleInExactMaxDuration = maxDuration / intervalDuration
|
|
||||||
if (forceExactMaxDuration && filePaths.size > minRequiredForPossibleInExactMaxDuration) {
|
|
||||||
stripConcatenatedFileToExactDuration(outputFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
return outputFile
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
@ -0,0 +1,119 @@
|
|||||||
|
package app.myzel394.alibi.helpers
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import app.myzel394.alibi.db.RecordingInformation
|
||||||
|
import app.myzel394.alibi.ui.RECORDER_SUBFOLDER_NAME
|
||||||
|
import com.arthenica.ffmpegkit.FFmpegKit
|
||||||
|
import com.arthenica.ffmpegkit.ReturnCode
|
||||||
|
import java.io.File
|
||||||
|
import java.time.format.DateTimeFormatter
|
||||||
|
|
||||||
|
data class AudioRecorderExporter(
|
||||||
|
val recording: RecordingInformation,
|
||||||
|
) {
|
||||||
|
val filePaths: List<File>
|
||||||
|
get() =
|
||||||
|
File(recording.folderPath).listFiles()?.filter {
|
||||||
|
val name = it.nameWithoutExtension
|
||||||
|
|
||||||
|
name.toIntOrNull() != null
|
||||||
|
}?.toList() ?: emptyList()
|
||||||
|
|
||||||
|
val hasRecordingAvailable: Boolean
|
||||||
|
get() = filePaths.isNotEmpty()
|
||||||
|
|
||||||
|
private fun stripConcatenatedFileToExactDuration(
|
||||||
|
outputFile: File
|
||||||
|
) {
|
||||||
|
// Move the concatenated file to a temporary file
|
||||||
|
val rawFile =
|
||||||
|
File("${recording.folderPath}/${outputFile.nameWithoutExtension}-raw.${recording.fileExtension}")
|
||||||
|
outputFile.renameTo(rawFile)
|
||||||
|
|
||||||
|
val command = "-sseof ${recording.maxDuration / -1000} -i $rawFile -y $outputFile"
|
||||||
|
|
||||||
|
val session = FFmpegKit.execute(command)
|
||||||
|
|
||||||
|
if (!ReturnCode.isSuccess(session.returnCode)) {
|
||||||
|
Log.d(
|
||||||
|
"Audio Concatenation",
|
||||||
|
String.format(
|
||||||
|
"Command failed with state %s and rc %s.%s",
|
||||||
|
session.state,
|
||||||
|
session.returnCode,
|
||||||
|
session.failStackTrace,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
throw Exception("Failed to strip concatenated audio")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun concatenateFiles(forceConcatenation: Boolean = false): File {
|
||||||
|
val paths = filePaths.joinToString("|")
|
||||||
|
val fileName = recording.recordingStart
|
||||||
|
.format(DateTimeFormatter.ISO_DATE_TIME)
|
||||||
|
.toString()
|
||||||
|
.replace(":", "-")
|
||||||
|
.replace(".", "_")
|
||||||
|
val outputFile = File("${recording.folderPath}/$fileName.${recording.fileExtension}")
|
||||||
|
|
||||||
|
if (outputFile.exists() && !forceConcatenation) {
|
||||||
|
return outputFile
|
||||||
|
}
|
||||||
|
|
||||||
|
val command = "-i 'concat:$paths' -y" +
|
||||||
|
" -acodec copy" +
|
||||||
|
" -metadata title='$fileName' " +
|
||||||
|
" -metadata date='${recording.recordingStart.format(DateTimeFormatter.ISO_DATE_TIME)}'" +
|
||||||
|
" -metadata batch_count='${filePaths.size}'" +
|
||||||
|
" -metadata batch_duration='${recording.intervalDuration}'" +
|
||||||
|
" -metadata max_duration='${recording.maxDuration}'" +
|
||||||
|
" $outputFile"
|
||||||
|
|
||||||
|
val session = FFmpegKit.execute(command)
|
||||||
|
|
||||||
|
if (!ReturnCode.isSuccess(session.returnCode)) {
|
||||||
|
Log.d(
|
||||||
|
"Audio Concatenation",
|
||||||
|
String.format(
|
||||||
|
"Command failed with state %s and rc %s.%s",
|
||||||
|
session.state,
|
||||||
|
session.returnCode,
|
||||||
|
session.failStackTrace,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
throw Exception("Failed to concatenate audios")
|
||||||
|
}
|
||||||
|
|
||||||
|
val minRequiredForPossibleInExactMaxDuration =
|
||||||
|
recording.maxDuration / recording.intervalDuration
|
||||||
|
if (recording.forceExactMaxDuration && filePaths.size > minRequiredForPossibleInExactMaxDuration) {
|
||||||
|
stripConcatenatedFileToExactDuration(outputFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
return outputFile
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun cleanupFiles() {
|
||||||
|
filePaths.forEach {
|
||||||
|
runCatching {
|
||||||
|
it.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getFolder(context: Context) = File(context.filesDir, RECORDER_SUBFOLDER_NAME)
|
||||||
|
|
||||||
|
fun clearAllRecordings(context: Context) {
|
||||||
|
getFolder(context).deleteRecursively()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasRecordingsAvailable(context: Context) {
|
||||||
|
getFolder(context).listFiles()?.isNotEmpty() ?: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,7 @@ import android.media.MediaRecorder.OnErrorListener
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import java.lang.IllegalStateException
|
import java.lang.IllegalStateException
|
||||||
|
|
||||||
class AudioRecorderService: IntervalRecorderService() {
|
class AudioRecorderService : IntervalRecorderService() {
|
||||||
var amplitudesAmount = 1000
|
var amplitudesAmount = 1000
|
||||||
|
|
||||||
var recorder: MediaRecorder? = null
|
var recorder: MediaRecorder? = null
|
||||||
@ -13,7 +13,7 @@ class AudioRecorderService: IntervalRecorderService() {
|
|||||||
var onError: () -> Unit = {}
|
var onError: () -> Unit = {}
|
||||||
|
|
||||||
val filePath: String
|
val filePath: String
|
||||||
get() = "$folder/$counter.${settings!!.fileExtension}"
|
get() = "${outputFolder}/$counter.${settings!!.fileExtension}"
|
||||||
|
|
||||||
private fun createRecorder(): MediaRecorder {
|
private fun createRecorder(): MediaRecorder {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
@ -1,39 +1,37 @@
|
|||||||
package app.myzel394.alibi.services
|
package app.myzel394.alibi.services
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.media.MediaRecorder
|
import android.media.MediaRecorder
|
||||||
import app.myzel394.alibi.dataStore
|
import app.myzel394.alibi.dataStore
|
||||||
import app.myzel394.alibi.db.AudioRecorderSettings
|
import app.myzel394.alibi.db.AudioRecorderSettings
|
||||||
import app.myzel394.alibi.db.LastRecording
|
import app.myzel394.alibi.db.RecordingInformation
|
||||||
|
import app.myzel394.alibi.helpers.AudioRecorderExporter
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
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.Executors
|
||||||
import java.util.concurrent.ScheduledExecutorService
|
import java.util.concurrent.ScheduledExecutorService
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
abstract class IntervalRecorderService: ExtraRecorderInformationService() {
|
abstract class IntervalRecorderService : ExtraRecorderInformationService() {
|
||||||
private var job = SupervisorJob()
|
private var job = SupervisorJob()
|
||||||
private var scope = CoroutineScope(Dispatchers.IO + job)
|
private var scope = CoroutineScope(Dispatchers.IO + job)
|
||||||
|
|
||||||
protected var counter = 0
|
protected var counter = 0
|
||||||
private set
|
private set
|
||||||
protected lateinit var folder: File
|
|
||||||
var settings: Settings? = null
|
var settings: Settings? = null
|
||||||
protected set
|
protected set
|
||||||
|
|
||||||
private lateinit var cycleTimer: ScheduledExecutorService
|
private lateinit var cycleTimer: ScheduledExecutorService
|
||||||
|
|
||||||
fun createLastRecording(): LastRecording = LastRecording(
|
protected val outputFolder: File
|
||||||
folderPath = folder.absolutePath,
|
get() = AudioRecorderExporter.getFolder(this)
|
||||||
|
|
||||||
|
fun createLastRecording(): RecordingInformation = RecordingInformation(
|
||||||
|
folderPath = outputFolder.absolutePath,
|
||||||
recordingStart = recordingStart,
|
recordingStart = recordingStart,
|
||||||
maxDuration = settings!!.maxDuration,
|
maxDuration = settings!!.maxDuration,
|
||||||
fileExtension = settings!!.fileExtension,
|
fileExtension = settings!!.fileExtension,
|
||||||
@ -60,18 +58,10 @@ abstract class IntervalRecorderService: ExtraRecorderInformationService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRandomFileFolder(): String {
|
|
||||||
// uuid
|
|
||||||
val folder = UUID.randomUUID().toString()
|
|
||||||
|
|
||||||
return "${externalCacheDir!!.absolutePath}/$folder"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
super.start()
|
super.start()
|
||||||
|
|
||||||
folder = File(getRandomFileFolder())
|
outputFolder.mkdirs()
|
||||||
folder.mkdirs()
|
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
dataStore.data.collectLatest { preferenceSettings ->
|
dataStore.data.collectLatest { preferenceSettings ->
|
||||||
@ -104,7 +94,7 @@ abstract class IntervalRecorderService: ExtraRecorderInformationService() {
|
|||||||
val timeMultiplier = settings!!.maxDuration / settings!!.intervalDuration
|
val timeMultiplier = settings!!.maxDuration / settings!!.intervalDuration
|
||||||
val earliestCounter = counter - timeMultiplier
|
val earliestCounter = counter - timeMultiplier
|
||||||
|
|
||||||
folder.listFiles()?.forEach { file ->
|
outputFolder.listFiles()?.forEach { file ->
|
||||||
val fileCounter = file.nameWithoutExtension.toIntOrNull() ?: return
|
val fileCounter = file.nameWithoutExtension.toIntOrNull() ?: return
|
||||||
|
|
||||||
if (fileCounter < earliestCounter) {
|
if (fileCounter < earliestCounter) {
|
||||||
@ -123,7 +113,7 @@ abstract class IntervalRecorderService: ExtraRecorderInformationService() {
|
|||||||
val encoder: Int,
|
val encoder: Int,
|
||||||
) {
|
) {
|
||||||
val fileExtension: String
|
val fileExtension: String
|
||||||
get() = when(outputFormat) {
|
get() = when (outputFormat) {
|
||||||
MediaRecorder.OutputFormat.AAC_ADTS -> "aac"
|
MediaRecorder.OutputFormat.AAC_ADTS -> "aac"
|
||||||
MediaRecorder.OutputFormat.THREE_GPP -> "3gp"
|
MediaRecorder.OutputFormat.THREE_GPP -> "3gp"
|
||||||
MediaRecorder.OutputFormat.MPEG_4 -> "mp4"
|
MediaRecorder.OutputFormat.MPEG_4 -> "mp4"
|
||||||
|
@ -2,10 +2,12 @@ package app.myzel394.alibi.ui
|
|||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
val BIG_PRIMARY_BUTTON_SIZE = 64.dp
|
val BIG_PRIMARY_BUTTON_SIZE = 64.dp
|
||||||
val MAX_AMPLITUDE = 20000
|
val MAX_AMPLITUDE = 20000
|
||||||
val SUPPORTS_DARK_MODE_NATIVELY = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
val SUPPORTS_DARK_MODE_NATIVELY = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
||||||
|
val RECORDER_SUBFOLDER_NAME = ".recordings"
|
||||||
|
|
||||||
// You are not allowed to change the constants below.
|
// You are not allowed to change the constants below.
|
||||||
// If you do so, you will be blocked on GitHub.
|
// If you do so, you will be blocked on GitHub.
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package app.myzel394.alibi.ui
|
package app.myzel394.alibi.ui
|
||||||
|
|
||||||
import androidx.compose.animation.core.AnimationSpec
|
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
@ -13,10 +12,6 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
@ -24,7 +19,6 @@ import androidx.navigation.compose.NavHost
|
|||||||
import androidx.navigation.compose.composable
|
import androidx.navigation.compose.composable
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import app.myzel394.alibi.dataStore
|
import app.myzel394.alibi.dataStore
|
||||||
import app.myzel394.alibi.db.LastRecording
|
|
||||||
import app.myzel394.alibi.ui.enums.Screen
|
import app.myzel394.alibi.ui.enums.Screen
|
||||||
import app.myzel394.alibi.ui.models.AudioRecorderModel
|
import app.myzel394.alibi.ui.models.AudioRecorderModel
|
||||||
import app.myzel394.alibi.ui.screens.AboutScreen
|
import app.myzel394.alibi.ui.screens.AboutScreen
|
||||||
|
@ -42,6 +42,7 @@ import app.myzel394.alibi.NotificationHelper
|
|||||||
import app.myzel394.alibi.R
|
import app.myzel394.alibi.R
|
||||||
import app.myzel394.alibi.dataStore
|
import app.myzel394.alibi.dataStore
|
||||||
import app.myzel394.alibi.db.AppSettings
|
import app.myzel394.alibi.db.AppSettings
|
||||||
|
import app.myzel394.alibi.helpers.AudioRecorderExporter
|
||||||
import app.myzel394.alibi.services.RecorderNotificationHelper
|
import app.myzel394.alibi.services.RecorderNotificationHelper
|
||||||
import app.myzel394.alibi.ui.BIG_PRIMARY_BUTTON_SIZE
|
import app.myzel394.alibi.ui.BIG_PRIMARY_BUTTON_SIZE
|
||||||
import app.myzel394.alibi.ui.components.atoms.PermissionRequester
|
import app.myzel394.alibi.ui.components.atoms.PermissionRequester
|
||||||
@ -79,6 +80,9 @@ fun StartRecording(
|
|||||||
it
|
it
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AudioRecorderExporter.clearAllRecordings(context)
|
||||||
|
|
||||||
audioRecorder.startRecording(context)
|
audioRecorder.startRecording(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -144,7 +148,7 @@ fun StartRecording(
|
|||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
)
|
)
|
||||||
if (audioRecorder.lastRecording != null && audioRecorder.lastRecording!!.hasRecordingAvailable) {
|
if (audioRecorder.lastRecording != null && audioRecorder.lastRecording!!.hasRecordingsAvailable) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
@ -4,22 +4,17 @@ import android.content.ComponentName
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.media.MediaRecorder
|
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import app.myzel394.alibi.dataStore
|
import app.myzel394.alibi.db.RecordingInformation
|
||||||
import app.myzel394.alibi.db.LastRecording
|
|
||||||
import app.myzel394.alibi.enums.RecorderState
|
import app.myzel394.alibi.enums.RecorderState
|
||||||
import app.myzel394.alibi.services.AudioRecorderService
|
import app.myzel394.alibi.services.AudioRecorderService
|
||||||
import app.myzel394.alibi.services.RecorderNotificationHelper
|
import app.myzel394.alibi.services.RecorderNotificationHelper
|
||||||
import app.myzel394.alibi.services.RecorderService
|
import app.myzel394.alibi.services.RecorderService
|
||||||
import kotlinx.coroutines.flow.last
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
||||||
class AudioRecorderModel : ViewModel() {
|
class AudioRecorderModel : ViewModel() {
|
||||||
@ -44,7 +39,7 @@ class AudioRecorderModel : ViewModel() {
|
|||||||
var recorderService: AudioRecorderService? = null
|
var recorderService: AudioRecorderService? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var lastRecording: LastRecording? by mutableStateOf<LastRecording?>(null)
|
var lastRecording: RecordingInformation? by mutableStateOf<RecordingInformation?>(null)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var onRecordingSave: () -> Unit = {}
|
var onRecordingSave: () -> Unit = {}
|
||||||
|
@ -28,7 +28,6 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import app.myzel394.alibi.ui.components.AudioRecorder.molecules.RecordingStatus
|
import app.myzel394.alibi.ui.components.AudioRecorder.molecules.RecordingStatus
|
||||||
import app.myzel394.alibi.ui.components.AudioRecorder.molecules.StartRecording
|
import app.myzel394.alibi.ui.components.AudioRecorder.molecules.StartRecording
|
||||||
@ -37,8 +36,7 @@ import app.myzel394.alibi.ui.utils.rememberFileSaverDialog
|
|||||||
import app.myzel394.alibi.R
|
import app.myzel394.alibi.R
|
||||||
import app.myzel394.alibi.dataStore
|
import app.myzel394.alibi.dataStore
|
||||||
import app.myzel394.alibi.db.AppSettings
|
import app.myzel394.alibi.db.AppSettings
|
||||||
import app.myzel394.alibi.db.LastRecording
|
import app.myzel394.alibi.helpers.AudioRecorderExporter
|
||||||
import app.myzel394.alibi.services.RecorderNotificationHelper
|
|
||||||
import app.myzel394.alibi.ui.effects.rememberSettings
|
import app.myzel394.alibi.ui.effects.rememberSettings
|
||||||
import app.myzel394.alibi.ui.models.AudioRecorderModel
|
import app.myzel394.alibi.ui.models.AudioRecorderModel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
@ -67,7 +65,8 @@ fun AudioRecorder(
|
|||||||
delay(100)
|
delay(100)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val file = audioRecorder.lastRecording!!.concatenateFiles()
|
val file =
|
||||||
|
AudioRecorderExporter(audioRecorder.lastRecording!!).concatenateFiles()
|
||||||
|
|
||||||
saveFile(file, file.name)
|
saveFile(file, file.name)
|
||||||
} catch (error: Exception) {
|
} catch (error: Exception) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user