I've implemented a new sample, here is a link which describes new CameraX api from Google codelabs, but TextureView doesn't show anything and throw next exception:
OpenGLRenderer: [SurfaceTexture-0-7609-1] dequeueImage: SurfaceTexture is not attached to a View
Another camera samples such as a Camera2 and the native camera app work fine I used an emulator with API level Q beta 3
class CameraXFragment : Fragment(), TextureView.SurfaceTextureListener {
companion object {
fun newInstance(): Fragment = CameraXFragment()
}
private val REQUEST_CODE_PERMISSIONS = 10
private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.fragment_camera, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewFinder.surfaceTextureListener = this
}
private fun startCamera() {
CameraX.unbindAll()
val previewConfig = PreviewConfig.Builder().apply {
setTargetAspectRatio(Rational(1, 1))
setTargetResolution(Size(320, 320))
}.build()
val preview = Preview(previewConfig)
preview.setOnPreviewOutputUpdateListener {
viewFinder.surfaceTexture = it.surfaceTexture
updateTransform()
}
val imageCaptureConfig = ImageCaptureConfig.Builder()
.apply {
setTargetAspectRatio(Rational(1, 1))
setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY)
}.build()
val imageCapture = ImageCapture(imageCaptureConfig)
captureButton.setOnClickListener {
val file = File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM), "${System.currentTimeMillis()}.jpg")
imageCapture.takePicture(file,
object : ImageCapture.OnImageSavedListener {
override fun onError(error: ImageCapture.UseCaseError, message: String, t: Throwable?) {
t?.printStackTrace()
}
override fun onImageSaved(file: File) {
val msg = "Photo capture succeeded: ${file.absolutePath}"
Toast.makeText(requireContext(), msg, Toast.LENGTH_SHORT).show()
}
})
}
CameraX.bindToLifecycle(this, preview, imageCapture)
}
private fun updateTransform() {
val matrix = Matrix()
val centerX = viewFinder.width / 2f
val centerY = viewFinder.height / 2f
val rotationDegrees = when (viewFinder.display.rotation) {
Surface.ROTATION_0 -> 0
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
else -> return
}
matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY)
viewFinder.setTransform(matrix)
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
return true
}
override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) {
if (allPermissionsGranted()) {
viewFinder.post { startCamera() }
} else {
requestPermissions(REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
}
viewFinder.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
updateTransform()
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
viewFinder.post { startCamera() }
} else {
Toast.makeText(requireContext(), "Permissions are not granted", Toast.LENGTH_SHORT).show()
}
}
}
private fun allPermissionsGranted(): Boolean {
for (permission in REQUIRED_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(requireContext(), permission) != PackageManager.PERMISSION_GRANTED) {
return false
}
}
return true
}
}
The TextureView needs to be removed and re-added from the parent view for the SurfaceTexture to be attached. This is because TextureView internally creates its own SurfaceTexture once it is attached to the view hierarchy, and that internal SurfaceTexture only gets correctly detached once the parent TextureView is removed from the view hierarchy. You should change preview.setOnPreviewOutputUpdateListener
to:
preview.setOnPreviewOutputUpdateListener {
val parent = viewFinder.parent as ViewGroup
parent.removeView(viewFinder)
viewFinder.surfaceTexture = it.surfaceTexture
parent.addView(viewFinder, 0)
updateTransform()
}
It looks like you may have copied the code from the codelab, which has now been updated to include the view re-attachment. The official sample also implements this view re-attachment.
I faced the same problem when following codeLabs. I locked the screen then turned it on again and suddenly it worked normally, the capture function worked as well. I don't have any idea about this situation, but you can try this way as a work around. I'm using Q beta 3 in Pixel 3.
PS: You can just trigger onStop and onStart event for the Activity (for example: press home and open app again), the live preview will work. In my opinion, I think this problem related to the CameraX.bindToLifecycle
.
The Kotlin code of Oscar Wahltinez in Java:
ViewGroup parent = (ViewGroup) textureView.getParent();
parent.removeView(textureView);
parent.addView(textureView, 0);
SurfaceTexture surfaceTexture = previewOutput.getSurfaceTexture();
textureView.setSurfaceTexture(surfaceTexture);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With