/** * Lifecycle aware View has a lifecycle aware component that starts/stops the camera **/ LifecycleEventObserver { _, event -> when (event) { Lifecycle.Event.ON_RESUME -> { // Requests to open the camera which eventually calls // cameraWrapper.openCamera() } Lifecycle.Event.ON_PAUSE -> { // Release the camera with cameraWrapper.release() } } } //When the camera opens, it eventually signals the CameraReady event which is sent only after the call //to bindUseCases() is successful, this is where recording starts. FrameProducer.Event.CameraReady -> { startRecording() } //================================================= /** * Camera Wrapper Component **/ // video events listener private val recordingEventsListener = Consumer { event -> when (event) { is VideoRecordEvent.Start -> { Log.i("VIDEO_MISNAP", "Video recording started: $event") } is VideoRecordEvent.Finalize -> { Log.i("VIDEO_MISNAP", "Video recording finalized: $event") if (event.hasError()) { LiveDataUtil.updateValue(_frameProducerEvents, FrameProducer.Event.VideoRecordingError) Log.i("VIDEO_MISNAP", "Video recording failed ${event.cause} => ${event.error}") } else { try { //Read the video val videoBytes = File(getVideoPath(weakContext.get()!!)).readBytes() Log.d("VIDEO_MISNAP", "Video recording size: ${videoBytes.size}") LiveDataUtil.updateValue(_videoRecordings, videoBytes) } catch (e: Exception) { Log.e("VIDEO_MISNAP", "Could not read the video file", e) LiveDataUtil.updateValue(_videoRecordings, null) } } //Remove the video from the temp file and the active recording weakContext.get()?.let { deleteVideo(it) } } else -> { //Log.w("VIDEO_MISNAP", "Unhandled video recording event: ${event.javaClass.simpleName}") //Pause, Resume, and non status events are not handled } } } //start video recording override fun startVideoRecording() { Log.w("VIDEO_MISNAP", "Starting video recording") stopVideoRecording() try { //Delete the video before starting a new recording weakContext.get()?.let { deleteVideo(it) } activeRecording = pendingRecording?.start(executor, recordingEventsListener) } catch (e: Exception) { activeRecording?.let { it.stop() activeRecording = null } Log.e("VIDEO_MISNAP", "Could not start the video recording", e) } } override fun stopVideoRecording() { activeRecording?.let { Log.w("VIDEO_MISNAP", "Stopping video recording") it.stop() activeRecording = null } ?: run { Log.w("VIDEO_MISNAP", "No active recording to stop") } } //Build the video usecase @JvmSynthetic internal fun buildVideoCaptureUseCase( internalSettings: CameraInternalSettings, context: Context ): VideoCapture { Log.d("VIDEO_MISNAP", "Building video capture use case") val recorder = Recorder.Builder() .setQualitySelector(getQualitySelector(cameraSettings.videoRecord.getVideoResolution())) .setTargetVideoEncodingBitRate(cameraSettings.videoRecord.getVideoBitrate()) .build() //NOTE: The target name is still an internal API, we should set the name to match the others // when it's available return VideoCapture.Builder(recorder) .setTargetRotation(internalSettings.rotation) .build().apply { val fileOutputOptions = FileOutputOptions.Builder( File(context.externalCacheDir, ".temp.mp4") ).build() pendingRecording = this.output.prepareRecording( context, fileOutputOptions ).asPersistentRecording() if (cameraSettings.videoRecord.shouldRecordAudio()) { if (ContextCompat.checkSelfPermission(context, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) { pendingRecording?.withAudioEnabled() } else { Log.w("VIDEO_MISNAP", "Audio permission not granted") } } }.also { videoCaptureUseCase = it } } //release override fun release() { Log.w("VIDEO_MISNAP", "Releasing camera") //Stop the video first to finish pending recordings before unbinding the use cases stopVideoRecording() //stopping the preview calls unbindAll() stopPreview() } //Opens the camera, at this point the camera was already selected and is ready to be bound private fun openCamera(vararg useCases: UseCase) { val lifecycleOwner = weakLifecycleOwner.get() if (processCameraProvider != null && cameraSelector != null && lifecycleOwner != null) { try { processCameraProvider!!.unbindAll() processCameraProvider!!.bindToLifecycle( lifecycleOwner, cameraSelector!!, *useCases ) } catch (e: Exception) { } } else { } }