Skip to content

Commit

Permalink
fix: Fix PreviewView being stretched (mrousavy#2519)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
mrousavy authored Feb 7, 2024
1 parent c98f81c commit 904b3d5
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
private fun configureOutputs(configuration: CameraConfiguration) {
private suspend fun configureOutputs(configuration: CameraConfiguration) {
val cameraId = configuration.cameraId ?: throw NoCameraDeviceError()

// Destroy previous outputs
Expand Down Expand Up @@ -313,7 +313,7 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
)
outputs.add(output)
// Size is usually landscape, so we flip it here
previewView?.size = Size(size.height, size.width)
previewView?.setSurfaceSize(size.width, size.height)
}

// CodeScanner Output
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,21 @@ import android.view.SurfaceView
import android.widget.FrameLayout
import com.facebook.react.bridge.UiThreadUtil
import com.mrousavy.camera.extensions.getMaximumPreviewSize
import com.mrousavy.camera.extensions.resize
import com.mrousavy.camera.types.ResizeMode
import kotlin.math.roundToInt
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext

@SuppressLint("ViewConstructor")
class PreviewView(context: Context, callback: SurfaceHolder.Callback) : SurfaceView(context) {
var size: Size = getMaximumPreviewSize()
set(value) {
field = value
UiThreadUtil.runOnUiThread {
Log.i(TAG, "Setting PreviewView Surface Size to $width x $height...")
holder.setFixedSize(value.height, value.width)
requestLayout()
invalidate()
}
}
private set
var resizeMode: ResizeMode = ResizeMode.COVER
set(value) {
field = value
UiThreadUtil.runOnUiThread {
Log.i(TAG, "Setting PreviewView ResizeMode to $value...")
requestLayout()
invalidate()
}
Expand All @@ -41,11 +37,23 @@ class PreviewView(context: Context, callback: SurfaceHolder.Callback) : SurfaceV
FrameLayout.LayoutParams.MATCH_PARENT,
Gravity.CENTER
)
holder.setKeepScreenOn(true)
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 {
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 widthOverHeight = when (resizeMode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ fun getMaximumPreviewSize(): Size {
// See https://developer.android.com/reference/android/hardware/camera2/params/StreamConfigurationMap
// 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.
val display1080p = Size(1080, 1920)
val display1080p = Size(1920, 1080)
val displaySize = Size(
Resources.getSystem().displayMetrics.widthPixels,
Resources.getSystem().displayMetrics.heightPixels
Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}
1 change: 0 additions & 1 deletion package/example/src/CameraPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ export function CameraPage({ navigation }: Props): React.ReactElement {
},
[isPressingButton],
)
// Camera callbacks
const onError = useCallback((error: CameraRuntimeError) => {
console.error(error)
}, [])
Expand Down

0 comments on commit 904b3d5

Please sign in to comment.