Just in case somebody else runs into the same problem, here is the solution that worked for me. I had a timing issue with the Camera2 example / CamEx2 class on B4A.
In my app the camera preview is hosted inside a custom XUI view. The preview panel is resized dynamically before the camera is opened. In some cases the code got stuck in CamEx2 here:
After adding some tracing I found an important detail: The B4X view size and the native Android view size can temporarily differ.
After calling
The workaround that helped me was:
The
Example modification in
In my final app code I moved most of the fix outside CamEx2: before calling
However, I still think the
So in my case the root cause was a timing issue between B4X layout values, the native Android layout pass, and the TextureView
In my app the camera preview is hosted inside a custom XUI view. The preview panel is resized dynamically before the camera is opened. In some cases the code got stuck in CamEx2 here:
B4X:
Wait For Camera_SurfaceReady
After adding some tracing I found an important detail: The B4X view size and the native Android view size can temporarily differ.
After calling
SetLayout / SetLayoutAnimated, the B4X view can already report the new Width / Height, while the native Android view still has the old layout until Android has completed its next layout pass. There are cases where the B4X preview panel already had the expected size, but the native Android view still had the previous size or even no size and CamEx2 already creates the TextureView during this time slot. The SurfaceTexture lifecycle can become timing-sensitive.The workaround that helped me was:
- make sure the native preview panel / TextureView layout is updated before waiting
- call
requestLayout/invalidate - and, very importantly, check
TextureView.isAvailablebefore waiting forCamera_SurfaceReady
The
isAvailable guard is important because Camera_SurfaceReady is an event, not a state. If the TextureView surface is already available before the Wait For line is reached, the event will may not be replayed.Example modification in
CamEx2.CreateSurface:
B4X:
Private Sub CreateSurface As ResumableSub
If tv.IsInitialized Then tv.RemoveView
tv = Camera.CreateSurface
mPanel.AddView(tv, 0, 0, mPanel.Width, mPanel.Height)
tv.SendToBack
Dim joPanel As JavaObject = mPanel
Dim joTv As JavaObject = tv
'Request / force the native Android layout to match the B4A panel size.
joPanel.RunMethod("layout", Array As Object(0, 0, mPanel.Width, mPanel.Height))
joTv.RunMethod("layout", Array As Object(0, 0, mPanel.Width, mPanel.Height))
joPanel.RunMethod("requestLayout", Null)
joPanel.RunMethod("invalidate", Null)
joTv.RunMethod("requestLayout", Null)
joTv.RunMethod("invalidate", Null)
'SurfaceReady is an event, not a state.
'If the TextureView surface already exists, waiting for the event can hang.
Dim SurfaceAvailable As Boolean
Try
SurfaceAvailable = joTv.RunMethod("isAvailable", Null)
Catch
SurfaceAvailable = False
End Try
If SurfaceAvailable Then
Return True
End If
Wait For Camera_SurfaceReady
Return True
End Sub
In my final app code I moved most of the fix outside CamEx2: before calling
PrepareSurface, I wait until the native Android layout of my preview panel matches the B4X panel size. This allows CamEx2 to stay almost unchanged.However, I still think the
isAvailable guard is a useful defensive check in CamEx2 itself, because it protects against missing the Camera_SurfaceReady event if the TextureView surface is already available.So in my case the root cause was a timing issue between B4X layout values, the native Android layout pass, and the TextureView
SurfaceTexture callback.