fix: Fix PreviewView being stretched (#2519)
* fix: Fix Preview stretching * feat: Keep screen on on Android * Add test code for race condition * fix: Fix preview stretching by awaiting SurfaceHolder resizing (`setFixedSize`) before configuring Camera * Format * Update SurfaceHolder+resize.kt * Update CameraPage.tsx
This commit is contained in:
parent
b20d0fc5f7
commit
3192f5e939
@ -232,7 +232,7 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
|
|||||||
/**
|
/**
|
||||||
* Set up the `CaptureSession` with all outputs (preview, photo, video, codeScanner) and their HDR/Format settings.
|
* Set up the `CaptureSession` with all outputs (preview, photo, video, codeScanner) and their HDR/Format settings.
|
||||||
*/
|
*/
|
||||||
private fun configureOutputs(configuration: CameraConfiguration) {
|
private suspend fun configureOutputs(configuration: CameraConfiguration) {
|
||||||
val cameraId = configuration.cameraId ?: throw NoCameraDeviceError()
|
val cameraId = configuration.cameraId ?: throw NoCameraDeviceError()
|
||||||
|
|
||||||
// Destroy previous outputs
|
// Destroy previous outputs
|
||||||
@ -313,7 +313,7 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
|
|||||||
)
|
)
|
||||||
outputs.add(output)
|
outputs.add(output)
|
||||||
// Size is usually landscape, so we flip it here
|
// Size is usually landscape, so we flip it here
|
||||||
previewView?.size = Size(size.height, size.width)
|
previewView?.setSurfaceSize(size.width, size.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CodeScanner Output
|
// CodeScanner Output
|
||||||
|
@ -10,25 +10,21 @@ import android.view.SurfaceView
|
|||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import com.facebook.react.bridge.UiThreadUtil
|
import com.facebook.react.bridge.UiThreadUtil
|
||||||
import com.mrousavy.camera.extensions.getMaximumPreviewSize
|
import com.mrousavy.camera.extensions.getMaximumPreviewSize
|
||||||
|
import com.mrousavy.camera.extensions.resize
|
||||||
import com.mrousavy.camera.types.ResizeMode
|
import com.mrousavy.camera.types.ResizeMode
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor")
|
@SuppressLint("ViewConstructor")
|
||||||
class PreviewView(context: Context, callback: SurfaceHolder.Callback) : SurfaceView(context) {
|
class PreviewView(context: Context, callback: SurfaceHolder.Callback) : SurfaceView(context) {
|
||||||
var size: Size = getMaximumPreviewSize()
|
var size: Size = getMaximumPreviewSize()
|
||||||
set(value) {
|
private set
|
||||||
field = value
|
|
||||||
UiThreadUtil.runOnUiThread {
|
|
||||||
Log.i(TAG, "Setting PreviewView Surface Size to $width x $height...")
|
|
||||||
holder.setFixedSize(value.height, value.width)
|
|
||||||
requestLayout()
|
|
||||||
invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var resizeMode: ResizeMode = ResizeMode.COVER
|
var resizeMode: ResizeMode = ResizeMode.COVER
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
UiThreadUtil.runOnUiThread {
|
UiThreadUtil.runOnUiThread {
|
||||||
|
Log.i(TAG, "Setting PreviewView ResizeMode to $value...")
|
||||||
requestLayout()
|
requestLayout()
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
@ -41,11 +37,23 @@ class PreviewView(context: Context, callback: SurfaceHolder.Callback) : SurfaceV
|
|||||||
FrameLayout.LayoutParams.MATCH_PARENT,
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
||||||
Gravity.CENTER
|
Gravity.CENTER
|
||||||
)
|
)
|
||||||
|
holder.setKeepScreenOn(true)
|
||||||
holder.addCallback(callback)
|
holder.addCallback(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun setSurfaceSize(width: Int, height: Int) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
size = Size(width, height)
|
||||||
|
Log.i(TAG, "Setting PreviewView Surface Size to $size...")
|
||||||
|
requestLayout()
|
||||||
|
invalidate()
|
||||||
|
holder.resize(width, height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getSize(contentSize: Size, containerSize: Size, resizeMode: ResizeMode): Size {
|
private fun getSize(contentSize: Size, containerSize: Size, resizeMode: ResizeMode): Size {
|
||||||
val contentAspectRatio = contentSize.width.toDouble() / contentSize.height
|
// TODO: Take sensor orientation into account here
|
||||||
|
val contentAspectRatio = contentSize.height.toDouble() / contentSize.width
|
||||||
val containerAspectRatio = containerSize.width.toDouble() / containerSize.height
|
val containerAspectRatio = containerSize.width.toDouble() / containerSize.height
|
||||||
|
|
||||||
val widthOverHeight = when (resizeMode) {
|
val widthOverHeight = when (resizeMode) {
|
||||||
|
@ -9,7 +9,7 @@ fun getMaximumPreviewSize(): Size {
|
|||||||
// See https://developer.android.com/reference/android/hardware/camera2/params/StreamConfigurationMap
|
// See https://developer.android.com/reference/android/hardware/camera2/params/StreamConfigurationMap
|
||||||
// According to the Android Developer documentation, PREVIEW streams can have a resolution
|
// According to the Android Developer documentation, PREVIEW streams can have a resolution
|
||||||
// of up to the phone's display's resolution, with a maximum of 1920x1080.
|
// of up to the phone's display's resolution, with a maximum of 1920x1080.
|
||||||
val display1080p = Size(1080, 1920)
|
val display1080p = Size(1920, 1080)
|
||||||
val displaySize = Size(
|
val displaySize = Size(
|
||||||
Resources.getSystem().displayMetrics.widthPixels,
|
Resources.getSystem().displayMetrics.widthPixels,
|
||||||
Resources.getSystem().displayMetrics.heightPixels
|
Resources.getSystem().displayMetrics.heightPixels
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.mrousavy.camera.extensions
|
||||||
|
|
||||||
|
import android.view.SurfaceHolder
|
||||||
|
import androidx.annotation.UiThread
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
|
||||||
|
@UiThread
|
||||||
|
suspend fun SurfaceHolder.resize(width: Int, height: Int) {
|
||||||
|
return suspendCancellableCoroutine { continuation ->
|
||||||
|
val currentSize = this.surfaceFrame
|
||||||
|
if (currentSize.width() == width && currentSize.height() == height) {
|
||||||
|
// Already in target size
|
||||||
|
continuation.resume(Unit)
|
||||||
|
return@suspendCancellableCoroutine
|
||||||
|
}
|
||||||
|
|
||||||
|
val callback = object : SurfaceHolder.Callback {
|
||||||
|
override fun surfaceCreated(holder: SurfaceHolder) = Unit
|
||||||
|
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
|
||||||
|
holder.removeCallback(this)
|
||||||
|
continuation.resume(Unit)
|
||||||
|
}
|
||||||
|
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||||
|
holder.removeCallback(this)
|
||||||
|
continuation.cancel(Error("Tried to resize SurfaceView, but Surface has been destroyed!"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.addCallback(callback)
|
||||||
|
this.setFixedSize(width, height)
|
||||||
|
}
|
||||||
|
}
|
@ -99,7 +99,6 @@ export function CameraPage({ navigation }: Props): React.ReactElement {
|
|||||||
},
|
},
|
||||||
[isPressingButton],
|
[isPressingButton],
|
||||||
)
|
)
|
||||||
// Camera callbacks
|
|
||||||
const onError = useCallback((error: CameraRuntimeError) => {
|
const onError = useCallback((error: CameraRuntimeError) => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
}, [])
|
}, [])
|
||||||
|
Loading…
Reference in New Issue
Block a user