Skip to content

Render the main video on a SurfaceView unless object detection needs the TextureView - #114

Open
iflyhere wants to merge 1 commit into
OpenIPC:masterfrom
iflyhere:fix/main-video-surfaceview
Open

Render the main video on a SurfaceView unless object detection needs the TextureView#114
iflyhere wants to merge 1 commit into
OpenIPC:masterfrom
iflyhere:fix/main-video-surfaceview

Conversation

@iflyhere

@iflyhere iflyhere commented Aug 27, 2026

Copy link
Copy Markdown

Note

Compile tested only (arm64-v8a + armeabi-v7a). Not yet flown. Object detection
itself is unchanged by this PR, but the renderer switch path deserves a check
on hardware.

The problem

#108 changed the primary renderer from a SurfaceView to a TextureView:

-    <SurfaceView
+    <TextureView
         android:id="@+id/mainVideo"

That is needed for the feature — TextureView.getBitmap() is how frames reach
MediaPipe — but the swap is unconditional. Users with object detection off, i.e.
everybody by default, now get the TextureView path too.

The difference is not cosmetic. A SurfaceView gets its own layer and can be
promoted to a hardware overlay plane: the decoder writes into it and the display
controller scans it out. A TextureView is drawn as part of the view hierarchy,
so every decoded frame has to go through GPU composition together with the rest
of the UI. That costs GPU time, power, and roughly one frame of extra latency,
on a receiver whose whole point is low latency.

The change

The layout carries both renderers and the active one is picked from the existing
od_enabled preference:

Object detection Renderer Behaviour
off (default) mainVideoSurface (SurfaceView) pre-#108
on mainVideo (TextureView) unchanged from #108

Details:

  • setupStandardVideoPlayer() selects the view and records the choice in
    videoUsesTextureView
  • setupVRVideoPlayers() hides both, as before
  • onVideoRatioChanged() keeps the aspect ratio of the new view in sync
  • startObjectDetectionLoop() bails out unless the TextureView is the active
    renderer, so getBitmap() is never called on the wrong view

Why the toggle restarts the app

Switching detection on or off means the decoder needs a different surface.
VideoPlayer.stopAndRemoveReceiverDecoder() also calls stop(), which tears
down the UDP receiver, and nothing restarts it — only onResume() calls
start(). So a hot surface swap silently kills reception today. Rather than
rework the receiver lifecycle in this PR, the toggle calls resetApp(), the
same thing the existing VR mode toggle does, and says so with a toast.

Fixing VideoPlayer's receiver/decoder lifecycle so the surface can be swapped
in place would be a good follow-up; it would also let VR mode be toggled without
a restart.


Part of a small series of independent fixes found while profiling the receive path.
Each one is standalone and mergeable on its own, in any order — no dependencies
between them, and no shared files except VideoActivity.java / VideoPlayer.*,
which touch different methods:

All five compile clean for arm64-v8a + armeabi-v7a.

…the TextureView

OpenIPC#108 replaced the main video SurfaceView with a TextureView so MediaPipe can
grab frames via getBitmap(). That swap is unconditional, so every user pays
for it even with object detection turned off: the video is no longer eligible
for a hardware overlay plane and instead goes through the view hierarchy's GPU
composition, which costs GPU time, power and about one frame of latency.

The layout now carries both renderers and the active one is picked from the
existing "od_enabled" preference:

- object detection off (default) -> mainVideoSurface (SurfaceView), the
  pre-OpenIPC#108 behaviour
- object detection on            -> mainVideo (TextureView), unchanged

Toggling detection in the menu swaps the renderer, which means the decoder
needs a different surface. VideoPlayer.stopAndRemoveReceiverDecoder() also
stops the UDP receiver and nothing restarts it, so a hot swap is not safe
today; the toggle restarts the app instead, the same way the VR mode toggle
already does. startObjectDetectionLoop() bails out if the TextureView is not
the active renderer.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Use SurfaceView unless object detection requires TextureView

🐞 Bug fix ✨ Enhancement 🕐 20-40 Minutes

Grey Divider

AI Description

• Selects SurfaceView by default to restore low-latency, hardware-overlay video rendering.
• Retains TextureView frame capture when object detection is enabled.
• Restarts standard-mode playback when detection toggles to safely replace decoder surfaces.
Diagram

graph TD
  P["OD Preference"] --> A["Video Activity"] --> D{"Detection enabled?"}
  D -- No --> S["SurfaceView"]
  D -- Yes --> T["TextureView"] --> M["MediaPipe"]
  A --> V["VideoPlayer"] --> S
  V --> T
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Hot-swap decoder surfaces
  • ➕ Avoids restarting the application when detection changes
  • ➕ Could also enable seamless VR mode switching
  • ➖ Requires reworking VideoPlayer receiver and decoder lifecycle
  • ➖ Expands native playback risk beyond this performance fix
2. Keep TextureView always active
  • ➕ Preserves the simplest renderer lifecycle
  • ➕ Allows immediate object-detection toggling
  • ➖ Retains unnecessary GPU composition and power usage
  • ➖ Adds latency for users with detection disabled

Recommendation: Keep the PR's preference-driven dual-renderer approach and restart safeguard. It restores the efficient default without destabilizing receiver lifecycle code; pursue hot surface swapping separately with dedicated hardware and lifecycle testing.

Files changed (2) +45 / -1

Enhancement (1) +14 / -0
activity_video.xmlAdd a dedicated low-latency main SurfaceView +14/-0

Add a dedicated low-latency main SurfaceView

• Adds a full-size SurfaceView as the default main renderer while retaining the hidden TextureView for object detection. Both views share the same constrained video area.

app/src/main/res/layout/activity_video.xml

Bug fix (1) +31 / -1
VideoActivity.javaSelect the main renderer from object-detection state +31/-1

Select the main renderer from object-detection state

• Chooses SurfaceView for normal playback and TextureView when object detection requires bitmap capture. It synchronizes aspect ratios, hides both main views in VR mode, restarts after renderer-changing toggles, and prevents detection from running without TextureView.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Restart can lose toggle 🐞 Bug ☼ Reliability
Description
The new renderer-switch branch calls resetApp() immediately after od_enabled was saved with
asynchronous apply(), and resetApp() terminates the process with System.exit(0). If the disk
write has not completed, the relaunched activity reads the old value and selects the old renderer,
so the user's toggle appears to fail.
Code

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[R2010-2012]

+        if (!isVRMode && enabled != videoUsesTextureView) {
+            Toast.makeText(this, "Restarting to switch video renderer...", Toast.LENGTH_SHORT).show();
+            resetApp();
Evidence
The setter asynchronously applies od_enabled and the added branch immediately invokes a helper
that kills the process. The equivalent VR toggle explicitly uses commit() before calling that
helper, demonstrating that restart-sensitive settings are persisted synchronously in this codebase.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[2004-2013]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[202-210]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[159-164]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[643-648]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The renderer restart can terminate the process before the asynchronous `od_enabled` preference write reaches disk, causing the app to restart with the old renderer.

## Issue Context
`resetApp()` immediately starts a fresh task and calls `System.exit(0)`. The existing VR restart path uses synchronous `commit()` before invoking the same reset helper.

## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[2004-2013]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[202-210]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Disabled detection keeps TextureView 🐞 Bug ➹ Performance
Description
setupStandardVideoPlayer() selects the TextureView solely from the persisted flag before
onResume() validates runtime and model availability. When that validation disables object
detection, it returns without switching or restarting the renderer, leaving the expensive
TextureView active for the entire session even though detection is off.
Code

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[R389-390]

+        videoUsesTextureView = getSharedPreferences("general", MODE_PRIVATE)
+                .getBoolean("od_enabled", false);
Evidence
Renderer setup reads the raw persisted flag during onCreate, while availability validation happens
later from onResume. The validation failure clears the preference and returns before the newly
added mismatch/reset logic, so the already configured TextureView remains active.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[278-293]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[356-366]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[389-400]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1514-1516]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1988-2001]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Startup can select the TextureView from a stale enabled preference and then automatically disable object detection without returning to the SurfaceView.

## Issue Context
This occurs when a previously enabled model is no longer available or the object-detection runtime is unsupported. The early validation failure clears `od_enabled` but does not execute the new renderer-mismatch restart branch.

## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[389-400]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1988-2001]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can ask Qodo to dismiss a finding you disagree with, with your reason on record

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +2010 to +2012
if (!isVRMode && enabled != videoUsesTextureView) {
Toast.makeText(this, "Restarting to switch video renderer...", Toast.LENGTH_SHORT).show();
resetApp();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Restart can lose toggle 🐞 Bug ☼ Reliability

The new renderer-switch branch calls resetApp() immediately after od_enabled was saved with
asynchronous apply(), and resetApp() terminates the process with System.exit(0). If the disk
write has not completed, the relaunched activity reads the old value and selects the old renderer,
so the user's toggle appears to fail.
Agent Prompt
## Issue description
The renderer restart can terminate the process before the asynchronous `od_enabled` preference write reaches disk, causing the app to restart with the old renderer.

## Issue Context
`resetApp()` immediately starts a fresh task and calls `System.exit(0)`. The existing VR restart path uses synchronous `commit()` before invoking the same reset helper.

## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[2004-2013]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[202-210]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +389 to +390
videoUsesTextureView = getSharedPreferences("general", MODE_PRIVATE)
.getBoolean("od_enabled", false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Disabled detection keeps textureview 🐞 Bug ➹ Performance

setupStandardVideoPlayer() selects the TextureView solely from the persisted flag before
onResume() validates runtime and model availability. When that validation disables object
detection, it returns without switching or restarting the renderer, leaving the expensive
TextureView active for the entire session even though detection is off.
Agent Prompt
## Issue description
Startup can select the TextureView from a stale enabled preference and then automatically disable object detection without returning to the SurfaceView.

## Issue Context
This occurs when a previously enabled model is no longer available or the object-detection runtime is unsupported. The early validation failure clears `od_enabled` but does not execute the new renderer-mismatch restart branch.

## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[389-400]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1988-2001]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant