mirror of
https://github.com/Myzel394/Alibi.git
synced 2025-06-18 23:05:26 +02:00
fix: Fix mainly Audio recording and some bugfixes for video recording
This commit is contained in:
parent
fa72ee096e
commit
f4334bf26b
@ -2,12 +2,16 @@ package app.myzel394.alibi.helpers
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.MediaStore
|
||||
import androidx.core.net.toFile
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import app.myzel394.alibi.helpers.MediaConverter.Companion.concatenateVideoFiles
|
||||
import app.myzel394.alibi.helpers.VideoBatchesFolder.Companion.MEDIA_SUBFOLDER
|
||||
import app.myzel394.alibi.helpers.MediaConverter.Companion.concatenateAudioFiles
|
||||
import app.myzel394.alibi.ui.MEDIA_SUBFOLDER_NAME
|
||||
import app.myzel394.alibi.ui.RECORDER_INTERNAL_SELECTED_VALUE
|
||||
import app.myzel394.alibi.ui.RECORDER_MEDIA_SELECTED_VALUE
|
||||
import com.arthenica.ffmpegkit.FFmpegKitConfig
|
||||
import java.io.File
|
||||
import java.io.FileDescriptor
|
||||
@ -24,15 +28,17 @@ class AudioBatchesFolder(
|
||||
customFolder,
|
||||
subfolderName,
|
||||
) {
|
||||
override val concatenationFunction = ::concatenateVideoFiles
|
||||
override val concatenationFunction = ::concatenateAudioFiles
|
||||
override val ffmpegParameters = FFMPEG_PARAMETERS
|
||||
override val scopedMediaContentUri: Uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
||||
override val legacyMediaFolder = File(
|
||||
scopedMediaContentUri.toFile(),
|
||||
MEDIA_SUBFOLDER + "/" + subfolderName
|
||||
// TODO: Add support for `DIRECTORY_RECORDINGS`
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
|
||||
MEDIA_RECORDINGS_SUBFOLDER,
|
||||
)
|
||||
|
||||
private var customFileFileDescriptor: ParcelFileDescriptor? = null
|
||||
private var mediaFileFileDescriptor: ParcelFileDescriptor? = null
|
||||
|
||||
override fun getOutputFileForFFmpeg(
|
||||
date: LocalDateTime,
|
||||
@ -54,7 +60,28 @@ class AudioBatchesFolder(
|
||||
}
|
||||
|
||||
BatchType.MEDIA -> {
|
||||
return ""
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val mediaUri = getOrCreateMediaFile(
|
||||
name = getName(date, extension),
|
||||
mimeType = "audio/$extension",
|
||||
relativePath = Environment.DIRECTORY_DCIM + "/" + MEDIA_SUBFOLDER_NAME,
|
||||
)
|
||||
|
||||
return FFmpegKitConfig.getSafParameterForWrite(
|
||||
context,
|
||||
mediaUri
|
||||
)!!
|
||||
} else {
|
||||
val path = arrayOf(
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
|
||||
MEDIA_SUBFOLDER_NAME,
|
||||
getName(date, extension)
|
||||
).joinToString("/")
|
||||
return File(path)
|
||||
.apply {
|
||||
createNewFile()
|
||||
}.absolutePath
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,6 +90,9 @@ class AudioBatchesFolder(
|
||||
runCatching {
|
||||
customFileFileDescriptor?.close()
|
||||
}
|
||||
runCatching {
|
||||
mediaFileFileDescriptor?.close()
|
||||
}
|
||||
}
|
||||
|
||||
fun asCustomGetFileDescriptor(
|
||||
@ -81,17 +111,44 @@ class AudioBatchesFolder(
|
||||
return customFileFileDescriptor!!.fileDescriptor
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
fun asMediaGetScopedStorageFileDescriptor(
|
||||
name: String,
|
||||
mimeType: String
|
||||
): FileDescriptor {
|
||||
runCatching {
|
||||
mediaFileFileDescriptor?.close()
|
||||
}
|
||||
|
||||
val mediaUri = getOrCreateMediaFile(
|
||||
name = name,
|
||||
mimeType = mimeType,
|
||||
relativePath = SCOPED_STORAGE_RELATIVE_PATH,
|
||||
)
|
||||
|
||||
mediaFileFileDescriptor = context.contentResolver.openFileDescriptor(mediaUri, "w")!!
|
||||
|
||||
return mediaFileFileDescriptor!!.fileDescriptor
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun viaInternalFolder(context: Context) = AudioBatchesFolder(context, BatchType.INTERNAL)
|
||||
|
||||
fun viaCustomFolder(context: Context, folder: DocumentFile) =
|
||||
AudioBatchesFolder(context, BatchType.CUSTOM, folder)
|
||||
|
||||
fun viaMediaFolder(context: Context) = AudioBatchesFolder(context, BatchType.MEDIA)
|
||||
|
||||
fun importFromFolder(folder: String, context: Context) = when (folder) {
|
||||
"_'internal" -> viaInternalFolder(context)
|
||||
RECORDER_INTERNAL_SELECTED_VALUE -> viaInternalFolder(context)
|
||||
RECORDER_MEDIA_SELECTED_VALUE -> viaMediaFolder(context)
|
||||
else -> viaCustomFolder(context, DocumentFile.fromTreeUri(context, Uri.parse(folder))!!)
|
||||
}
|
||||
|
||||
val MEDIA_RECORDINGS_SUBFOLDER = MEDIA_SUBFOLDER_NAME + "/audio_recordings"
|
||||
val SCOPED_STORAGE_RELATIVE_PATH =
|
||||
Environment.DIRECTORY_DCIM + "/" + MEDIA_RECORDINGS_SUBFOLDER
|
||||
|
||||
// Parameters to be passed in descending order
|
||||
// Those parameters first try to concatenate without re-encoding
|
||||
// if that fails, it'll try several fallback methods
|
||||
|
@ -170,6 +170,14 @@ abstract class BatchesFolder(
|
||||
return File(getInternalFolder(), getName(date, extension))
|
||||
}
|
||||
|
||||
fun asMediaGetLegacyFile(name: String): File = File(
|
||||
legacyMediaFolder,
|
||||
name
|
||||
).apply {
|
||||
createNewFile()
|
||||
}
|
||||
|
||||
|
||||
fun checkIfOutputAlreadyExists(
|
||||
date: LocalDateTime,
|
||||
extension: String
|
||||
|
@ -1,21 +1,20 @@
|
||||
package app.myzel394.alibi.helpers
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.MediaStore
|
||||
import androidx.core.net.toFile
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import app.myzel394.alibi.helpers.MediaConverter.Companion.concatenateVideoFiles
|
||||
import app.myzel394.alibi.ui.MEDIA_SUBFOLDER_NAME
|
||||
import app.myzel394.alibi.ui.RECORDER_INTERNAL_SELECTED_VALUE
|
||||
import app.myzel394.alibi.ui.RECORDER_MEDIA_SELECTED_VALUE
|
||||
import com.arthenica.ffmpegkit.FFmpegKitConfig
|
||||
import java.io.File
|
||||
import java.nio.file.Paths
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.io.path.Path
|
||||
|
||||
class VideoBatchesFolder(
|
||||
override val context: Context,
|
||||
@ -28,6 +27,7 @@ class VideoBatchesFolder(
|
||||
customFolder,
|
||||
subfolderName,
|
||||
) {
|
||||
// TODO: Sort batches!
|
||||
override val concatenationFunction = ::concatenateVideoFiles
|
||||
override val ffmpegParameters = FFMPEG_PARAMETERS
|
||||
override val scopedMediaContentUri: Uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
||||
@ -59,7 +59,7 @@ class VideoBatchesFolder(
|
||||
val mediaUri = getOrCreateMediaFile(
|
||||
name = getName(date, extension),
|
||||
mimeType = "video/$extension",
|
||||
relativePath = Environment.DIRECTORY_DCIM + MEDIA_SUBFOLDER,
|
||||
relativePath = Environment.DIRECTORY_DCIM + "/" + MEDIA_SUBFOLDER_NAME,
|
||||
)
|
||||
|
||||
return FFmpegKitConfig.getSafParameterForWrite(
|
||||
@ -67,12 +67,12 @@ class VideoBatchesFolder(
|
||||
mediaUri
|
||||
)!!
|
||||
} else {
|
||||
return Paths.get(
|
||||
MediaStore.Video.Media.EXTERNAL_CONTENT_URI.path,
|
||||
Environment.DIRECTORY_DCIM,
|
||||
MEDIA_SUBFOLDER,
|
||||
val path = arrayOf(
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM),
|
||||
MEDIA_SUBFOLDER_NAME,
|
||||
getName(date, extension)
|
||||
).toFile()
|
||||
).joinToString("/")
|
||||
return File(path)
|
||||
.apply {
|
||||
createNewFile()
|
||||
}.absolutePath
|
||||
@ -109,6 +109,24 @@ class VideoBatchesFolder(
|
||||
}
|
||||
}
|
||||
|
||||
fun asMediaGetScopedStorageContentValues(name: String) = ContentValues().apply {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
put(
|
||||
MediaStore.Video.Media.IS_PENDING,
|
||||
1
|
||||
)
|
||||
put(
|
||||
MediaStore.Video.Media.RELATIVE_PATH,
|
||||
SCOPED_STORAGE_RELATIVE_PATH,
|
||||
)
|
||||
}
|
||||
|
||||
put(
|
||||
MediaStore.Video.Media.DISPLAY_NAME,
|
||||
name
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun viaInternalFolder(context: Context) = VideoBatchesFolder(context, BatchType.INTERNAL)
|
||||
|
||||
@ -126,9 +144,9 @@ class VideoBatchesFolder(
|
||||
)
|
||||
}
|
||||
|
||||
val MEDIA_SUBFOLDER = "/alibi"
|
||||
val MEDIA_RECORDINGS_SUBFOLDER = MEDIA_SUBFOLDER + "/video_recordings"
|
||||
val SCOPED_STORAGE_RELATIVE_PATH = Environment.DIRECTORY_DCIM + MEDIA_RECORDINGS_SUBFOLDER
|
||||
val MEDIA_RECORDINGS_SUBFOLDER = MEDIA_SUBFOLDER_NAME + "/video_recordings"
|
||||
val SCOPED_STORAGE_RELATIVE_PATH =
|
||||
Environment.DIRECTORY_DCIM + "/" + MEDIA_RECORDINGS_SUBFOLDER
|
||||
|
||||
// Parameters to be passed in descending order
|
||||
// Those parameters first try to concatenate without re-encoding
|
||||
|
@ -16,6 +16,7 @@ import app.myzel394.alibi.db.RecordingInformation
|
||||
import app.myzel394.alibi.enums.RecorderState
|
||||
import app.myzel394.alibi.helpers.AudioBatchesFolder
|
||||
import app.myzel394.alibi.helpers.BatchesFolder
|
||||
import app.myzel394.alibi.ui.SUPPORTS_SCOPED_STORAGE
|
||||
import app.myzel394.alibi.ui.utils.MicrophoneInfo
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
@ -160,6 +161,9 @@ class AudioRecorderService :
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNameForMediaFile() =
|
||||
"${batchesFolder.mediaPrefix}$counter.${settings.videoRecorderSettings.fileExtension}"
|
||||
|
||||
// ==== Actual recording related ====
|
||||
private fun createRecorder(): MediaRecorder {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
@ -196,8 +200,22 @@ class AudioRecorderService :
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Add media
|
||||
else -> {}
|
||||
BatchesFolder.BatchType.MEDIA -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
setOutputFile(
|
||||
batchesFolder.asMediaGetScopedStorageFileDescriptor(
|
||||
getNameForMediaFile(),
|
||||
"audio/${audioSettings.fileExtension}"
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val name = getNameForMediaFile()
|
||||
val file = batchesFolder.asMediaGetLegacyFile(name)
|
||||
|
||||
// TODO: Ask permission on settings screen
|
||||
setOutputFile(file.absolutePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setOutputFormat(audioSettings.getOutputFormat())
|
||||
|
@ -1,11 +1,9 @@
|
||||
package app.myzel394.alibi.services
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ContentValues
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.os.Build
|
||||
import android.provider.MediaStore
|
||||
import android.util.Range
|
||||
import androidx.camera.core.Camera
|
||||
import androidx.camera.core.CameraSelector
|
||||
@ -35,7 +33,6 @@ import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import java.io.File
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class VideoRecorderService :
|
||||
@ -246,45 +243,30 @@ class VideoRecorderService :
|
||||
)
|
||||
} else if (batchesFolder.type == BatchesFolder.BatchType.MEDIA) {
|
||||
if (SUPPORTS_SCOPED_STORAGE) {
|
||||
val name = getNameForMediaFile()
|
||||
|
||||
it.prepareRecording(
|
||||
this,
|
||||
MediaStoreOutputOptions.Builder(
|
||||
contentResolver,
|
||||
batchesFolder.scopedMediaContentUri,
|
||||
).setContentValues(
|
||||
ContentValues().apply {
|
||||
val name = getNameForMediaFile()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
put(
|
||||
MediaStore.Video.Media.IS_PENDING,
|
||||
1
|
||||
)
|
||||
put(
|
||||
MediaStore.Video.Media.RELATIVE_PATH,
|
||||
VideoBatchesFolder.SCOPED_STORAGE_RELATIVE_PATH,
|
||||
)
|
||||
}
|
||||
|
||||
put(
|
||||
MediaStore.Video.Media.DISPLAY_NAME,
|
||||
MediaStoreOutputOptions
|
||||
.Builder(
|
||||
contentResolver,
|
||||
batchesFolder.scopedMediaContentUri,
|
||||
)
|
||||
.setContentValues(
|
||||
batchesFolder.asMediaGetScopedStorageContentValues(
|
||||
name
|
||||
)
|
||||
}
|
||||
).build()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
} else {
|
||||
val name = getNameForMediaFile()
|
||||
val file = File(
|
||||
batchesFolder.legacyMediaFolder,
|
||||
name
|
||||
).apply {
|
||||
createNewFile()
|
||||
}
|
||||
|
||||
it.prepareRecording(
|
||||
this,
|
||||
FileOutputOptions.Builder(file).build()
|
||||
FileOutputOptions
|
||||
.Builder(batchesFolder.asMediaGetLegacyFile(name))
|
||||
.build()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
@ -9,7 +9,8 @@ val BIG_PRIMARY_BUTTON_SIZE = 64.dp
|
||||
val SHEET_BOTTOM_OFFSET = 56.dp
|
||||
val MAX_AMPLITUDE = 20000
|
||||
val SUPPORTS_DARK_MODE_NATIVELY = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
||||
val RECORDER_SUBFOLDER_NAME = ".recordings"
|
||||
|
||||
val MEDIA_SUBFOLDER_NAME = "alibi"
|
||||
|
||||
// TODO: Fix!
|
||||
val SUPPORTS_SCOPED_STORAGE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||||
|
@ -10,7 +10,9 @@ import app.myzel394.alibi.db.AppSettings
|
||||
import app.myzel394.alibi.db.RecordingInformation
|
||||
import app.myzel394.alibi.enums.RecorderState
|
||||
import app.myzel394.alibi.helpers.AudioBatchesFolder
|
||||
import app.myzel394.alibi.helpers.VideoBatchesFolder
|
||||
import app.myzel394.alibi.services.AudioRecorderService
|
||||
import app.myzel394.alibi.ui.RECORDER_MEDIA_SELECTED_VALUE
|
||||
import app.myzel394.alibi.ui.utils.MicrophoneInfo
|
||||
|
||||
class AudioRecorderModel :
|
||||
@ -63,16 +65,17 @@ class AudioRecorderModel :
|
||||
}
|
||||
|
||||
override fun startRecording(context: Context, settings: AppSettings) {
|
||||
batchesFolder = if (settings.saveFolder == null)
|
||||
AudioBatchesFolder.viaInternalFolder(context)
|
||||
else
|
||||
AudioBatchesFolder.viaCustomFolder(
|
||||
batchesFolder = when (settings.saveFolder) {
|
||||
null -> AudioBatchesFolder.viaInternalFolder(context)
|
||||
RECORDER_MEDIA_SELECTED_VALUE -> AudioBatchesFolder.viaMediaFolder(context)
|
||||
else -> AudioBatchesFolder.viaCustomFolder(
|
||||
context,
|
||||
DocumentFile.fromTreeUri(
|
||||
context,
|
||||
Uri.parse(settings.saveFolder)
|
||||
)!!
|
||||
)
|
||||
}
|
||||
|
||||
super.startRecording(context, settings)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user