Skip to content

Add Ground Station Streaming: hand video/telemetry ports to an external app - #125

Open
mengelh wants to merge 1 commit into
OpenIPC:masterfrom
mengelh:ground-station-streaming
Open

mengelh wants to merge 1 commit into
OpenIPC:masterfrom
mengelh:ground-station-streaming

Conversation

@mengelh

@mengelh mengelh commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

PixelPilot's own WfbngLink::initAgg() already sends decrypted video
(RTP/H264) and MAVLink to 127.0.0.1:5600/14550 unconditionally via
wfb-ng's AggregatorUDPv4 -- but the app's own in-app video player and
MAVLink OSD parser also bind those same ports, so nothing else on the
device can. This adds a way to free them up for any other UDP-based
ground station app (QGroundControl, Mission Planner, a custom OSD, ...)
running on the same device, without touching devourer/wfb-ng at all.

Two independent settings-menu toggles under "Ground Station Streaming",
"Video" and "Telemetry" (a ground station app might only care about
one channel -- e.g. a video-only OSD box, or a telemetry-only
companion computer):

  • Video: stops/starts the in-app UDPReceiver + decoder on port 5600.
  • Telemetry: stops/starts the in-app MAVLink OSD parser on port 14550.

Both apply immediately (no restart) by starting/stopping the relevant
in-app consumer right away, and persist via SharedPreferences so they
survive a restart.

onPause()/onStop() now skip wfbLinkManager.stopAdapters() while either
channel is streaming, since the whole point is for wfb-ng to keep
forwarding while this Activity is backgrounded (e.g. the ground station
app in the foreground instead) -- both channels share one USB/wfb-ng
adapter, so it can't be paused for one channel while kept alive for the
other. This only covers ordinary Activity lifecycle transitions, not
memory-pressure eviction; a Foreground Service would be needed for
guaranteed long-running background survival, which is out of scope
here.

Verified on-device (RTL8812AU, Galaxy Tab S7 FE) with QGroundControl:
both video and telemetry arrive correctly with their respective toggle
enabled; each toggle independently frees/rebinds only its own port;
the USB/wfb-ng pipeline is never restarted by toggling or by
backgrounding while streaming is active; background/foreground cycling
with only one channel enabled still keeps the pipeline alive.

🤖 Generated with Claude Code

…al app

PixelPilot's own WfbngLink::initAgg() already sends decrypted video
(RTP/H264) and MAVLink to 127.0.0.1:5600/14550 unconditionally via
wfb-ng's AggregatorUDPv4 -- but the app's own in-app video player and
MAVLink OSD parser also bind those same ports, so nothing else on the
device can. This adds a way to free them up for any other UDP-based
ground station app (QGroundControl, Mission Planner, a custom OSD, ...)
running on the same device, without touching devourer/wfb-ng at all.

Two independent settings-menu toggles under "Ground Station Streaming",
"Video" and "Telemetry" (a ground station app might only care about
one channel -- e.g. a video-only OSD box, or a telemetry-only
companion computer):
- Video: stops/starts the in-app UDPReceiver + decoder on port 5600.
- Telemetry: stops/starts the in-app MAVLink OSD parser on port 14550.

Both apply immediately (no restart) by starting/stopping the relevant
in-app consumer right away, and persist via SharedPreferences so they
survive a restart.

onPause()/onStop() now skip wfbLinkManager.stopAdapters() while either
channel is streaming, since the whole point is for wfb-ng to keep
forwarding while this Activity is backgrounded (e.g. the ground station
app in the foreground instead) -- both channels share one USB/wfb-ng
adapter, so it can't be paused for one channel while kept alive for the
other. This only covers ordinary Activity lifecycle transitions, not
memory-pressure eviction; a Foreground Service would be needed for
guaranteed long-running background survival, which is out of scope
here.

Verified on-device (RTL8812AU, Galaxy Tab S7 FE) with QGroundControl:
both video and telemetry arrive correctly with their respective toggle
enabled; each toggle independently frees/rebinds only its own port;
the USB/wfb-ng pipeline is never restarted by toggling or by
backgrounding while streaming is active; background/foreground cycling
with only one channel enabled still keeps the pipeline alive.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

PR Summary by Qodo

Add independent video and telemetry ground station streaming toggles

✨ Enhancement 🕐 20-40 Minutes

Grey Divider

AI Description

• Add persistent, independent toggles that release video and telemetry UDP ports for external ground
 station apps.
• Apply each toggle immediately without restarting the shared USB/wfb-ng pipeline.
• Keep that pipeline running during ordinary Activity background transitions while either toggle is
 enabled.
Diagram

graph TD
  Menu["Streaming menu"] --> Prefs[("Streaming preferences")] --> Video["Video receiver"]
  Prefs --> OSD["MAVLink parser"]
  Lifecycle["Activity lifecycle"] --> Adapter["USB pipeline"] --> Agg["UDP aggregators"] --> External["Ground station"]
  Agg -->|"streaming off"| Video
  Agg -->|"streaming off"| OSD
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Move the receive pipeline into a foreground service
  • ➕ Supports more reliable streaming when the Activity is backgrounded or evicted.
  • ➖ Requires a broader lifecycle and ownership change than releasing the two ports.

Recommendation: The Activity-level toggles fit the immediate goal without modifying wfb-ng, which already forwards both channels. A foreground service is the better follow-up if guaranteed long-running background streaming becomes a requirement.

Files changed (1) +129 / -5

Enhancement (1) +129 / -5
VideoActivity.javaRelease in-app UDP consumers independently for ground station streaming +129/-5

Release in-app UDP consumers independently for ground station streaming

• Adds persistent Video and Telemetry settings that immediately stop or restart their respective in-app consumers and honor those choices during initialization and resume. While either toggle is enabled, pause and stop leave the shared USB/wfb-ng adapter running so another app can receive its chosen channel.

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

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

qodo-free-for-open-source-projects Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. External apps cannot bind the telemetry port 🐞 Bug ≡ Correctness ⭐ New
Description
setTelemetryStreamingEnabled(true) calls nativeStop(), which signals the MAVLink listener to
exit but never closes its socket bound to port 14550. When telemetry was already running in-app,
that socket remains open after the listener exits, so the ground station cannot bind the port the
toggle is meant to free.
Code

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1239]

+            MavlinkNative.nativeStop(this);
Evidence
The new toggle relies on nativeStop to release the port. The native listener binds an fd to
0.0.0.0:14550, but its stop path only changes the loop signal and returns without closing that fd.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1236-1244]
app/mavlink/src/main/cpp/mavlink.cpp[52-101]
app/mavlink/src/main/cpp/mavlink.cpp[318-325]
app/mavlink/src/main/cpp/mavlink.cpp[377-388]

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

## Issue description
Enabling telemetry streaming stops the listener thread but leaves its UDP socket bound, preventing another app from binding port 14550.
## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1236-1244]
- app/mavlink/src/main/cpp/mavlink.cpp[52-101]
- app/mavlink/src/main/cpp/mavlink.cpp[318-325]
## Recommended Fix
Ensure the native listener closes its socket on every exit path, and complete listener shutdown and socket release before treating the telemetry handoff as complete.

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


2. Telemetry never returns in-app 🐞 Bug ≡ Correctness
Description
setTelemetryStreamingEnabled() calls nativeStop() and later nativeStart(), but the native stop
signal is only incremented and is never reset before a replacement listener starts. Every
enable-then-disable cycle therefore launches a listener that exits immediately, while a rapid cycle
can additionally bind before the old socket releases port 14550.
Code

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[R1239-1243]

+            MavlinkNative.nativeStop(this);
+            handler.removeCallbacks(runnable);
+        } else {
+            MavlinkNative.nativeStart(this);
+            handler.post(runnable);
Evidence
The toggle directly introduces a stop-then-start lifecycle, while the native implementation uses a
persistent global stop signal and detached thread. The receive loop only runs while that signal is
zero, and stopping does not close or join the listener before another thread attempts to bind the
same port.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1236-1244]
app/mavlink/src/main/cpp/mavlink.cpp[57-58]
app/mavlink/src/main/cpp/mavlink.cpp[78-95]
app/mavlink/src/main/cpp/mavlink.cpp[377-388]

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

## Issue description
Make the native MAVLink listener safely reusable so disabling ground-station telemetry reliably restores the in-app telemetry display.
## Issue Context
The new toggle stops and restarts the listener, but `nativeStop()` permanently increments a process-global signal and does not wait for the existing socket thread to finish. `nativeStart()` neither resets that signal nor synchronizes against the previous listener.
## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1236-1244]
- app/mavlink/src/main/cpp/mavlink.cpp[57-58]
- app/mavlink/src/main/cpp/mavlink.cpp[377-388]

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


3. Exiting leaves the radio pipeline running 🐞 Bug ☼ Reliability
Description
The new lifecycle conditions skip stopAdapters() whenever either persisted streaming preference is
enabled, but the activity has no destruction cleanup for the retained native link, USB connection,
thread, or callback timer. Finishing or recreating the activity therefore leaves its old pipeline
holding the adapter and activity context, and reopening constructs another link that attempts to use
the same statically tracked device.
Code

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[R1660-1662]

+        if (!isGroundStationStreamingActive()) {
+            wfbLinkManager.stopAdapters();
+        }
Evidence
Both changed lifecycle branches can retain the adapter after the activity is destroyed, and no
onDestroy() cleanup exists. Adapter membership is static, whereas native links, worker maps, USB
connections, timers, and callbacks belong to individual activity-created WfbNgLink instances, so a
later activity cannot safely adopt the retained instance.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1641-1683]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[367-373]
app/src/main/java/com/openipc/pixelpilot/WfbLinkManager.java[20-25]
app/src/main/java/com/openipc/pixelpilot/WfbLinkManager.java[175-185]
app/wfbngrtl8812/src/main/java/com/openipc/wfbngrtl8812/WfbNgLink.java[33-62]
app/wfbngrtl8812/src/main/java/com/openipc/wfbngrtl8812/WfbNgLink.java[95-134]

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

## Issue description
Preserve streaming during temporary background transitions without orphaning the USB and native pipeline when the activity is actually destroyed.
## Issue Context
The activity now skips adapter shutdown in both pause and stop, while `WfbNgLink` owns per-instance native state, threads, USB connections, and a timer retaining the activity. Add explicit terminal cleanup or move ownership into a lifecycle-appropriate service; distinguish ordinary backgrounding from finishing or recreation.
## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1660-1662]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1676-1680]
- app/wfbngrtl8812/src/main/java/com/openipc/wfbngrtl8812/WfbNgLink.java[53-67]
- app/src/main/java/com/openipc/pixelpilot/WfbLinkManager.java[209-233]

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



Remediation recommended

4. Video handoff leaves recordings stalled 🐞 Bug ≡ Correctness ⭐ New
Description
setVideoStreamingEnabled(true) stops the receivers without stopping an active DVR session or
changing its recording indicator. If the user switches to ground-station video during a recording,
the DVR receives no more decoded frames while its timer and recording UI continue until they
manually stop it.
Code

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[R1222-1224]

+        if (enabled) {
+            videoPlayer.stop();
+            videoPlayer.stopAudio();
Evidence
Native video stop removes both receivers, whereas DVR frames are enqueued from received NALUs.
Recording cleanup and indicator reset occur only in the separate stopDvr path, which the new toggle
does not call.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1220-1229]
app/videonative/src/main/cpp/VideoPlayer.cpp[135-146]
app/videonative/src/main/cpp/VideoPlayer.cpp[190-204]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1448-1504]

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

## Issue description
Switching video to an external ground station stops the source of DVR frames but leaves an active recording and its UI running.
## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1220-1229]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1448-1504]
## Recommended Fix
Finalize any active DVR session before stopping the video receiver, and prevent starting an in-app DVR session while its video source is disabled.

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


5. Video handoff keeps stale detection running 🐞 Bug ➹ Performance ⭐ New
Description
setVideoStreamingEnabled(true) stops video reception without stopping the object-detection loop or
hiding its overlay. With detection enabled, that loop continues sampling the video view and running
inference even though the in-app video source has been disabled.
Code

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[R1222-1224]

+        if (enabled) {
+            videoPlayer.stop();
+            videoPlayer.stopAudio();
Evidence
The toggle stops only the player and audio. The independent detection loop continues while its
enabled flags remain set, obtains bitmaps from the video view, and invokes inference repeatedly.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1220-1229]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[2181-2226]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[2229-2317]

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

## Issue description
Object detection continues consuming resources after video streaming disables the in-app video receiver.
## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1220-1229]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[2181-2226]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[2229-2317]
## Recommended Fix
Stop and clear the detection loop when video streaming is enabled; restart it when in-app video is restored if detection remains configured.

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


6. Video handoff can freeze the settings menu 🐞 Bug ☼ Reliability ⭐ New
Description
setVideoStreamingEnabled(true) calls videoPlayer.stop() directly from the menu callback, and
native stop synchronously joins both receiver threads. If either receiver is still processing a
packet or has not exited its receive loop, the main thread waits for it before the menu interaction
can finish.
Code

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1223]

+            videoPlayer.stop();
Evidence
The added menu callback invokes the setter synchronously. VideoPlayer.stop reaches native stop,
whose UDP and UDS receiver shutdown paths both join their worker threads.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1253-1261]
app/videonative/src/main/java/com/openipc/videonative/VideoPlayer.java[88-101]
app/videonative/src/main/cpp/VideoPlayer.cpp[190-204]
app/videonative/src/main/cpp/UdpReceiver.cpp[57-66]
app/videonative/src/main/cpp/UdsReceiver.cpp[33-38]

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 video-streaming menu action joins native receiver threads on the Android main thread.
## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1220-1229]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1253-1261]
- app/videonative/src/main/cpp/VideoPlayer.cpp[190-204]
## Recommended Fix
Perform potentially blocking receiver teardown on a worker thread, and update the menu state on the main thread once the receiver has stopped and released its port.

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


Grey Divider

Context sources
Review mode: Auto: ⚖️ Balanced: Downgraded extended -> standard: change is below the extended eligibility bar (hunks 5/18, lines 134/200; both must reach the floor). Router rationale: Multiple lifecycle, socket, and native-threading paths create independent defect risks.

Grey Divider

Tip of the day
💡 Did you know, you can move or hide resolved findings to focus on what still needs action

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Previous reviews

Review updated until commit 24d53e9 ⚖️ Balanced

Results up to commit 24d53e9


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


Action required
1. Telemetry never returns in-app 🐞 Bug ≡ Correctness
Description
setTelemetryStreamingEnabled() calls nativeStop() and later nativeStart(), but the native stop
signal is only incremented and is never reset before a replacement listener starts. Every
enable-then-disable cycle therefore launches a listener that exits immediately, while a rapid cycle
can additionally bind before the old socket releases port 14550.
Code

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[R1239-1243]

+            MavlinkNative.nativeStop(this);
+            handler.removeCallbacks(runnable);
+        } else {
+            MavlinkNative.nativeStart(this);
+            handler.post(runnable);
Evidence
The toggle directly introduces a stop-then-start lifecycle, while the native implementation uses a
persistent global stop signal and detached thread. The receive loop only runs while that signal is
zero, and stopping does not close or join the listener before another thread attempts to bind the
same port.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1236-1244]
app/mavlink/src/main/cpp/mavlink.cpp[57-58]
app/mavlink/src/main/cpp/mavlink.cpp[78-95]
app/mavlink/src/main/cpp/mavlink.cpp[377-388]

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

## Issue description
Make the native MAVLink listener safely reusable so disabling ground-station telemetry reliably restores the in-app telemetry display.

## Issue Context
The new toggle stops and restarts the listener, but `nativeStop()` permanently increments a process-global signal and does not wait for the existing socket thread to finish. `nativeStart()` neither resets that signal nor synchronizes against the previous listener.

## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1236-1244]
- app/mavlink/src/main/cpp/mavlink.cpp[57-58]
- app/mavlink/src/main/cpp/mavlink.cpp[377-388]

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


2. Exiting leaves the radio pipeline running 🐞 Bug ☼ Reliability
Description
The new lifecycle conditions skip stopAdapters() whenever either persisted streaming preference is
enabled, but the activity has no destruction cleanup for the retained native link, USB connection,
thread, or callback timer. Finishing or recreating the activity therefore leaves its old pipeline
holding the adapter and activity context, and reopening constructs another link that attempts to use
the same statically tracked device.
Code

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[R1660-1662]

+        if (!isGroundStationStreamingActive()) {
+            wfbLinkManager.stopAdapters();
+        }
Evidence
Both changed lifecycle branches can retain the adapter after the activity is destroyed, and no
onDestroy() cleanup exists. Adapter membership is static, whereas native links, worker maps, USB
connections, timers, and callbacks belong to individual activity-created WfbNgLink instances, so a
later activity cannot safely adopt the retained instance.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1641-1683]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[367-373]
app/src/main/java/com/openipc/pixelpilot/WfbLinkManager.java[20-25]
app/src/main/java/com/openipc/pixelpilot/WfbLinkManager.java[175-185]
app/wfbngrtl8812/src/main/java/com/openipc/wfbngrtl8812/WfbNgLink.java[33-62]
app/wfbngrtl8812/src/main/java/com/openipc/wfbngrtl8812/WfbNgLink.java[95-134]

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

## Issue description
Preserve streaming during temporary background transitions without orphaning the USB and native pipeline when the activity is actually destroyed.

## Issue Context
The activity now skips adapter shutdown in both pause and stop, while `WfbNgLink` owns per-instance native state, threads, USB connections, and a timer retaining the activity. Add explicit terminal cleanup or move ownership into a lifecycle-appropriate service; distinguish ordinary backgrounding from finishing or recreation.

## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1660-1662]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1676-1680]
- app/wfbngrtl8812/src/main/java/com/openipc/wfbngrtl8812/WfbNgLink.java[53-67]
- app/src/main/java/com/openipc/pixelpilot/WfbLinkManager.java[209-233]

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


Context sources
Review mode: ⚖️ Balanced: This changes lifecycle handling, UDP consumers, native MAVLink control, persisted settings, and shared adapter behavior, creating meaningful cross-path and background-execution risks despite being confined to one file.

Grey Divider

Qodo Logo

Comment on lines +1239 to +1243
MavlinkNative.nativeStop(this);
handler.removeCallbacks(runnable);
} else {
MavlinkNative.nativeStart(this);
handler.post(runnable);

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. Telemetry never returns in-app 🐞 Bug ≡ Correctness

setTelemetryStreamingEnabled() calls nativeStop() and later nativeStart(), but the native stop
signal is only incremented and is never reset before a replacement listener starts. Every
enable-then-disable cycle therefore launches a listener that exits immediately, while a rapid cycle
can additionally bind before the old socket releases port 14550.
Agent Prompt
## Issue description
Make the native MAVLink listener safely reusable so disabling ground-station telemetry reliably restores the in-app telemetry display.

## Issue Context
The new toggle stops and restarts the listener, but `nativeStop()` permanently increments a process-global signal and does not wait for the existing socket thread to finish. `nativeStart()` neither resets that signal nor synchronizes against the previous listener.

## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1236-1244]
- app/mavlink/src/main/cpp/mavlink.cpp[57-58]
- app/mavlink/src/main/cpp/mavlink.cpp[377-388]

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

Comment on lines +1660 to +1662
if (!isGroundStationStreamingActive()) {
wfbLinkManager.stopAdapters();
}

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

2. Exiting leaves the radio pipeline running 🐞 Bug ☼ Reliability

The new lifecycle conditions skip stopAdapters() whenever either persisted streaming preference is
enabled, but the activity has no destruction cleanup for the retained native link, USB connection,
thread, or callback timer. Finishing or recreating the activity therefore leaves its old pipeline
holding the adapter and activity context, and reopening constructs another link that attempts to use
the same statically tracked device.
Agent Prompt
## Issue description
Preserve streaming during temporary background transitions without orphaning the USB and native pipeline when the activity is actually destroyed.

## Issue Context
The activity now skips adapter shutdown in both pause and stop, while `WfbNgLink` owns per-instance native state, threads, USB connections, and a timer retaining the activity. Add explicit terminal cleanup or move ownership into a lifecycle-appropriate service; distinguish ordinary backgrounding from finishing or recreation.

## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1660-1662]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1676-1680]
- app/wfbngrtl8812/src/main/java/com/openipc/wfbngrtl8812/WfbNgLink.java[53-67]
- app/src/main/java/com/openipc/pixelpilot/WfbLinkManager.java[209-233]

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

@josephnef josephnef closed this Oct 18, 2026
@josephnef josephnef reopened this Oct 18, 2026
private void setTelemetryStreamingEnabled(boolean enabled) {
getSharedPreferences("general", MODE_PRIVATE).edit().putBoolean(PREF_STREAM_TELEMETRY, enabled).apply();
if (enabled) {
MavlinkNative.nativeStop(this);

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. External apps cannot bind the telemetry port 🐞 Bug ≡ Correctness

setTelemetryStreamingEnabled(true) calls nativeStop(), which signals the MAVLink listener to
exit but never closes its socket bound to port 14550. When telemetry was already running in-app,
that socket remains open after the listener exits, so the ground station cannot bind the port the
toggle is meant to free.
Agent Prompt
## Issue description
Enabling telemetry streaming stops the listener thread but leaves its UDP socket bound, preventing another app from binding port 14550.
## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1236-1244]
- app/mavlink/src/main/cpp/mavlink.cpp[52-101]
- app/mavlink/src/main/cpp/mavlink.cpp[318-325]
## Recommended Fix
Ensure the native listener closes its socket on every exit path, and complete listener shutdown and socket release before treating the telemetry handoff as complete.

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

Comment on lines +1222 to +1224
if (enabled) {
videoPlayer.stop();
videoPlayer.stopAudio();

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

4. Video handoff leaves recordings stalled 🐞 Bug ≡ Correctness

setVideoStreamingEnabled(true) stops the receivers without stopping an active DVR session or
changing its recording indicator. If the user switches to ground-station video during a recording,
the DVR receives no more decoded frames while its timer and recording UI continue until they
manually stop it.
Agent Prompt
## Issue description
Switching video to an external ground station stops the source of DVR frames but leaves an active recording and its UI running.
## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1220-1229]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1448-1504]
## Recommended Fix
Finalize any active DVR session before stopping the video receiver, and prevent starting an in-app DVR session while its video source is disabled.

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

Comment on lines +1222 to +1224
if (enabled) {
videoPlayer.stop();
videoPlayer.stopAudio();

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

5. Video handoff keeps stale detection running 🐞 Bug ➹ Performance

setVideoStreamingEnabled(true) stops video reception without stopping the object-detection loop or
hiding its overlay. With detection enabled, that loop continues sampling the video view and running
inference even though the in-app video source has been disabled.
Agent Prompt
## Issue description
Object detection continues consuming resources after video streaming disables the in-app video receiver.
## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1220-1229]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[2181-2226]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[2229-2317]
## Recommended Fix
Stop and clear the detection loop when video streaming is enabled; restart it when in-app video is restored if detection remains configured.

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

private void setVideoStreamingEnabled(boolean enabled) {
getSharedPreferences("general", MODE_PRIVATE).edit().putBoolean(PREF_STREAM_VIDEO, enabled).apply();
if (enabled) {
videoPlayer.stop();

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

6. Video handoff can freeze the settings menu 🐞 Bug ☼ Reliability

setVideoStreamingEnabled(true) calls videoPlayer.stop() directly from the menu callback, and
native stop synchronously joins both receiver threads. If either receiver is still processing a
packet or has not exited its receive loop, the main thread waits for it before the menu interaction
can finish.
Agent Prompt
## Issue description
The video-streaming menu action joins native receiver threads on the Android main thread.
## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1220-1229]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1253-1261]
- app/videonative/src/main/cpp/VideoPlayer.cpp[190-204]
## Recommended Fix
Perform potentially blocking receiver teardown on a worker thread, and update the menu state on the main thread once the receiver has stopped and released its port.

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

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

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 24d53e9

@josephnef josephnef left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The use case is legitimate and the approach (free the in-app consumers so another app can bind 5600 / 14550, keep the adapter alive across pause) is the right shape. CI is green on the reopened run. A few things need to change before it can merge:

  1. It depends on #124. setTelemetryStreamingEnabled(false) calls MavlinkNative.nativeStart after a nativeStop, but on master mavlink_thread_signal is never reset and the old listener never closes its socket, so telemetry never comes back in-app once toggled off. Qodo's first finding is correct. Merge #124 first and rebase on it.

  2. Finishing the activity leaks the adapter. With either toggle on, onPause and onStop skip stopAdapters(), and there is no onDestroy or isFinishing() path that stops it. Back-swiping out of the app leaves the USB adapter claimed and the wfb-ng threads running until the process dies, and the next launch has to recover from that. Stop the adapters when isFinishing() is true (or in onDestroy), and keep the skip only for the pause/stop that is not a finish.

  3. In-app telemetry dies after the first background anyway. onStop still calls nativeStop unconditionally and nothing restarts the listener in onResume, so with the telemetry toggle off the OSD loses telemetry after the first pause. That is a pre-existing master bug, but this PR now owns the mavlink start/stop logic, so it should restart in onResume under the same isTelemetryStreamingEnabled() gate it adds to setupMavlink().

  4. The comment in onPause mentions a devourer teardown race in RtlJaguarDevice. The submodule has moved a lot since (bb03774 now); if that was the motivation for skipping the stop, please recheck, and either way the comment should not describe a bug in another repo as a reason for app behaviour.

Documented limitation that is fine as-is: without a foreground service the process can still be killed in the background. Worth a line in the README section when you add one.

This branch has not been deployed

No deployments
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.

2 participants