@@ -18,13 +18,15 @@ package com.example.android.cameraxextensions.viewmodel
18
18
19
19
import android.app.Application
20
20
import android.graphics.Bitmap
21
+ import android.util.Log
21
22
import androidx.camera.core.AspectRatio
22
23
import androidx.camera.core.Camera
23
24
import androidx.camera.core.CameraSelector
24
25
import androidx.camera.core.CameraSelector.LensFacing
25
26
import androidx.camera.core.FocusMeteringAction
26
27
import androidx.camera.core.ImageCapture
27
28
import androidx.camera.core.ImageCaptureException
29
+ import androidx.camera.core.ImageCaptureLatencyEstimate
28
30
import androidx.camera.core.MeteringPoint
29
31
import androidx.camera.core.Preview
30
32
import androidx.camera.core.UseCaseGroup
@@ -41,11 +43,16 @@ import com.example.android.cameraxextensions.model.CameraUiState
41
43
import com.example.android.cameraxextensions.model.CaptureState
42
44
import com.example.android.cameraxextensions.repository.ImageCaptureRepository
43
45
import kotlinx.coroutines.Dispatchers
46
+ import kotlinx.coroutines.Job
44
47
import kotlinx.coroutines.asExecutor
48
+ import kotlinx.coroutines.delay
45
49
import kotlinx.coroutines.flow.Flow
46
50
import kotlinx.coroutines.flow.MutableStateFlow
47
51
import kotlinx.coroutines.guava.await
52
+ import kotlinx.coroutines.isActive
48
53
import kotlinx.coroutines.launch
54
+ import androidx.lifecycle.asFlow
55
+ import kotlinx.coroutines.CoroutineScope
49
56
50
57
/* *
51
58
* View model for camera extensions. This manages all the operations on the camera.
@@ -61,6 +68,11 @@ class CameraExtensionsViewModel(
61
68
private val application : Application ,
62
69
private val imageCaptureRepository : ImageCaptureRepository
63
70
) : ViewModel() {
71
+ private companion object {
72
+ const val TAG = " CameraExtensionsViewModel"
73
+ const val REALTIME_LATENCY_UPDATE_INTERVAL_MILLIS = 1000L
74
+ }
75
+
64
76
private lateinit var cameraProvider: ProcessCameraProvider
65
77
private lateinit var extensionsManager: ExtensionsManager
66
78
@@ -69,6 +81,7 @@ class CameraExtensionsViewModel(
69
81
private var imageCapture = ImageCapture .Builder ()
70
82
.setTargetAspectRatio(AspectRatio .RATIO_16_9 )
71
83
.build()
84
+ private var realtimeLatencyEstimateJob: Job ? = null
72
85
73
86
private val preview = Preview .Builder ()
74
87
.setTargetAspectRatio(AspectRatio .RATIO_16_9 )
@@ -127,6 +140,7 @@ class CameraExtensionsViewModel(
127
140
availableExtensions = listOf (ExtensionMode .NONE ) + availableExtensions,
128
141
availableCameraLens = availableCameraLens,
129
142
extensionMode = if (availableExtensions.isEmpty()) ExtensionMode .NONE else currentCameraUiState.extensionMode,
143
+ realtimeCaptureLatencyEstimate = ImageCaptureLatencyEstimate .UNDEFINED_IMAGE_CAPTURE_LATENCY ,
130
144
)
131
145
_cameraUiState .emit(newCameraUiState)
132
146
}
@@ -141,6 +155,8 @@ class CameraExtensionsViewModel(
141
155
lifecycleOwner : LifecycleOwner ,
142
156
previewView : PreviewView
143
157
) {
158
+ realtimeLatencyEstimateJob?.cancel()
159
+
144
160
val currentCameraUiState = _cameraUiState .value
145
161
val cameraSelector = if (currentCameraUiState.extensionMode == ExtensionMode .NONE ) {
146
162
cameraLensToSelector(currentCameraUiState.cameraLens)
@@ -175,30 +191,75 @@ class CameraExtensionsViewModel(
175
191
useCaseGroup
176
192
)
177
193
178
- preview.setSurfaceProvider( previewView.surfaceProvider)
194
+ preview.surfaceProvider = previewView.surfaceProvider
179
195
180
196
viewModelScope.launch {
181
197
_cameraUiState .emit(_cameraUiState .value.copy(cameraState = CameraState .READY ))
182
198
_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 )
183
239
}
184
240
}
185
241
186
242
/* *
187
243
* Stops the preview stream. This should be invoked when the captured image is displayed.
188
244
*/
189
245
fun stopPreview () {
190
- preview.setSurfaceProvider(null )
246
+ realtimeLatencyEstimateJob?.cancel()
247
+ preview.surfaceProvider = null
191
248
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
+ ))
193
253
}
194
254
}
195
255
196
256
/* *
197
257
* Toggle the camera lens face. This has no effect if there is only one available camera lens.
198
258
*/
199
259
fun switchCamera () {
260
+ realtimeLatencyEstimateJob?.cancel()
200
261
val currentCameraUiState = _cameraUiState .value
201
- if (currentCameraUiState.cameraState == CameraState .READY ) {
262
+ if (currentCameraUiState.cameraState == CameraState .READY || currentCameraUiState.cameraState == CameraState . PREVIEW_ACTIVE ) {
202
263
// To switch the camera lens, there has to be at least 2 camera lenses
203
264
if (currentCameraUiState.availableCameraLens.size == 1 ) return
204
265
@@ -230,6 +291,7 @@ class CameraExtensionsViewModel(
230
291
* exception containing more details on the reason for failure.
231
292
*/
232
293
fun capturePhoto () {
294
+ realtimeLatencyEstimateJob?.cancel()
233
295
viewModelScope.launch {
234
296
_captureUiState .emit(CaptureState .CaptureStarted )
235
297
}
@@ -306,6 +368,7 @@ class CameraExtensionsViewModel(
306
368
_cameraUiState .value.copy(
307
369
cameraState = CameraState .NOT_READY ,
308
370
extensionMode = extensionMode,
371
+ realtimeCaptureLatencyEstimate = ImageCaptureLatencyEstimate .UNDEFINED_IMAGE_CAPTURE_LATENCY ,
309
372
)
310
373
)
311
374
_captureUiState .emit(CaptureState .CaptureNotReady )
@@ -331,4 +394,18 @@ class CameraExtensionsViewModel(
331
394
CameraSelector .LENS_FACING_BACK -> CameraSelector .DEFAULT_BACK_CAMERA
332
395
else -> throw IllegalArgumentException (" Invalid lens facing type: $lensFacing " )
333
396
}
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
+ }
334
411
}
0 commit comments