davidliu
Committed by GitHub

More documentation for CameraX (#423)

* Call CameraSession.Events.onCameraClosed when session stops

* Documentation for ScaleZoomHelper

* Rename getCameraProvider to createCameraProvider to clarify behavior
@@ -20,11 +20,44 @@ import android.content.Context @@ -20,11 +20,44 @@ import android.content.Context
20 import android.view.ScaleGestureDetector 20 import android.view.ScaleGestureDetector
21 import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener 21 import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener
22 import androidx.camera.core.Camera 22 import androidx.camera.core.Camera
  23 +import androidx.camera.core.CameraControl
  24 +import io.livekit.android.camerax.ui.ScaleZoomHelper.Companion.createGestureDetector
23 import io.livekit.android.room.track.LocalVideoTrack 25 import io.livekit.android.room.track.LocalVideoTrack
24 import io.livekit.android.util.LKLog 26 import io.livekit.android.util.LKLog
25 import kotlinx.coroutines.flow.StateFlow 27 import kotlinx.coroutines.flow.StateFlow
26 import livekit.org.webrtc.getCameraX 28 import livekit.org.webrtc.getCameraX
27 29
  30 +/**
  31 + * A helper class that handles zoom for a CameraX video capturer.
  32 + *
  33 + * For view based apps, [createGestureDetector] can be used to create a
  34 + * GestureDetector that attaches to your views to provide pinch-to-zoom
  35 + * functionality.
  36 + *
  37 + * For compose based apps, similar functionality can be implemented using
  38 + * Modifier.pointerInput:
  39 + *
  40 + * ```
  41 + * val scaleZoomHelper = remember(room, videoTrack) {
  42 + * if (videoTrack is LocalVideoTrack) {
  43 + * ScaleZoomHelper(videoTrack)
  44 + * } else {
  45 + * null
  46 + * }
  47 + * }
  48 + *
  49 + * VideoRenderer(
  50 + * modifier = modifier
  51 + * .pointerInput(Unit) {
  52 + * detectTransformGestures(
  53 + * onGesture = { _, _, zoom, _ ->
  54 + * scaleZoomHelper.zoom(zoom)
  55 + * },
  56 + * )
  57 + * },
  58 + * )
  59 + * ```
  60 + */
28 class ScaleZoomHelper( 61 class ScaleZoomHelper(
29 private val cameraFlow: StateFlow<Camera?>?, 62 private val cameraFlow: StateFlow<Camera?>?,
30 ) { 63 ) {
@@ -32,10 +65,19 @@ class ScaleZoomHelper( @@ -32,10 +65,19 @@ class ScaleZoomHelper(
32 65
33 init { 66 init {
34 if (cameraFlow != null) { 67 if (cameraFlow != null) {
35 - LKLog.w { "null camera flow passed in to ScaleZoomHelper, zoom is disabled." } 68 + LKLog.i { "null camera flow passed in to ScaleZoomHelper, zoom is disabled." }
36 } 69 }
37 } 70 }
38 71
  72 + /**
  73 + * Scales the current zoom value by [factor].
  74 + *
  75 + * This method handles clamping the resulting zoom value to within the camera's
  76 + * minimum and maximum zoom. Best used with a scale gesture detector.
  77 + *
  78 + * @see CameraControl.setZoomRatio
  79 + * @see createGestureDetector
  80 + */
39 fun zoom(factor: Float) { 81 fun zoom(factor: Float) {
40 val camera = cameraFlow?.value ?: return 82 val camera = cameraFlow?.value ?: return
41 val zoomState = camera.cameraInfo.zoomState.value ?: return 83 val zoomState = camera.cameraInfo.zoomState.value ?: return
@@ -48,10 +90,35 @@ class ScaleZoomHelper( @@ -48,10 +90,35 @@ class ScaleZoomHelper(
48 } 90 }
49 91
50 companion object { 92 companion object {
  93 +
  94 + /**
  95 + * Creates a ScaleGestureDetector that can be used with a view to provide pinch-to-zoom functionality.
  96 + *
  97 + * Example:
  98 + * ```
  99 + * val scaleGestureDetector = ScaleZoomHelper.createGestureDetector(viewBinding.renderer.context, localVideoTrack)
  100 + * viewBinding.renderer.setOnTouchListener { _, event ->
  101 + * scaleGestureDetector?.onTouchEvent(event)
  102 + * return@setOnTouchListener true
  103 + * }
  104 + * ```
  105 + */
51 fun createGestureDetector(context: Context, localVideoTrack: LocalVideoTrack): ScaleGestureDetector { 106 fun createGestureDetector(context: Context, localVideoTrack: LocalVideoTrack): ScaleGestureDetector {
52 return createGestureDetector(context, localVideoTrack.capturer.getCameraX()) 107 return createGestureDetector(context, localVideoTrack.capturer.getCameraX())
53 } 108 }
54 109
  110 + /**
  111 + * Creates a ScaleGestureDetector that can be used with a view to provide pinch-to-zoom functionality.
  112 + *
  113 + * Example:
  114 + * ```
  115 + * val scaleGestureDetector = ScaleZoomHelper.createGestureDetector(viewBinding.renderer.context, localVideoTrack)
  116 + * viewBinding.renderer.setOnTouchListener { _, event ->
  117 + * scaleGestureDetector?.onTouchEvent(event)
  118 + * return@setOnTouchListener true
  119 + * }
  120 + * ```
  121 + */
55 fun createGestureDetector(context: Context, cameraFlow: StateFlow<Camera?>?): ScaleGestureDetector { 122 fun createGestureDetector(context: Context, cameraFlow: StateFlow<Camera?>?): ScaleGestureDetector {
56 val helper = ScaleZoomHelper(cameraFlow) 123 val helper = ScaleZoomHelper(cameraFlow)
57 124
@@ -29,12 +29,14 @@ class CameraXHelper { @@ -29,12 +29,14 @@ class CameraXHelper {
29 companion object { 29 companion object {
30 30
31 /** 31 /**
32 - * Gets a CameraProvider that uses CameraX for its sessions. 32 + * Creates a CameraProvider that uses CameraX for its sessions.
33 * 33 *
34 * For use with [CameraCapturerUtils.registerCameraProvider]. 34 * For use with [CameraCapturerUtils.registerCameraProvider].
  35 + * Remember to unregister the provider when outside the lifecycle
  36 + * of [lifecycleOwner].
35 */ 37 */
36 @ExperimentalCamera2Interop 38 @ExperimentalCamera2Interop
37 - fun getCameraProvider( 39 + fun createCameraProvider(
38 lifecycleOwner: LifecycleOwner, 40 lifecycleOwner: LifecycleOwner,
39 ) = object : CameraCapturerUtils.CameraProvider { 41 ) = object : CameraCapturerUtils.CameraProvider {
40 42
@@ -24,13 +24,13 @@ import android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ @@ -24,13 +24,13 @@ import android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_
24 import android.hardware.camera2.CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_OFF 24 import android.hardware.camera2.CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_OFF
25 import android.hardware.camera2.CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_ON 25 import android.hardware.camera2.CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_ON
26 import android.hardware.camera2.CaptureRequest 26 import android.hardware.camera2.CaptureRequest
  27 +import android.os.Handler
27 import android.util.Range 28 import android.util.Range
28 import android.util.Size 29 import android.util.Size
29 import android.view.Surface 30 import android.view.Surface
30 import androidx.camera.camera2.interop.Camera2CameraInfo 31 import androidx.camera.camera2.interop.Camera2CameraInfo
31 import androidx.camera.camera2.interop.Camera2Interop 32 import androidx.camera.camera2.interop.Camera2Interop
32 import androidx.camera.core.Camera 33 import androidx.camera.core.Camera
33 -import androidx.camera.core.CameraControl  
34 import androidx.camera.core.CameraSelector 34 import androidx.camera.core.CameraSelector
35 import androidx.camera.core.ImageAnalysis 35 import androidx.camera.core.ImageAnalysis
36 import androidx.camera.core.Preview 36 import androidx.camera.core.Preview
@@ -60,7 +60,7 @@ internal constructor( @@ -60,7 +60,7 @@ internal constructor(
60 ) : CameraSession { 60 ) : CameraSession {
61 61
62 private var state = SessionState.RUNNING 62 private var state = SessionState.RUNNING
63 - private var cameraThreadHandler = surfaceTextureHelper.handler 63 + private var cameraThreadHandler: Handler = surfaceTextureHelper.handler
64 private lateinit var cameraProvider: ProcessCameraProvider 64 private lateinit var cameraProvider: ProcessCameraProvider
65 private lateinit var surfaceProvider: SurfaceProvider 65 private lateinit var surfaceProvider: SurfaceProvider
66 var camera: Camera? = null 66 var camera: Camera? = null
@@ -228,8 +228,11 @@ internal constructor( @@ -228,8 +228,11 @@ internal constructor(
228 228
229 ContextCompat.getMainExecutor(context).execute { 229 ContextCompat.getMainExecutor(context).execute {
230 cameraProvider.unbindAll() 230 cameraProvider.unbindAll()
  231 + cameraThreadHandler.postAtFrontOfQueue {
  232 + events.onCameraClosed(this)
  233 + Logging.d(TAG, "Stop done")
  234 + }
231 } 235 }
232 - Logging.d(TAG, "Stop done")  
233 } 236 }
234 237
235 private fun reportError(error: String) { 238 private fun reportError(error: String) {
@@ -311,8 +314,4 @@ internal constructor( @@ -311,8 +314,4 @@ internal constructor(
311 internal enum class StabilizationMode { 314 internal enum class StabilizationMode {
312 OPTICAL, VIDEO, NONE 315 OPTICAL, VIDEO, NONE
313 } 316 }
314 -  
315 - interface CameraControlListener {  
316 - fun onCameraControlAvailable(control: CameraControl)  
317 - }  
318 } 317 }
@@ -147,7 +147,7 @@ class CallViewModel( @@ -147,7 +147,7 @@ class CallViewModel(
147 147
148 init { 148 init {
149 149
150 - CameraXHelper.getCameraProvider(ProcessLifecycleOwner.get()).let { 150 + CameraXHelper.createCameraProvider(ProcessLifecycleOwner.get()).let {
151 if (it.isSupported(application)) { 151 if (it.isSupported(application)) {
152 CameraCapturerUtils.registerCameraProvider(it) 152 CameraCapturerUtils.registerCameraProvider(it)
153 cameraProvider = it 153 cameraProvider = it
@@ -31,8 +31,17 @@ import io.livekit.android.room.track.Track @@ -31,8 +31,17 @@ import io.livekit.android.room.track.Track
31 import io.livekit.android.room.track.VideoTrack 31 import io.livekit.android.room.track.VideoTrack
32 import io.livekit.android.sample.databinding.ParticipantItemBinding 32 import io.livekit.android.sample.databinding.ParticipantItemBinding
33 import io.livekit.android.util.flow 33 import io.livekit.android.util.flow
34 -import kotlinx.coroutines.*  
35 -import kotlinx.coroutines.flow.* 34 +import kotlinx.coroutines.CoroutineScope
  35 +import kotlinx.coroutines.Dispatchers
  36 +import kotlinx.coroutines.ExperimentalCoroutinesApi
  37 +import kotlinx.coroutines.SupervisorJob
  38 +import kotlinx.coroutines.cancel
  39 +import kotlinx.coroutines.flow.Flow
  40 +import kotlinx.coroutines.flow.collectLatest
  41 +import kotlinx.coroutines.flow.flatMapLatest
  42 +import kotlinx.coroutines.flow.flowOf
  43 +import kotlinx.coroutines.flow.map
  44 +import kotlinx.coroutines.launch
36 45
37 class ParticipantItem( 46 class ParticipantItem(
38 private val room: Room, 47 private val room: Room,