Repository navigation
fix: single view on a SurfaceView instead of a TextureView (#76) - #185
Conversation
The TextureView's frames go through the app's own GL composition and window buffer; on a Mali-G31 TV box (Android 10) that buffer's queueBuffer stalled for seconds, growing with every grid/single switch (up to 14 s in 60 cycles). With a SurfaceView, as the grid cells use, the stalls are gone (#76). - Zoom and pan: the view is laid out zoom times the fitted size and moved by the pan; its surface keeps the fitted size (setFixedSize). Same gestures, clamping and double-tap reset as before, on every Android version. - Long-press screenshot and the debug page snapshot: PixelCopy on the snapshot thread (Snapshots.copy), the UI thread does not wait. Before Android 7 a screenshot says it is not available, the snapshot cell says "no picture". - MJPEG to H.264/H.265: the view is hidden and shown again for a new surface, as a grid cell does (#139); the decoder waits until surfaceCreated() and is closed in surfaceDestroyed(). - Version 2.09.
PR Summary by QodoMove single-video playback to SurfaceView to prevent rendering stalls
AI Description
Diagram
High-Level Assessment
Files changed (5)
|
Code Review by Qodo
1.
|
…check before the permission (Qodo on #185)
|
Device test 2026-10-15: passed. Stall test on Aceline-1 (AG-216, .144): fresh app process, then 60 cycles of grid → slot 4 single (4 s) → grid (8 s). Same script and layout as in the #76 comment of 2026-10-15.
TECNO KM4 (Android 15), single view on
|
…check before the permission (Qodo on #185)
fix: single view on a SurfaceView instead of a TextureView (#76)
…, debug page, JSON and log; phone menu, single view (SurfaceView, #185), Reset and WebUI
The README (EN/RU/ZH) keeps the overview, screenshots, download, requirements, quick start, a short controls summary, known limitations, a documentation table, support and license. The details move to English-only documents in docs/, each with its own table of contents: - usage.md: menu, gestures and keys, remote, multi-view, Slides, U cell, WebUI, share intent, screenshots, Reset - transports.md: URL formats and options per transport, codecs, authentication, what the servers support - debug.md: debug server, page, snapshot, stream checks, a /stats.json field reference from the code, /log, corner text, bug reports - architecture.md: data path diagram, classes, threads, decode planner (#85, #189), SurfaceView rendering (#61, #76), settings, layout - testing.md: branches, CI, unit tests, versions and releases, branch builds, mapping.txt (#181), local builds - security.md: network, credential store, debug server LAN rule, log redaction, WebUI, permissions - devices.md: verified and not working devices, hardware decoder notes - toys.md: drones and Wi-Fi toys, FLOW-UFO in testing (#187, #188), unknown toys, what to send The #76 freeze is no longer a known limitation (fixed in 2.09, #185). The device report form points to docs/devices.md. Docs only, no version bump.
The single view (
video_surface) moves from a TextureView to a SurfaceView, as the grid cells already use (#61).Why. On the AG-216 TV box (Aceline-1, Android 10, Mali-G31) the TextureView's frames go through the app's own GL composition and window buffer, and that buffer's
queueBufferstalled (Davey!) for seconds, growing with every grid ↔ single switch in one process: up to 14 s within 60 cycles, ANRs before. The experiment (7938365, branchexperiment/surfaceview-76) with a SurfaceView removed them: 0 stalls ≥ 2 s in 60 cycles, longest 1.4 s. Measurements: #76 (comment)The experiment lost three things; this PR puts them back:
setTransform(), andsetScaleX()/setTranslationX()move its surface only from Android 7 on. The view itself is now laid out zoom × the fitted size, centred, with the pan as left/top margin, which places the surface the same way on every Android version (minSdk 21). Its surface keeps the fitted size (SurfaceHolder.setFixedSize()infitVideo()), so SurfaceFlinger scales it and an MJPEG frame drawn withlockCanvas()is not up to 5× the screen. Gesture code, clamping (still against the fitted size) and double-tap reset are unchanged; the detectors get the touch in the parent's coordinates, since the view itself moves with the pan (Qodo). One visible difference: zoomed in, the picture now also fills the letterbox bars instead of being cut to the fitted rectangle.getBitmap()on a SurfaceView:PixelCopyon the existing snapshot thread (newSnapshots.copy(), which the grid cells' copies use too). The UI thread does not wait: the screenshot is saved from the copy callback, on its own thread as before (Cleanup: small code issues #35); the single view's debug snapshot is copied inSnapshots.render()like a grid cell.Snapshots.Cell.frameandrecycleFrames()are gone, nothing takes a frame on the UI thread any more. The screenshot is at stream size, at most 3840×2160 (scaled down above, Qodo). Before Android 7: the screenshot shows "Screenshots need Android 7 or newer" (checked before the storage permission), the snapshot cell "no picture".surfaceDestroyed()closes the decoder, drops the surface and shows the view again (unless the grid or U view took the screen meanwhile), andsurfacePendingends only insurfaceCreated(). No surface or already hidden: no recreation, the next surface is new anyway. The surface belongs to the SurfaceView and is never released by the app. One holder callback, added once inonCreate().Z-order: the SurfaceView stays behind the window (no
setZOrderOnTop/setZOrderMediaOverlay), so the status line, debug URLs, black clear overlay, stuck banner, the menu popup, dialogs (Reset, URL editor) and WebUI stay above it. It is GONE whenever the grid or the U view is on screen, as before.To test on a device (Aceline-1 and a phone):
/snapshot.jpg) in single view: the camera's picture in its cell.Davey!≥ 2 s count and longest stall against the table in the issue.Version 2.09.
Fixes #76