mirror of
https://github.com/Myzel394/Alibi.git
synced 2025-06-19 15:15:26 +02:00
feat: Add support for media folders to video recording
This commit is contained in:
parent
99085b2176
commit
7401454269
@ -3,8 +3,8 @@ package app.myzel394.alibi.helpers
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
|
import android.provider.MediaStore
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import app.myzel394.alibi.helpers.MediaConverter.Companion.concatenateAudioFiles
|
|
||||||
import app.myzel394.alibi.helpers.MediaConverter.Companion.concatenateVideoFiles
|
import app.myzel394.alibi.helpers.MediaConverter.Companion.concatenateVideoFiles
|
||||||
import com.arthenica.ffmpegkit.FFmpegKitConfig
|
import com.arthenica.ffmpegkit.FFmpegKitConfig
|
||||||
import java.io.FileDescriptor
|
import java.io.FileDescriptor
|
||||||
@ -23,6 +23,7 @@ class AudioBatchesFolder(
|
|||||||
) {
|
) {
|
||||||
override val concatenationFunction = ::concatenateVideoFiles
|
override val concatenationFunction = ::concatenateVideoFiles
|
||||||
override val ffmpegParameters = FFMPEG_PARAMETERS
|
override val ffmpegParameters = FFMPEG_PARAMETERS
|
||||||
|
override val mediaContentUri: Uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
||||||
|
|
||||||
private var customFileFileDescriptor: ParcelFileDescriptor? = null
|
private var customFileFileDescriptor: ParcelFileDescriptor? = null
|
||||||
|
|
||||||
@ -32,6 +33,7 @@ class AudioBatchesFolder(
|
|||||||
): String {
|
): String {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
BatchType.INTERNAL -> asInternalGetOutputFile(date, extension).absolutePath
|
BatchType.INTERNAL -> asInternalGetOutputFile(date, extension).absolutePath
|
||||||
|
|
||||||
BatchType.CUSTOM -> {
|
BatchType.CUSTOM -> {
|
||||||
val name = getName(date, extension)
|
val name = getName(date, extension)
|
||||||
|
|
||||||
@ -43,6 +45,10 @@ class AudioBatchesFolder(
|
|||||||
)!!).uri
|
)!!).uri
|
||||||
)!!
|
)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BatchType.MEDIA -> {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
package app.myzel394.alibi.helpers
|
package app.myzel394.alibi.helpers
|
||||||
|
|
||||||
|
import app.myzel394.alibi.ui.MEDIA_RECORDINGS_PREFIX
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.database.Cursor
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.provider.MediaStore.Video.Media
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import com.arthenica.ffmpegkit.FFmpegKitConfig
|
import com.arthenica.ffmpegkit.FFmpegKitConfig
|
||||||
import android.os.ParcelFileDescriptor
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import java.io.FileDescriptor
|
|
||||||
import kotlin.reflect.KFunction3
|
import kotlin.reflect.KFunction3
|
||||||
|
|
||||||
abstract class BatchesFolder(
|
abstract class BatchesFolder(
|
||||||
@ -21,15 +23,24 @@ abstract class BatchesFolder(
|
|||||||
) {
|
) {
|
||||||
abstract val concatenationFunction: KFunction3<Iterable<String>, String, String, CompletableDeferred<Unit>>
|
abstract val concatenationFunction: KFunction3<Iterable<String>, String, String, CompletableDeferred<Unit>>
|
||||||
abstract val ffmpegParameters: Array<String>
|
abstract val ffmpegParameters: Array<String>
|
||||||
|
abstract val mediaContentUri: Uri
|
||||||
|
|
||||||
|
val mediaPrefix
|
||||||
|
get() = MEDIA_RECORDINGS_PREFIX + subfolderName
|
||||||
|
|
||||||
fun initFolders() {
|
fun initFolders() {
|
||||||
when (type) {
|
when (type) {
|
||||||
BatchType.INTERNAL -> getInternalFolder().mkdirs()
|
BatchType.INTERNAL -> getInternalFolder().mkdirs()
|
||||||
|
|
||||||
BatchType.CUSTOM -> {
|
BatchType.CUSTOM -> {
|
||||||
if (customFolder!!.findFile(subfolderName) == null) {
|
if (customFolder!!.findFile(subfolderName) == null) {
|
||||||
customFolder!!.createDirectory(subfolderName)
|
customFolder!!.createDirectory(subfolderName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BatchType.MEDIA -> {
|
||||||
|
// Add support for < Android 10
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +52,52 @@ abstract class BatchesFolder(
|
|||||||
return customFolder!!.findFile(subfolderName)!!
|
return customFolder!!.findFile(subfolderName)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun queryMediaContent(
|
||||||
|
callback: (rawName: String, counter: Int, uri: Uri, cursor: Cursor) -> Any?,
|
||||||
|
) {
|
||||||
|
context.contentResolver.query(
|
||||||
|
mediaContentUri,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
)!!.use { cursor ->
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
val rawName = cursor.getColumnIndex(Media.DISPLAY_NAME).let { id ->
|
||||||
|
if (id == -1) "" else cursor.getString(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawName == "" || rawName == null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rawName.startsWith(mediaPrefix)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val counter =
|
||||||
|
rawName.substringAfter(mediaPrefix).substringBeforeLast(".").toIntOrNull()
|
||||||
|
?: continue
|
||||||
|
|
||||||
|
val id = cursor.getColumnIndex(Media._ID).let { id ->
|
||||||
|
if (id == -1) "" else cursor.getString(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id == "" || id == null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val uri = Uri.withAppendedPath(mediaContentUri, id)
|
||||||
|
|
||||||
|
val result = callback(rawName, counter, uri, cursor)
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getBatchesForFFmpeg(): List<String> {
|
fun getBatchesForFFmpeg(): List<String> {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
BatchType.INTERNAL ->
|
BatchType.INTERNAL ->
|
||||||
@ -64,6 +121,21 @@ abstract class BatchesFolder(
|
|||||||
it.uri,
|
it.uri,
|
||||||
)!!
|
)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BatchType.MEDIA -> {
|
||||||
|
val filePaths = mutableListOf<String>()
|
||||||
|
|
||||||
|
queryMediaContent { _, _, uri, _ ->
|
||||||
|
filePaths.add(
|
||||||
|
FFmpegKitConfig.getSafParameterForRead(
|
||||||
|
context,
|
||||||
|
uri,
|
||||||
|
)!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
filePaths
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,27 +153,36 @@ abstract class BatchesFolder(
|
|||||||
return File(getInternalFolder(), getName(date, extension))
|
return File(getInternalFolder(), getName(date, extension))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun asCustomGetOutputFile(
|
|
||||||
date: LocalDateTime,
|
|
||||||
extension: String,
|
|
||||||
): DocumentFile {
|
|
||||||
return getCustomDefinedFolder().createFile("audio/$extension", getName(date, extension))!!
|
|
||||||
}
|
|
||||||
|
|
||||||
fun checkIfOutputAlreadyExists(
|
fun checkIfOutputAlreadyExists(
|
||||||
date: LocalDateTime,
|
date: LocalDateTime,
|
||||||
extension: String
|
extension: String
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val name = date
|
val stem = date
|
||||||
.format(DateTimeFormatter.ISO_DATE_TIME)
|
.format(DateTimeFormatter.ISO_DATE_TIME)
|
||||||
.toString()
|
.toString()
|
||||||
.replace(":", "-")
|
.replace(":", "-")
|
||||||
.replace(".", "_")
|
.replace(".", "_")
|
||||||
|
val fileName = "$stem.$extension"
|
||||||
|
|
||||||
return when (type) {
|
return when (type) {
|
||||||
BatchType.INTERNAL -> File(getInternalFolder(), "$name.$extension").exists()
|
BatchType.INTERNAL -> File(getInternalFolder(), fileName).exists()
|
||||||
|
|
||||||
BatchType.CUSTOM ->
|
BatchType.CUSTOM ->
|
||||||
getCustomDefinedFolder().findFile("${name}.${extension}")?.exists() ?: false
|
getCustomDefinedFolder().findFile(fileName)?.exists() ?: false
|
||||||
|
|
||||||
|
BatchType.MEDIA -> {
|
||||||
|
var exists = false
|
||||||
|
|
||||||
|
queryMediaContent { rawName, _, _, _ ->
|
||||||
|
if (rawName == fileName) {
|
||||||
|
exists = true
|
||||||
|
return@queryMediaContent true
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exists
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,24 +235,48 @@ abstract class BatchesFolder(
|
|||||||
return when (type) {
|
return when (type) {
|
||||||
BatchType.INTERNAL -> "_'internal"
|
BatchType.INTERNAL -> "_'internal"
|
||||||
BatchType.CUSTOM -> customFolder!!.uri.toString()
|
BatchType.CUSTOM -> customFolder!!.uri.toString()
|
||||||
|
BatchType.MEDIA -> "_'media"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteRecordings() {
|
fun deleteRecordings() {
|
||||||
when (type) {
|
when (type) {
|
||||||
BatchType.INTERNAL -> getInternalFolder().deleteRecursively()
|
BatchType.INTERNAL -> getInternalFolder().deleteRecursively()
|
||||||
|
|
||||||
BatchType.CUSTOM -> customFolder?.findFile(subfolderName)?.delete()
|
BatchType.CUSTOM -> customFolder?.findFile(subfolderName)?.delete()
|
||||||
?: customFolder?.findFile(subfolderName)?.listFiles()?.forEach {
|
?: customFolder?.findFile(subfolderName)?.listFiles()?.forEach {
|
||||||
it.delete()
|
it.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BatchType.MEDIA -> {
|
||||||
|
queryMediaContent { _, _, uri, _ ->
|
||||||
|
context.contentResolver.delete(
|
||||||
|
uri,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasRecordingsAvailable(): Boolean {
|
fun hasRecordingsAvailable(): Boolean {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
BatchType.INTERNAL -> getInternalFolder().listFiles()?.isNotEmpty() ?: false
|
BatchType.INTERNAL -> getInternalFolder().listFiles()?.isNotEmpty() ?: false
|
||||||
|
|
||||||
BatchType.CUSTOM -> customFolder?.findFile(subfolderName)?.listFiles()?.isNotEmpty()
|
BatchType.CUSTOM -> customFolder?.findFile(subfolderName)?.listFiles()?.isNotEmpty()
|
||||||
?: false
|
?: false
|
||||||
|
|
||||||
|
BatchType.MEDIA -> {
|
||||||
|
var hasRecordings = false
|
||||||
|
|
||||||
|
queryMediaContent { _, _, _, _ ->
|
||||||
|
hasRecordings = true
|
||||||
|
return@queryMediaContent true
|
||||||
|
}
|
||||||
|
|
||||||
|
hasRecordings
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,6 +297,18 @@ abstract class BatchesFolder(
|
|||||||
it.delete()
|
it.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BatchType.MEDIA -> {
|
||||||
|
queryMediaContent { _, counter, uri, _ ->
|
||||||
|
if (counter < earliestCounter) {
|
||||||
|
context.contentResolver.delete(
|
||||||
|
uri,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,6 +316,8 @@ abstract class BatchesFolder(
|
|||||||
return when (type) {
|
return when (type) {
|
||||||
BatchType.INTERNAL -> true
|
BatchType.INTERNAL -> true
|
||||||
BatchType.CUSTOM -> getCustomDefinedFolder().canWrite() && getCustomDefinedFolder().canRead()
|
BatchType.CUSTOM -> getCustomDefinedFolder().canWrite() && getCustomDefinedFolder().canRead()
|
||||||
|
// Add support for < Android 10
|
||||||
|
BatchType.MEDIA -> true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,6 +328,7 @@ abstract class BatchesFolder(
|
|||||||
enum class BatchType {
|
enum class BatchType {
|
||||||
INTERNAL,
|
INTERNAL,
|
||||||
CUSTOM,
|
CUSTOM,
|
||||||
|
MEDIA,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
package app.myzel394.alibi.helpers
|
package app.myzel394.alibi.helpers
|
||||||
|
|
||||||
|
import android.content.ContentUris
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.Environment
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.ParcelFileDescriptor
|
||||||
|
import android.provider.MediaStore
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import app.myzel394.alibi.helpers.MediaConverter.Companion.concatenateVideoFiles
|
import app.myzel394.alibi.helpers.MediaConverter.Companion.concatenateVideoFiles
|
||||||
|
import app.myzel394.alibi.ui.RECORDER_MEDIA_SELECTED_VALUE
|
||||||
import com.arthenica.ffmpegkit.FFmpegKitConfig
|
import com.arthenica.ffmpegkit.FFmpegKitConfig
|
||||||
import com.arthenica.ffmpegkit.ReturnCode
|
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
class VideoBatchesFolder(
|
class VideoBatchesFolder(
|
||||||
override val context: Context,
|
override val context: Context,
|
||||||
override val type: BatchesFolder.BatchType,
|
override val type: BatchType,
|
||||||
override val customFolder: DocumentFile? = null,
|
override val customFolder: DocumentFile? = null,
|
||||||
override val subfolderName: String = ".video_recordings",
|
override val subfolderName: String = ".video_recordings",
|
||||||
) : BatchesFolder(
|
) : BatchesFolder(
|
||||||
@ -22,12 +25,14 @@ class VideoBatchesFolder(
|
|||||||
) {
|
) {
|
||||||
override val concatenationFunction = ::concatenateVideoFiles
|
override val concatenationFunction = ::concatenateVideoFiles
|
||||||
override val ffmpegParameters = FFMPEG_PARAMETERS
|
override val ffmpegParameters = FFMPEG_PARAMETERS
|
||||||
|
override val mediaContentUri: Uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
||||||
|
|
||||||
private var customParcelFileDescriptor: ParcelFileDescriptor? = null
|
private var customParcelFileDescriptor: ParcelFileDescriptor? = null
|
||||||
|
|
||||||
override fun getOutputFileForFFmpeg(date: LocalDateTime, extension: String): String {
|
override fun getOutputFileForFFmpeg(date: LocalDateTime, extension: String): String {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
BatchType.INTERNAL -> asInternalGetOutputFile(date, extension).absolutePath
|
BatchType.INTERNAL -> asInternalGetOutputFile(date, extension).absolutePath
|
||||||
|
|
||||||
BatchType.CUSTOM -> {
|
BatchType.CUSTOM -> {
|
||||||
val name = getName(date, extension)
|
val name = getName(date, extension)
|
||||||
|
|
||||||
@ -39,6 +44,72 @@ class VideoBatchesFolder(
|
|||||||
)!!).uri
|
)!!).uri
|
||||||
)!!
|
)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BatchType.MEDIA -> {
|
||||||
|
val name = getName(date, extension)
|
||||||
|
|
||||||
|
// Check if already exists
|
||||||
|
var uri: Uri? = null
|
||||||
|
context.contentResolver.query(
|
||||||
|
mediaContentUri,
|
||||||
|
null,
|
||||||
|
// TODO: Improve
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
)!!.use { cursor ->
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
val id = cursor.getColumnIndex(MediaStore.MediaColumns._ID)
|
||||||
|
|
||||||
|
if (id == -1) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val nameID = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)
|
||||||
|
|
||||||
|
if (nameID == -1) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val cursorName = cursor.getString(nameID)
|
||||||
|
|
||||||
|
if (cursorName != name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = ContentUris.withAppendedId(
|
||||||
|
mediaContentUri,
|
||||||
|
cursor.getLong(id)
|
||||||
|
)
|
||||||
|
return@use
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uri == null) {
|
||||||
|
uri = context.contentResolver.insert(
|
||||||
|
mediaContentUri,
|
||||||
|
android.content.ContentValues().apply {
|
||||||
|
put(
|
||||||
|
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
MediaStore.MediaColumns.MIME_TYPE,
|
||||||
|
"video/$extension"
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
MediaStore.Video.Media.RELATIVE_PATH,
|
||||||
|
Environment.DIRECTORY_DCIM + "/alibi/video_recordings"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)!!
|
||||||
|
}
|
||||||
|
|
||||||
|
FFmpegKitConfig.getSafParameterForWrite(
|
||||||
|
context,
|
||||||
|
uri
|
||||||
|
)!!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,8 +147,11 @@ class VideoBatchesFolder(
|
|||||||
fun viaCustomFolder(context: Context, folder: DocumentFile) =
|
fun viaCustomFolder(context: Context, folder: DocumentFile) =
|
||||||
VideoBatchesFolder(context, BatchType.CUSTOM, folder)
|
VideoBatchesFolder(context, BatchType.CUSTOM, folder)
|
||||||
|
|
||||||
|
fun viaMediaFolder(context: Context) = VideoBatchesFolder(context, BatchType.MEDIA)
|
||||||
|
|
||||||
fun importFromFolder(folder: String, context: Context) = when (folder) {
|
fun importFromFolder(folder: String, context: Context) = when (folder) {
|
||||||
"_'internal" -> viaInternalFolder(context)
|
"_'internal" -> viaInternalFolder(context)
|
||||||
|
RECORDER_MEDIA_SELECTED_VALUE -> viaMediaFolder(context)
|
||||||
else -> viaCustomFolder(
|
else -> viaCustomFolder(
|
||||||
context,
|
context,
|
||||||
DocumentFile.fromTreeUri(context, Uri.parse(folder))!!
|
DocumentFile.fromTreeUri(context, Uri.parse(folder))!!
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package app.myzel394.alibi.services
|
package app.myzel394.alibi.services
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Context.AUDIO_SERVICE
|
|
||||||
import android.content.pm.ServiceInfo
|
import android.content.pm.ServiceInfo
|
||||||
import android.media.AudioDeviceCallback
|
import android.media.AudioDeviceCallback
|
||||||
import android.media.AudioDeviceInfo
|
import android.media.AudioDeviceInfo
|
||||||
@ -12,9 +11,7 @@ import android.os.Build
|
|||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import androidx.core.app.ServiceCompat
|
import androidx.core.app.ServiceCompat
|
||||||
import androidx.core.content.ContextCompat.getSystemService
|
|
||||||
import app.myzel394.alibi.NotificationHelper
|
import app.myzel394.alibi.NotificationHelper
|
||||||
import app.myzel394.alibi.db.AppSettings
|
|
||||||
import app.myzel394.alibi.db.RecordingInformation
|
import app.myzel394.alibi.db.RecordingInformation
|
||||||
import app.myzel394.alibi.enums.RecorderState
|
import app.myzel394.alibi.enums.RecorderState
|
||||||
import app.myzel394.alibi.helpers.AudioBatchesFolder
|
import app.myzel394.alibi.helpers.AudioBatchesFolder
|
||||||
@ -198,6 +195,9 @@ class AudioRecorderService :
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Add media
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
setOutputFormat(audioSettings.getOutputFormat())
|
setOutputFormat(audioSettings.getOutputFormat())
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
package app.myzel394.alibi.services
|
package app.myzel394.alibi.services
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.ContentValues
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ServiceInfo
|
import android.content.pm.ServiceInfo
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.ParcelFileDescriptor
|
import android.os.Environment
|
||||||
|
import android.provider.MediaStore
|
||||||
import android.util.Range
|
import android.util.Range
|
||||||
import androidx.camera.core.Camera
|
import androidx.camera.core.Camera
|
||||||
import androidx.camera.core.CameraSelector
|
import androidx.camera.core.CameraSelector
|
||||||
import androidx.camera.core.TorchState
|
import androidx.camera.core.TorchState
|
||||||
import androidx.camera.core.processing.SurfaceProcessorNode.Out
|
|
||||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
import androidx.camera.video.FileDescriptorOutputOptions
|
import androidx.camera.video.FileDescriptorOutputOptions
|
||||||
import androidx.camera.video.FileOutputOptions
|
import androidx.camera.video.FileOutputOptions
|
||||||
import androidx.camera.video.MediaStoreOutputOptions
|
import androidx.camera.video.MediaStoreOutputOptions
|
||||||
import androidx.camera.video.OutputOptions
|
|
||||||
import androidx.camera.video.Quality
|
import androidx.camera.video.Quality
|
||||||
import androidx.camera.video.QualitySelector
|
import androidx.camera.video.QualitySelector
|
||||||
import androidx.camera.video.Recorder
|
import androidx.camera.video.Recorder
|
||||||
@ -36,7 +36,6 @@ import kotlinx.coroutines.SupervisorJob
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import java.nio.file.Files.createFile
|
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
class VideoRecorderService :
|
class VideoRecorderService :
|
||||||
@ -232,7 +231,6 @@ class VideoRecorderService :
|
|||||||
private fun prepareVideoRecording() =
|
private fun prepareVideoRecording() =
|
||||||
videoCapture!!.output
|
videoCapture!!.output
|
||||||
.let {
|
.let {
|
||||||
// TODO: Add hint
|
|
||||||
if (batchesFolder.type == BatchesFolder.BatchType.CUSTOM && VIDEO_RECORDER_SUPPORTS_CUSTOM_FOLDER) {
|
if (batchesFolder.type == BatchesFolder.BatchType.CUSTOM && VIDEO_RECORDER_SUPPORTS_CUSTOM_FOLDER) {
|
||||||
it.prepareRecording(
|
it.prepareRecording(
|
||||||
this,
|
this,
|
||||||
@ -243,6 +241,39 @@ class VideoRecorderService :
|
|||||||
)
|
)
|
||||||
).build()
|
).build()
|
||||||
)
|
)
|
||||||
|
} else if (batchesFolder.type == BatchesFolder.BatchType.MEDIA) {
|
||||||
|
it.prepareRecording(
|
||||||
|
this,
|
||||||
|
MediaStoreOutputOptions.Builder(
|
||||||
|
contentResolver,
|
||||||
|
batchesFolder.mediaContentUri,
|
||||||
|
).setContentValues(
|
||||||
|
ContentValues().apply {
|
||||||
|
val name =
|
||||||
|
"${batchesFolder.mediaPrefix}$counter.${settings.videoRecorderSettings.fileExtension}"
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
put(
|
||||||
|
MediaStore.Video.Media.IS_PENDING,
|
||||||
|
1
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
MediaStore.Video.Media.RELATIVE_PATH,
|
||||||
|
Environment.DIRECTORY_DCIM + "/alibi/video_recordings"
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
MediaStore.Video.Media.DISPLAY_NAME,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
put(
|
||||||
|
MediaStore.Video.Media.DISPLAY_NAME,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).build()
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
it.prepareRecording(
|
it.prepareRecording(
|
||||||
this,
|
this,
|
||||||
|
@ -104,6 +104,15 @@ fun RecorderEventsHandler(
|
|||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showSnackbar() {
|
||||||
|
scope.launch {
|
||||||
|
snackbarHostState.showSnackbar(
|
||||||
|
message = successMessage,
|
||||||
|
duration = SnackbarDuration.Short,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun showSnackbar(uri: Uri) {
|
fun showSnackbar(uri: Uri) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val result = snackbarHostState.showSnackbar(
|
val result = snackbarHostState.showSnackbar(
|
||||||
@ -186,6 +195,14 @@ fun RecorderEventsHandler(
|
|||||||
batchesFolder.deleteRecordings()
|
batchesFolder.deleteRecordings()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BatchesFolder.BatchType.MEDIA -> {
|
||||||
|
showSnackbar()
|
||||||
|
|
||||||
|
if (settings.deleteRecordingsImmediately) {
|
||||||
|
batchesFolder.deleteRecordings()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error: Exception) {
|
} catch (error: Exception) {
|
||||||
Log.getStackTraceString(error)
|
Log.getStackTraceString(error)
|
||||||
|
@ -13,10 +13,9 @@ import androidx.documentfile.provider.DocumentFile
|
|||||||
import app.myzel394.alibi.db.AppSettings
|
import app.myzel394.alibi.db.AppSettings
|
||||||
import app.myzel394.alibi.db.RecordingInformation
|
import app.myzel394.alibi.db.RecordingInformation
|
||||||
import app.myzel394.alibi.enums.RecorderState
|
import app.myzel394.alibi.enums.RecorderState
|
||||||
import app.myzel394.alibi.helpers.AudioBatchesFolder
|
|
||||||
import app.myzel394.alibi.helpers.BatchesFolder
|
|
||||||
import app.myzel394.alibi.helpers.VideoBatchesFolder
|
import app.myzel394.alibi.helpers.VideoBatchesFolder
|
||||||
import app.myzel394.alibi.services.VideoRecorderService
|
import app.myzel394.alibi.services.VideoRecorderService
|
||||||
|
import app.myzel394.alibi.ui.RECORDER_MEDIA_SELECTED_VALUE
|
||||||
import app.myzel394.alibi.ui.utils.CameraInfo
|
import app.myzel394.alibi.ui.utils.CameraInfo
|
||||||
import app.myzel394.alibi.ui.utils.PermissionHelper
|
import app.myzel394.alibi.ui.utils.PermissionHelper
|
||||||
|
|
||||||
@ -43,16 +42,17 @@ class VideoRecorderModel :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun startRecording(context: Context, settings: AppSettings) {
|
override fun startRecording(context: Context, settings: AppSettings) {
|
||||||
batchesFolder = if (settings.saveFolder == null)
|
batchesFolder = when (settings.saveFolder) {
|
||||||
VideoBatchesFolder.viaInternalFolder(context)
|
null -> VideoBatchesFolder.viaInternalFolder(context)
|
||||||
else
|
RECORDER_MEDIA_SELECTED_VALUE -> VideoBatchesFolder.viaMediaFolder(context)
|
||||||
VideoBatchesFolder.viaCustomFolder(
|
else -> VideoBatchesFolder.viaCustomFolder(
|
||||||
context,
|
context,
|
||||||
DocumentFile.fromTreeUri(
|
DocumentFile.fromTreeUri(
|
||||||
context,
|
context,
|
||||||
Uri.parse(settings.saveFolder)
|
Uri.parse(settings.saveFolder)
|
||||||
)!!
|
)!!
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
super.startRecording(context, settings)
|
super.startRecording(context, settings)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user