fix: Fix mainly Audio recording and some bugfixes for video recording

This commit is contained in:
Myzel394 2023-12-31 22:10:14 +01:00
parent fa72ee096e
commit f4334bf26b
No known key found for this signature in database
GPG Key ID: 79CC92F37B3E1A2B
7 changed files with 146 additions and 59 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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())

View File

@ -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 {

View File

@ -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

View File

@ -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)
}