Skip to content

Commit 3192f5e

Browse files
authored
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
1 parent b20d0fc commit 3192f5e

File tree

5 files changed

+53
-14
lines changed

5 files changed

+53
-14
lines changed

package/android/src/main/java/com/mrousavy/camera/core/CameraSession.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ class CameraSession(private val context: Context, private val cameraManager: Cam
232232
/**
233233
* Set up the `CaptureSession` with all outputs (preview, photo, video, codeScanner) and their HDR/Format settings.
234234
*/
235-
private fun configureOutputs(configuration: CameraConfiguration) {
235+
private suspend fun configureOutputs(configuration: CameraConfiguration) {
236236
val cameraId = configuration.cameraId ?: throw NoCameraDeviceError()
237237

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

319319
// CodeScanner Output

package/android/src/main/java/com/mrousavy/camera/core/PreviewView.kt

+18-10
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,21 @@ import android.view.SurfaceView
1010
import android.widget.FrameLayout
1111
import com.facebook.react.bridge.UiThreadUtil
1212
import com.mrousavy.camera.extensions.getMaximumPreviewSize
13+
import com.mrousavy.camera.extensions.resize
1314
import com.mrousavy.camera.types.ResizeMode
1415
import kotlin.math.roundToInt
16+
import kotlinx.coroutines.Dispatchers
17+
import kotlinx.coroutines.withContext
1518

1619
@SuppressLint("ViewConstructor")
1720
class PreviewView(context: Context, callback: SurfaceHolder.Callback) : SurfaceView(context) {
1821
var size: Size = getMaximumPreviewSize()
19-
set(value) {
20-
field = value
21-
UiThreadUtil.runOnUiThread {
22-
Log.i(TAG, "Setting PreviewView Surface Size to $width x $height...")
23-
holder.setFixedSize(value.height, value.width)
24-
requestLayout()
25-
invalidate()
26-
}
27-
}
22+
private set
2823
var resizeMode: ResizeMode = ResizeMode.COVER
2924
set(value) {
3025
field = value
3126
UiThreadUtil.runOnUiThread {
27+
Log.i(TAG, "Setting PreviewView ResizeMode to $value...")
3228
requestLayout()
3329
invalidate()
3430
}
@@ -41,11 +37,23 @@ class PreviewView(context: Context, callback: SurfaceHolder.Callback) : SurfaceV
4137
FrameLayout.LayoutParams.MATCH_PARENT,
4238
Gravity.CENTER
4339
)
40+
holder.setKeepScreenOn(true)
4441
holder.addCallback(callback)
4542
}
4643

44+
suspend fun setSurfaceSize(width: Int, height: Int) {
45+
withContext(Dispatchers.Main) {
46+
size = Size(width, height)
47+
Log.i(TAG, "Setting PreviewView Surface Size to $size...")
48+
requestLayout()
49+
invalidate()
50+
holder.resize(width, height)
51+
}
52+
}
53+
4754
private fun getSize(contentSize: Size, containerSize: Size, resizeMode: ResizeMode): Size {
48-
val contentAspectRatio = contentSize.width.toDouble() / contentSize.height
55+
// TODO: Take sensor orientation into account here
56+
val contentAspectRatio = contentSize.height.toDouble() / contentSize.width
4957
val containerAspectRatio = containerSize.width.toDouble() / containerSize.height
5058

5159
val widthOverHeight = when (resizeMode) {

package/android/src/main/java/com/mrousavy/camera/extensions/CameraCharacteristics+getPreviewSize.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ fun getMaximumPreviewSize(): Size {
99
// See https://developer.android.com/reference/android/hardware/camera2/params/StreamConfigurationMap
1010
// According to the Android Developer documentation, PREVIEW streams can have a resolution
1111
// of up to the phone's display's resolution, with a maximum of 1920x1080.
12-
val display1080p = Size(1080, 1920)
12+
val display1080p = Size(1920, 1080)
1313
val displaySize = Size(
1414
Resources.getSystem().displayMetrics.widthPixels,
1515
Resources.getSystem().displayMetrics.heightPixels
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.mrousavy.camera.extensions
2+
3+
import android.view.SurfaceHolder
4+
import androidx.annotation.UiThread
5+
import kotlin.coroutines.resume
6+
import kotlinx.coroutines.suspendCancellableCoroutine
7+
8+
@UiThread
9+
suspend fun SurfaceHolder.resize(width: Int, height: Int) {
10+
return suspendCancellableCoroutine { continuation ->
11+
val currentSize = this.surfaceFrame
12+
if (currentSize.width() == width && currentSize.height() == height) {
13+
// Already in target size
14+
continuation.resume(Unit)
15+
return@suspendCancellableCoroutine
16+
}
17+
18+
val callback = object : SurfaceHolder.Callback {
19+
override fun surfaceCreated(holder: SurfaceHolder) = Unit
20+
override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
21+
holder.removeCallback(this)
22+
continuation.resume(Unit)
23+
}
24+
override fun surfaceDestroyed(holder: SurfaceHolder) {
25+
holder.removeCallback(this)
26+
continuation.cancel(Error("Tried to resize SurfaceView, but Surface has been destroyed!"))
27+
}
28+
}
29+
this.addCallback(callback)
30+
this.setFixedSize(width, height)
31+
}
32+
}

package/example/src/CameraPage.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ export function CameraPage({ navigation }: Props): React.ReactElement {
9999
},
100100
[isPressingButton],
101101
)
102-
// Camera callbacks
103102
const onError = useCallback((error: CameraRuntimeError) => {
104103
console.error(error)
105104
}, [])

0 commit comments

Comments
 (0)