fix: Close camera correctly

This commit is contained in:
Myzel394 2023-12-02 16:03:03 +01:00
parent 65be9a1e3e
commit 5f7c6a9140
No known key found for this signature in database
GPG Key ID: 79CC92F37B3E1A2B

View File

@ -3,10 +3,8 @@ package app.myzel394.alibi.services
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.util.Range import android.util.Range
import androidx.camera.core.Camera import androidx.camera.core.Camera
import androidx.camera.core.CameraInfo
import androidx.camera.core.CameraSelector import androidx.camera.core.CameraSelector
import androidx.camera.core.TorchState import androidx.camera.core.TorchState
import androidx.camera.core.impl.CameraConfig
import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.video.FileOutputOptions import androidx.camera.video.FileOutputOptions
import androidx.camera.video.Quality import androidx.camera.video.Quality
@ -18,15 +16,16 @@ import androidx.camera.video.VideoRecordEvent
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
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.helpers.BatchesFolder import app.myzel394.alibi.helpers.BatchesFolder
import app.myzel394.alibi.helpers.VideoBatchesFolder import app.myzel394.alibi.helpers.VideoBatchesFolder
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
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.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
const val CAMERA_CLOSE_TIMEOUT = 20000L const val CAMERA_CLOSE_TIMEOUT = 20000L
@ -45,7 +44,10 @@ class VideoRecorderService :
// Used to listen and check if the camera is available // Used to listen and check if the camera is available
private var _cameraAvailableListener = CompletableDeferred<Unit>() private var _cameraAvailableListener = CompletableDeferred<Unit>()
private var _cameraClosedListener = CompletableDeferred<Unit>() private var _videoFinalizerListener = CompletableDeferred<Unit>()
// Absolute last completer that can be awaited to ensure that the camera is closed
private var _cameraCloserListener = CompletableDeferred<Unit>()
private var selectedCamera: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA private var selectedCamera: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
@ -65,12 +67,16 @@ class VideoRecorderService :
stopActiveRecording() stopActiveRecording()
withTimeoutOrNull(CAMERA_CLOSE_TIMEOUT) {
// Camera can only be closed after the recording has been finalized // Camera can only be closed after the recording has been finalized
_cameraClosedListener.await() withTimeoutOrNull(CAMERA_CLOSE_TIMEOUT) {
_videoFinalizerListener.await()
} }
closeCamera() closeCamera()
withTimeoutOrNull(CAMERA_CLOSE_TIMEOUT) {
_cameraCloserListener.await()
}
} }
override fun pause() { override fun pause() {
@ -88,8 +94,8 @@ class VideoRecorderService :
val newRecording = prepareVideoRecording() val newRecording = prepareVideoRecording()
activeRecording = newRecording.start(ContextCompat.getMainExecutor(this)) { event -> activeRecording = newRecording.start(ContextCompat.getMainExecutor(this)) { event ->
if (event is VideoRecordEvent.Finalize) { if (event is VideoRecordEvent.Finalize && this@VideoRecorderService.state == RecorderState.IDLE) {
_cameraClosedListener.complete(Unit) _videoFinalizerListener.complete(Unit)
} }
} }
} }
@ -158,16 +164,20 @@ class VideoRecorderService :
// Used to close it finally, shouldn't be called when pausing / resuming. // Used to close it finally, shouldn't be called when pausing / resuming.
// This should only be called after recording has finished. // This should only be called after recording has finished.
private fun closeCamera() { private fun closeCamera() {
runCatching {
runOnMain { runOnMain {
runCatching {
cameraProvider?.unbindAll() cameraProvider?.unbindAll()
} }
} _cameraCloserListener.complete(Unit)
// Doesn't need to run on main thread, but
// if it runs outside `runOnMain`, `cameraProvider` is already null
// before it's unbound
cameraProvider = null cameraProvider = null
videoCapture = null videoCapture = null
camera = null camera = null
} }
}
// `resume` override not needed as `startNewCycle` is called by `IntervalRecorderService` // `resume` override not needed as `startNewCycle` is called by `IntervalRecorderService`