@@ -18,13 +18,15 @@ package com.example.android.cameraxextensions.viewmodel
1818
1919import android.app.Application
2020import android.graphics.Bitmap
21+ import android.util.Log
2122import androidx.camera.core.AspectRatio
2223import androidx.camera.core.Camera
2324import androidx.camera.core.CameraSelector
2425import androidx.camera.core.CameraSelector.LensFacing
2526import androidx.camera.core.FocusMeteringAction
2627import androidx.camera.core.ImageCapture
2728import androidx.camera.core.ImageCaptureException
29+ import androidx.camera.core.ImageCaptureLatencyEstimate
2830import androidx.camera.core.MeteringPoint
2931import androidx.camera.core.Preview
3032import androidx.camera.core.UseCaseGroup
@@ -41,11 +43,16 @@ import com.example.android.cameraxextensions.model.CameraUiState
4143import com.example.android.cameraxextensions.model.CaptureState
4244import com.example.android.cameraxextensions.repository.ImageCaptureRepository
4345import kotlinx.coroutines.Dispatchers
46+ import kotlinx.coroutines.Job
4447import kotlinx.coroutines.asExecutor
48+ import kotlinx.coroutines.delay
4549import kotlinx.coroutines.flow.Flow
4650import kotlinx.coroutines.flow.MutableStateFlow
4751import kotlinx.coroutines.guava.await
52+ import kotlinx.coroutines.isActive
4853import kotlinx.coroutines.launch
54+ import androidx.lifecycle.asFlow
55+ import kotlinx.coroutines.CoroutineScope
4956
5057/* *
5158 * View model for camera extensions. This manages all the operations on the camera.
@@ -61,6 +68,11 @@ class CameraExtensionsViewModel(
6168 private val application : Application ,
6269 private val imageCaptureRepository : ImageCaptureRepository
6370) : ViewModel() {
71+ private companion object {
72+ const val TAG = " CameraExtensionsViewModel"
73+ const val REALTIME_LATENCY_UPDATE_INTERVAL_MILLIS = 1000L
74+ }
75+
6476 private lateinit var cameraProvider: ProcessCameraProvider
6577 private lateinit var extensionsManager: ExtensionsManager
6678
@@ -69,6 +81,7 @@ class CameraExtensionsViewModel(
6981 private var imageCapture = ImageCapture .Builder ()
7082 .setTargetAspectRatio(AspectRatio .RATIO_16_9 )
7183 .build()
84+ private var realtimeLatencyEstimateJob: Job ? = null
7285
7386 private val preview = Preview .Builder ()
7487 .setTargetAspectRatio(AspectRatio .RATIO_16_9 )
@@ -127,6 +140,7 @@ class CameraExtensionsViewModel(
127140 availableExtensions = listOf (ExtensionMode .NONE ) + availableExtensions,
128141 availableCameraLens = availableCameraLens,
129142 extensionMode = if (availableExtensions.isEmpty()) ExtensionMode .NONE else currentCameraUiState.extensionMode,
143+ realtimeCaptureLatencyEstimate = ImageCaptureLatencyEstimate .UNDEFINED_IMAGE_CAPTURE_LATENCY ,
130144 )
131145 _cameraUiState .emit(newCameraUiState)
132146 }
@@ -141,6 +155,8 @@ class CameraExtensionsViewModel(
141155 lifecycleOwner : LifecycleOwner ,
142156 previewView : PreviewView
143157 ) {
158+ realtimeLatencyEstimateJob?.cancel()
159+
144160 val currentCameraUiState = _cameraUiState .value
145161 val cameraSelector = if (currentCameraUiState.extensionMode == ExtensionMode .NONE ) {
146162 cameraLensToSelector(currentCameraUiState.cameraLens)
@@ -175,30 +191,75 @@ class CameraExtensionsViewModel(
175191 useCaseGroup
176192 )
177193
178- preview.setSurfaceProvider( previewView.surfaceProvider)
194+ preview.surfaceProvider = previewView.surfaceProvider
179195
180196 viewModelScope.launch {
181197 _cameraUiState .emit(_cameraUiState .value.copy(cameraState = CameraState .READY ))
182198 _captureUiState .emit(CaptureState .CaptureReady )
199+ previewView.previewStreamState.asFlow().collect { previewStreamState ->
200+ when (previewStreamState) {
201+ PreviewView .StreamState .IDLE -> {
202+ realtimeLatencyEstimateJob?.cancel()
203+ realtimeLatencyEstimateJob = null
204+ }
205+ PreviewView .StreamState .STREAMING -> {
206+ if (realtimeLatencyEstimateJob == null ) {
207+ realtimeLatencyEstimateJob = launch {
208+ observeRealtimeLatencyEstimate()
209+ }
210+ }
211+ }
212+ }
213+ }
214+ }
215+ }
216+
217+ private suspend fun CoroutineScope.observeRealtimeLatencyEstimate () {
218+ Log .d(TAG , " Starting realtime latency estimate job" )
219+
220+ val currentCameraUiState = _cameraUiState .value
221+ val isSupported =
222+ currentCameraUiState.extensionMode != ExtensionMode .NONE
223+ && imageCapture.realtimeCaptureLatencyEstimate != ImageCaptureLatencyEstimate .UNDEFINED_IMAGE_CAPTURE_LATENCY
224+
225+ if (! isSupported) {
226+ Log .d(TAG , " Starting realtime latency estimate job: no extension mode or not supported" )
227+ _cameraUiState .emit(
228+ _cameraUiState .value.copy(
229+ cameraState = CameraState .PREVIEW_ACTIVE ,
230+ realtimeCaptureLatencyEstimate = ImageCaptureLatencyEstimate .UNDEFINED_IMAGE_CAPTURE_LATENCY
231+ )
232+ )
233+ return
234+ }
235+
236+ while (isActive) {
237+ updateRealtimeCaptureLatencyEstimate()
238+ delay(REALTIME_LATENCY_UPDATE_INTERVAL_MILLIS )
183239 }
184240 }
185241
186242 /* *
187243 * Stops the preview stream. This should be invoked when the captured image is displayed.
188244 */
189245 fun stopPreview () {
190- preview.setSurfaceProvider(null )
246+ realtimeLatencyEstimateJob?.cancel()
247+ preview.surfaceProvider = null
191248 viewModelScope.launch {
192- _cameraUiState .emit(_cameraUiState .value.copy(cameraState = CameraState .PREVIEW_STOPPED ))
249+ _cameraUiState .emit(_cameraUiState .value.copy(
250+ cameraState = CameraState .PREVIEW_STOPPED ,
251+ realtimeCaptureLatencyEstimate = ImageCaptureLatencyEstimate .UNDEFINED_IMAGE_CAPTURE_LATENCY
252+ ))
193253 }
194254 }
195255
196256 /* *
197257 * Toggle the camera lens face. This has no effect if there is only one available camera lens.
198258 */
199259 fun switchCamera () {
260+ realtimeLatencyEstimateJob?.cancel()
200261 val currentCameraUiState = _cameraUiState .value
201- if (currentCameraUiState.cameraState == CameraState .READY ) {
262+ if (currentCameraUiState.cameraState == CameraState .READY || currentCameraUiState.cameraState == CameraState . PREVIEW_ACTIVE ) {
202263 // To switch the camera lens, there has to be at least 2 camera lenses
203264 if (currentCameraUiState.availableCameraLens.size == 1 ) return
204265
@@ -230,6 +291,7 @@ class CameraExtensionsViewModel(
230291 * exception containing more details on the reason for failure.
231292 */
232293 fun capturePhoto () {
294+ realtimeLatencyEstimateJob?.cancel()
233295 viewModelScope.launch {
234296 _captureUiState .emit(CaptureState .CaptureStarted )
235297 }
@@ -306,6 +368,7 @@ class CameraExtensionsViewModel(
306368 _cameraUiState .value.copy(
307369 cameraState = CameraState .NOT_READY ,
308370 extensionMode = extensionMode,
371+ realtimeCaptureLatencyEstimate = ImageCaptureLatencyEstimate .UNDEFINED_IMAGE_CAPTURE_LATENCY ,
309372 )
310373 )
311374 _captureUiState .emit(CaptureState .CaptureNotReady )
@@ -331,4 +394,18 @@ class CameraExtensionsViewModel(
331394 CameraSelector .LENS_FACING_BACK -> CameraSelector .DEFAULT_BACK_CAMERA
332395 else -> throw IllegalArgumentException (" Invalid lens facing type: $lensFacing " )
333396 }
397+
398+ private suspend fun updateRealtimeCaptureLatencyEstimate () {
399+ val estimate = imageCapture.realtimeCaptureLatencyEstimate
400+ Log .d(TAG , " Realtime capture latency estimate: $estimate " )
401+ if (estimate == ImageCaptureLatencyEstimate .UNDEFINED_IMAGE_CAPTURE_LATENCY ) {
402+ return
403+ }
404+ _cameraUiState .emit(
405+ _cameraUiState .value.copy(
406+ cameraState = CameraState .PREVIEW_ACTIVE ,
407+ realtimeCaptureLatencyEstimate = estimate
408+ )
409+ )
410+ }
334411}
0 commit comments