fix: Catch insufficient-storage
errors (#2422)
* fix: Catch `insufficient-storage` errors * feat: Implement `insufficient-storage` error for Android * fix: Catch insufficient storage error also on takePhoto android
This commit is contained in:
@@ -5,8 +5,8 @@ import android.annotation.SuppressLint
|
||||
import android.content.pm.PackageManager
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.facebook.react.bridge.*
|
||||
import com.mrousavy.camera.core.CameraError
|
||||
import com.mrousavy.camera.core.MicrophonePermissionError
|
||||
import com.mrousavy.camera.core.RecorderError
|
||||
import com.mrousavy.camera.core.RecordingSession
|
||||
import com.mrousavy.camera.core.code
|
||||
import com.mrousavy.camera.types.RecordVideoOptions
|
||||
@@ -29,7 +29,7 @@ suspend fun CameraView.startRecording(options: RecordVideoOptions, onRecordCallb
|
||||
map.putInt("height", video.size.height)
|
||||
onRecordCallback(map, null)
|
||||
}
|
||||
val onError = { error: RecorderError ->
|
||||
val onError = { error: CameraError ->
|
||||
val errorMap = makeErrorMap(error.code, error.message)
|
||||
onRecordCallback(null, errorMap)
|
||||
}
|
||||
|
@@ -12,11 +12,13 @@ import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
import com.facebook.react.bridge.WritableMap
|
||||
import com.mrousavy.camera.core.CameraSession
|
||||
import com.mrousavy.camera.core.InsufficientStorageError
|
||||
import com.mrousavy.camera.types.Flash
|
||||
import com.mrousavy.camera.types.QualityPrioritization
|
||||
import com.mrousavy.camera.utils.*
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
private const val TAG = "CameraView.takePhoto"
|
||||
@@ -49,7 +51,15 @@ suspend fun CameraView.takePhoto(optionsMap: ReadableMap): WritableMap {
|
||||
|
||||
val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId!!)
|
||||
|
||||
val path = savePhotoToFile(context, cameraCharacteristics, photo)
|
||||
val path = try {
|
||||
savePhotoToFile(context, cameraCharacteristics, photo)
|
||||
} catch (e: IOException) {
|
||||
if (e.message?.contains("no space left", true) == true) {
|
||||
throw InsufficientStorageError()
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(TAG, "Successfully saved photo to file! $path")
|
||||
|
||||
@@ -93,7 +103,7 @@ private suspend fun savePhotoToFile(
|
||||
when (photo.format) {
|
||||
// When the format is JPEG or DEPTH JPEG we can simply save the bytes as-is
|
||||
ImageFormat.JPEG, ImageFormat.DEPTH_JPEG -> {
|
||||
val file = createFile(context, ".jpg")
|
||||
val file = FileUtils.createTempFile(context, ".jpg")
|
||||
writePhotoToFile(photo, file)
|
||||
return@withContext file.absolutePath
|
||||
}
|
||||
@@ -101,7 +111,7 @@ private suspend fun savePhotoToFile(
|
||||
// When the format is RAW we use the DngCreator utility library
|
||||
ImageFormat.RAW_SENSOR -> {
|
||||
val dngCreator = DngCreator(cameraCharacteristics, photo.metadata)
|
||||
val file = createFile(context, ".dng")
|
||||
val file = FileUtils.createTempFile(context, ".dng")
|
||||
FileOutputStream(file).use { stream ->
|
||||
// TODO: Make sure orientation is loaded properly here?
|
||||
dngCreator.writeImage(stream, photo.image)
|
||||
@@ -114,8 +124,3 @@ private suspend fun savePhotoToFile(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createFile(context: Context, extension: String): File =
|
||||
File.createTempFile("mrousavy", extension, context.cacheDir).apply {
|
||||
deleteOnExit()
|
||||
}
|
||||
|
@@ -106,6 +106,7 @@ class RecorderError(name: String, extra: Int) :
|
||||
CameraError("capture", "recorder-error", "An error occured while recording a video! $name $extra")
|
||||
class NoRecordingInProgressError :
|
||||
CameraError("capture", "no-recording-in-progress", "There was no active video recording in progress! Did you call stopRecording() twice?")
|
||||
class InsufficientStorageError : CameraError("capture", "insufficient-storage", "There is not enough storage space available.")
|
||||
class RecordingInProgressError :
|
||||
CameraError(
|
||||
"capture",
|
||||
|
@@ -623,7 +623,7 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
|
||||
enableAudio: Boolean,
|
||||
options: RecordVideoOptions,
|
||||
callback: (video: RecordingSession.Video) -> Unit,
|
||||
onError: (error: RecorderError) -> Unit
|
||||
onError: (error: CameraError) -> Unit
|
||||
) {
|
||||
mutex.withLock {
|
||||
if (recording != null) throw RecordingInProgressError()
|
||||
|
@@ -7,9 +7,11 @@ import android.os.Build
|
||||
import android.util.Log
|
||||
import android.util.Size
|
||||
import android.view.Surface
|
||||
import com.facebook.common.statfs.StatFsHelper
|
||||
import com.mrousavy.camera.extensions.getRecommendedBitRate
|
||||
import com.mrousavy.camera.types.Orientation
|
||||
import com.mrousavy.camera.types.RecordVideoOptions
|
||||
import com.mrousavy.camera.utils.FileUtils
|
||||
import java.io.File
|
||||
|
||||
class RecordingSession(
|
||||
@@ -22,7 +24,7 @@ class RecordingSession(
|
||||
private val orientation: Orientation,
|
||||
private val options: RecordVideoOptions,
|
||||
private val callback: (video: Video) -> Unit,
|
||||
private val onError: (error: RecorderError) -> Unit
|
||||
private val onError: (error: CameraError) -> Unit
|
||||
) {
|
||||
companion object {
|
||||
private const val TAG = "RecordingSession"
|
||||
@@ -42,7 +44,7 @@ class RecordingSession(
|
||||
|
||||
// TODO: Implement HDR
|
||||
init {
|
||||
outputFile = File.createTempFile("mrousavy", options.fileType.toExtension(), context.cacheDir)
|
||||
outputFile = FileUtils.createTempFile(context, options.fileType.toExtension())
|
||||
|
||||
Log.i(TAG, "Creating RecordingSession for ${outputFile.absolutePath}")
|
||||
|
||||
@@ -52,9 +54,11 @@ class RecordingSession(
|
||||
recorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)
|
||||
|
||||
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
|
||||
|
||||
recorder.setOutputFile(outputFile.absolutePath)
|
||||
recorder.setVideoEncodingBitRate(bitRate)
|
||||
recorder.setVideoSize(size.height, size.width)
|
||||
recorder.setMaxFileSize(getMaxFileSize())
|
||||
if (fps != null) recorder.setVideoFrameRate(fps)
|
||||
|
||||
Log.i(TAG, "Using ${options.videoCodec} Video Codec at ${bitRate / 1_000_000.0} Mbps..")
|
||||
@@ -81,6 +85,9 @@ class RecordingSession(
|
||||
}
|
||||
recorder.setOnInfoListener { _, what, extra ->
|
||||
Log.i(TAG, "MediaRecorder Info: $what ($extra)")
|
||||
if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
|
||||
onError(InsufficientStorageError())
|
||||
}
|
||||
}
|
||||
|
||||
Log.i(TAG, "Created $this!")
|
||||
@@ -142,6 +149,13 @@ class RecordingSession(
|
||||
return bitRate
|
||||
}
|
||||
|
||||
private fun getMaxFileSize(): Long {
|
||||
val statFs = StatFsHelper.getInstance()
|
||||
val availableStorage = statFs.getAvailableStorageSpace(StatFsHelper.StorageType.INTERNAL)
|
||||
Log.i(TAG, "Maximum available storage space: ${availableStorage / 1_000} kB")
|
||||
return availableStorage
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val audio = if (enableAudio) "with audio" else "without audio"
|
||||
return "${size.width} x ${size.height} @ $fps FPS ${options.videoCodec} ${options.fileType} " +
|
||||
|
@@ -0,0 +1,13 @@
|
||||
package com.mrousavy.camera.utils
|
||||
|
||||
import android.content.Context
|
||||
import java.io.File
|
||||
|
||||
class FileUtils {
|
||||
companion object {
|
||||
fun createTempFile(context: Context, extension: String): File =
|
||||
File.createTempFile("mrousavy", extension, context.cacheDir).also {
|
||||
it.deleteOnExit()
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user