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
import android.view.ScaleGestureDetector
import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener
import androidx.camera.core.Camera
import androidx.camera.core.CameraControl
import io.livekit.android.camerax.ui.ScaleZoomHelper.Companion.createGestureDetector
import io.livekit.android.room.track.LocalVideoTrack
import io.livekit.android.util.LKLog
import kotlinx.coroutines.flow.StateFlow
import livekit.org.webrtc.getCameraX
/**
* A helper class that handles zoom for a CameraX video capturer.
*
* For view based apps, [createGestureDetector] can be used to create a
* GestureDetector that attaches to your views to provide pinch-to-zoom
* functionality.
*
* For compose based apps, similar functionality can be implemented using
* Modifier.pointerInput:
*
* ```
* val scaleZoomHelper = remember(room, videoTrack) {
* if (videoTrack is LocalVideoTrack) {
* ScaleZoomHelper(videoTrack)
* } else {
* null
* }
* }
*
* VideoRenderer(
* modifier = modifier
* .pointerInput(Unit) {
* detectTransformGestures(
* onGesture = { _, _, zoom, _ ->
* scaleZoomHelper.zoom(zoom)
* },
* )
* },
* )
* ```
*/
class ScaleZoomHelper(
private val cameraFlow: StateFlow<Camera?>?,
) {
... ... @@ -32,10 +65,19 @@ class ScaleZoomHelper(
init {
if (cameraFlow != null) {
LKLog.w { "null camera flow passed in to ScaleZoomHelper, zoom is disabled." }
LKLog.i { "null camera flow passed in to ScaleZoomHelper, zoom is disabled." }
}
}
/**
* Scales the current zoom value by [factor].
*
* This method handles clamping the resulting zoom value to within the camera's
* minimum and maximum zoom. Best used with a scale gesture detector.
*
* @see CameraControl.setZoomRatio
* @see createGestureDetector
*/
fun zoom(factor: Float) {
val camera = cameraFlow?.value ?: return
val zoomState = camera.cameraInfo.zoomState.value ?: return
... ... @@ -48,10 +90,35 @@ class ScaleZoomHelper(
}
companion object {
/**
* Creates a ScaleGestureDetector that can be used with a view to provide pinch-to-zoom functionality.
*
* Example:
* ```
* val scaleGestureDetector = ScaleZoomHelper.createGestureDetector(viewBinding.renderer.context, localVideoTrack)
* viewBinding.renderer.setOnTouchListener { _, event ->
* scaleGestureDetector?.onTouchEvent(event)
* return@setOnTouchListener true
* }
* ```
*/
fun createGestureDetector(context: Context, localVideoTrack: LocalVideoTrack): ScaleGestureDetector {
return createGestureDetector(context, localVideoTrack.capturer.getCameraX())
}
/**
* Creates a ScaleGestureDetector that can be used with a view to provide pinch-to-zoom functionality.
*
* Example:
* ```
* val scaleGestureDetector = ScaleZoomHelper.createGestureDetector(viewBinding.renderer.context, localVideoTrack)
* viewBinding.renderer.setOnTouchListener { _, event ->
* scaleGestureDetector?.onTouchEvent(event)
* return@setOnTouchListener true
* }
* ```
*/
fun createGestureDetector(context: Context, cameraFlow: StateFlow<Camera?>?): ScaleGestureDetector {
val helper = ScaleZoomHelper(cameraFlow)
... ...
... ... @@ -29,12 +29,14 @@ class CameraXHelper {
companion object {
/**
* Gets a CameraProvider that uses CameraX for its sessions.
* Creates a CameraProvider that uses CameraX for its sessions.
*
* For use with [CameraCapturerUtils.registerCameraProvider].
* Remember to unregister the provider when outside the lifecycle
* of [lifecycleOwner].
*/
@ExperimentalCamera2Interop
fun getCameraProvider(
fun createCameraProvider(
lifecycleOwner: LifecycleOwner,
) = object : CameraCapturerUtils.CameraProvider {
... ...
... ... @@ -24,13 +24,13 @@ import android.hardware.camera2.CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_
import android.hardware.camera2.CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_OFF
import android.hardware.camera2.CameraMetadata.LENS_OPTICAL_STABILIZATION_MODE_ON
import android.hardware.camera2.CaptureRequest
import android.os.Handler
import android.util.Range
import android.util.Size
import android.view.Surface
import androidx.camera.camera2.interop.Camera2CameraInfo
import androidx.camera.camera2.interop.Camera2Interop
import androidx.camera.core.Camera
import androidx.camera.core.CameraControl
import androidx.camera.core.CameraSelector
import androidx.camera.core.ImageAnalysis
import androidx.camera.core.Preview
... ... @@ -60,7 +60,7 @@ internal constructor(
) : CameraSession {
private var state = SessionState.RUNNING
private var cameraThreadHandler = surfaceTextureHelper.handler
private var cameraThreadHandler: Handler = surfaceTextureHelper.handler
private lateinit var cameraProvider: ProcessCameraProvider
private lateinit var surfaceProvider: SurfaceProvider
var camera: Camera? = null
... ... @@ -228,8 +228,11 @@ internal constructor(
ContextCompat.getMainExecutor(context).execute {
cameraProvider.unbindAll()
cameraThreadHandler.postAtFrontOfQueue {
events.onCameraClosed(this)
Logging.d(TAG, "Stop done")
}
}
Logging.d(TAG, "Stop done")
}
private fun reportError(error: String) {
... ... @@ -311,8 +314,4 @@ internal constructor(
internal enum class StabilizationMode {
OPTICAL, VIDEO, NONE
}
interface CameraControlListener {
fun onCameraControlAvailable(control: CameraControl)
}
}
... ...
... ... @@ -147,7 +147,7 @@ class CallViewModel(
init {
CameraXHelper.getCameraProvider(ProcessLifecycleOwner.get()).let {
CameraXHelper.createCameraProvider(ProcessLifecycleOwner.get()).let {
if (it.isSupported(application)) {
CameraCapturerUtils.registerCameraProvider(it)
cameraProvider = it
... ...
... ... @@ -31,8 +31,17 @@ import io.livekit.android.room.track.Track
import io.livekit.android.room.track.VideoTrack
import io.livekit.android.sample.databinding.ParticipantItemBinding
import io.livekit.android.util.flow
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
class ParticipantItem(
private val room: Room,
... ...