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
正在显示
5 个修改的文件
包含
90 行增加
和
13 行删除
| @@ -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, |
-
请 注册 或 登录 后发表评论