feat: Add FPS Counter to Android (enableFpsGraph) (#2460)
				
					
				
			* feat: Add FPS Counter to Android (`enableFpsGraph`) * feat: Add FPS View * Update FpsCounterView.kt * Implement actual graph * fix layout * Update FpsGraphView.kt * Update CameraPage.tsx
This commit is contained in:
		| @@ -81,22 +81,28 @@ class CameraView(context: Context) : | |||||||
|       previewView.resizeMode = value |       previewView.resizeMode = value | ||||||
|       field = value |       field = value | ||||||
|     } |     } | ||||||
|  |   var enableFpsGraph: Boolean = false | ||||||
|  |     set(value) { | ||||||
|  |       field = value | ||||||
|  |       updateFpsGraph() | ||||||
|  |     } | ||||||
|  |  | ||||||
|   // code scanner |   // code scanner | ||||||
|   var codeScannerOptions: CodeScannerOptions? = null |   var codeScannerOptions: CodeScannerOptions? = null | ||||||
|  |  | ||||||
|   // private properties |   // private properties | ||||||
|   private var isMounted = false |   private var isMounted = false | ||||||
|  |   private val coroutineScope = CoroutineScope(CameraQueues.cameraQueue.coroutineDispatcher) | ||||||
|   internal val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager |   internal val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager | ||||||
|  |  | ||||||
|   // session |   // session | ||||||
|   internal val cameraSession: CameraSession |   internal val cameraSession: CameraSession | ||||||
|   private val previewView: PreviewView |   private val previewView: PreviewView | ||||||
|   private var currentConfigureCall: Long = System.currentTimeMillis() |   private var currentConfigureCall: Long = System.currentTimeMillis() | ||||||
|  |  | ||||||
|   internal var frameProcessor: FrameProcessor? = null |   internal var frameProcessor: FrameProcessor? = null | ||||||
|  |  | ||||||
|   private val coroutineScope = CoroutineScope(CameraQueues.cameraQueue.coroutineDispatcher) |   // other | ||||||
|  |   private var fpsGraph: FpsGraphView? = null | ||||||
|  |  | ||||||
|   init { |   init { | ||||||
|     this.installHierarchyFitter() |     this.installHierarchyFitter() | ||||||
| @@ -225,8 +231,22 @@ class CameraView(context: Context) : | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   private fun updateFpsGraph() { | ||||||
|  |     if (enableFpsGraph) { | ||||||
|  |       if (fpsGraph != null) return | ||||||
|  |       fpsGraph = FpsGraphView(context) | ||||||
|  |       addView(fpsGraph) | ||||||
|  |     } else { | ||||||
|  |       if (fpsGraph == null) return | ||||||
|  |       removeView(fpsGraph) | ||||||
|  |       fpsGraph = null | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   override fun onFrame(frame: Frame) { |   override fun onFrame(frame: Frame) { | ||||||
|     frameProcessor?.call(frame) |     frameProcessor?.call(frame) | ||||||
|  |  | ||||||
|  |     fpsGraph?.onTick() | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   override fun onError(error: Throwable) { |   override fun onError(error: Throwable) { | ||||||
|   | |||||||
| @@ -79,6 +79,11 @@ class CameraViewManager : ViewGroupManager<CameraView>() { | |||||||
|     view.enableZoomGesture = enableZoomGesture |     view.enableZoomGesture = enableZoomGesture | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   @ReactProp(name = "enableFpsGraph") | ||||||
|  |   fun setEnableFpsGraph(view: CameraView, enableFpsGraph: Boolean) { | ||||||
|  |     view.enableFpsGraph = enableFpsGraph | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @ReactProp(name = "videoStabilizationMode") |   @ReactProp(name = "videoStabilizationMode") | ||||||
|   fun setVideoStabilizationMode(view: CameraView, videoStabilizationMode: String?) { |   fun setVideoStabilizationMode(view: CameraView, videoStabilizationMode: String?) { | ||||||
|     val newMode = VideoStabilizationMode.fromUnionValue(videoStabilizationMode) |     val newMode = VideoStabilizationMode.fromUnionValue(videoStabilizationMode) | ||||||
|   | |||||||
| @@ -0,0 +1,116 @@ | |||||||
|  | package com.mrousavy.camera | ||||||
|  |  | ||||||
|  | import android.annotation.SuppressLint | ||||||
|  | import android.content.Context | ||||||
|  | import android.graphics.Canvas | ||||||
|  | import android.graphics.Color | ||||||
|  | import android.graphics.Paint | ||||||
|  | import android.view.Gravity | ||||||
|  | import android.view.View | ||||||
|  | import android.widget.FrameLayout | ||||||
|  | import android.widget.TextView | ||||||
|  | import kotlin.math.max | ||||||
|  | import kotlin.math.roundToInt | ||||||
|  |  | ||||||
|  | @SuppressLint("SetTextI18n") | ||||||
|  | class FpsGraphView(context: Context) : FrameLayout(context) { | ||||||
|  |   private val textView = TextView(context) | ||||||
|  |   private val graph = Graph(context) | ||||||
|  |  | ||||||
|  |   init { | ||||||
|  |     textView.textSize = 18f | ||||||
|  |     textView.setTextColor(Color.WHITE) | ||||||
|  |     textView.text = "0 FPS" | ||||||
|  |  | ||||||
|  |     graph.callback = object : Graph.Callback { | ||||||
|  |       override fun onAverageFpsChanged(averageFps: Int) { | ||||||
|  |         textView.text = "$averageFps FPS" | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     val layoutParams = LayoutParams(300, 150) | ||||||
|  |     layoutParams.setMargins(15, 150, 0, 0) | ||||||
|  |     layoutParams.gravity = Gravity.TOP or Gravity.LEFT | ||||||
|  |     addView(graph, layoutParams) | ||||||
|  |     addView(textView, layoutParams) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   fun onTick() { | ||||||
|  |     graph.onTick() | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   class Graph(context: Context) : | ||||||
|  |     View(context), | ||||||
|  |     Runnable { | ||||||
|  |     private val maxBars = 30 | ||||||
|  |     private val ticks = arrayListOf<Double>() | ||||||
|  |     private val bars = arrayListOf<Int>() | ||||||
|  |     private val paint = Paint().apply { | ||||||
|  |       color = Color.RED | ||||||
|  |       strokeWidth = 5f | ||||||
|  |       style = Paint.Style.FILL | ||||||
|  |     } | ||||||
|  |     var callback: Callback? = null | ||||||
|  |  | ||||||
|  |     init { | ||||||
|  |       post(this) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun run() { | ||||||
|  |       val averageFps = getAverageFps(ticks) | ||||||
|  |       ticks.clear() | ||||||
|  |  | ||||||
|  |       bars.add(averageFps) | ||||||
|  |       if (bars.size > maxBars) { | ||||||
|  |         bars.removeAt(0) | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       invalidate() | ||||||
|  |       postDelayed(this, 1000) | ||||||
|  |  | ||||||
|  |       callback?.onAverageFpsChanged(averageFps) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private fun getAverageFps(ticks: List<Double>): Int { | ||||||
|  |       if (ticks.isEmpty()) return 0 | ||||||
|  |       if (ticks.size < 2) return 1 | ||||||
|  |  | ||||||
|  |       val totalDuration = ticks.last() - ticks.first() | ||||||
|  |       val averageFrameDuration = totalDuration / (ticks.size - 1).toDouble() | ||||||
|  |  | ||||||
|  |       val double = 1000.0 / averageFrameDuration | ||||||
|  |       return double.roundToInt() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fun onTick() { | ||||||
|  |       val currentTick = System.currentTimeMillis() | ||||||
|  |       ticks.add(currentTick.toDouble()) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     override fun onDraw(canvas: Canvas) { | ||||||
|  |       super.onDraw(canvas) | ||||||
|  |  | ||||||
|  |       if (bars.size < 2) return | ||||||
|  |  | ||||||
|  |       val maxFps = max(bars.max().toFloat(), 60f) | ||||||
|  |       val blockWidth = width.toFloat() / maxBars | ||||||
|  |       for (i in 0..maxBars) { | ||||||
|  |         val fps = bars.getOrNull(i) | ||||||
|  |         if (fps != null) { | ||||||
|  |           val blockHeight = (fps / maxFps) * height | ||||||
|  |           canvas.drawRect( | ||||||
|  |             i * blockWidth, | ||||||
|  |             height - blockHeight, | ||||||
|  |             (i + 1) * blockWidth, | ||||||
|  |             height.toFloat(), | ||||||
|  |             paint | ||||||
|  |           ) | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     interface Callback { | ||||||
|  |       fun onAverageFpsChanged(averageFps: Int) | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user