Skip to content

Restart the app when the low latency toggle changes - #121

Merged
vertexodessa merged 2 commits into
masterfrom
fix/low-latency-toggle-restart
Sep 2, 2026
Merged

Restart the app when the low latency toggle changes#121
vertexodessa merged 2 commits into
masterfrom
fix/low-latency-toggle-restart

Conversation

@vertexodessa

Copy link
Copy Markdown
Collaborator

Follow-up to #113.

The low-latency keys are written into the AMediaFormat when the codec is configured, and the codec is only torn down when its surface goes away (VideoDecoder::setOutputSurface(nullptr)). VideoPlayer::stop()/start() recycle the UDP/UDS receivers and leave the codec alone, and a channel change never touches the surface. So the toast "applies on next video start" only came true after backgrounding or restarting the app. A pilot who turned the option off in the field because of decoder artifacts and then changed channel kept flying on the old configuration.

This does what the VR mode toggle does: write the preference and resetApp(). The preference is read on startup, so the runtime setLowLatency() call in the click handler goes away.

Two small leftovers from #113 in the same area:

  • VideoPlayer.setLowLatency() now calls verifyApplicationThread() like every other setter in that class.
  • The commented-out AMEDIAFORMAT_KEY_PRIORITY line in h264_configureAMediaFormat is dropped; writeAndroidPerformanceParams() sets the real key.

Not changed here: resetApp() calls System.exit(0) and does not finalize an active DVR recording. That is pre-existing with the VR toggle and worth its own fix.

Compile tested: assembleDebug, arm64-v8a and armeabi-v7a.

The low-latency keys from #113 are written into the AMediaFormat when the
codec is configured, and the codec is only torn down when its surface goes
away. VideoPlayer stop()/start() recycle the UDP receivers and leave the
codec alone, and a channel change never touches the surface. So the toast
"applies on next video start" promised something that only happened after
backgrounding the app or restarting it; a pilot who turned the option off
in the field and changed channel kept flying on the old configuration.

Do what the VR mode toggle does and restart the app. The preference is
read on startup, so the runtime setLowLatency() call in the click handler
goes away.

Two leftovers from #113 in the same area: setLowLatency() now runs
verifyApplicationThread() like every other VideoPlayer setter, and the
commented-out AMEDIAFORMAT_KEY_PRIORITY line in h264_configureAMediaFormat
is dropped, since writeAndroidPerformanceParams() sets the real key.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Restart app when low-latency decoder setting changes

🐞 Bug fix 🕐 10-20 Minutes

Grey Divider

AI Description

• Restart the app after low-latency changes so MediaCodec receives the new configuration.
• Remove misleading runtime updates and next-video-start feedback.
• Enforce application-thread access and remove a redundant priority-key comment.
Diagram

sequenceDiagram
    actor Pilot
    participant Menu as Video Menu
    participant Prefs as Preferences
    participant App as App Lifecycle
    participant Player as VideoPlayer
    participant Decoder as Native Decoder
    participant Codec as MediaCodec
    Pilot->>Menu: Toggle low latency
    Menu->>Prefs: Persist setting
    Menu->>App: resetApp()
    App->>Prefs: Read on startup
    Prefs-->>App: Setting value
    App->>Player: setLowLatency(value)
    Player->>Decoder: Store native option
    Decoder->>Codec: Configure latency keys
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Recreate the decoder in place
  • ➕ Applies the setting without terminating the application.
  • ➕ Could preserve unrelated state and allow DVR recording to be finalized safely.
  • ➖ Requires coordinated codec, surface, and receiver teardown and reconstruction.
  • ➖ Introduces substantially more lifecycle risk than the existing restart pattern.

Recommendation: Use the PR's restart-based approach for this focused fix because it matches the established VR-toggle behavior and guarantees codec reconstruction. A dedicated in-place decoder reconfiguration path would provide a better long-term experience, especially during DVR recording, but should be implemented separately with explicit lifecycle and resource-management coverage.

Files changed (3) +7 / -4

Bug fix (2) +7 / -3
VideoActivity.javaRestart after changing low-latency mode +6/-3

Restart after changing low-latency mode

• Persists the low-latency preference and restarts the app so the next MediaCodec instance uses the new configuration. Removes the ineffective runtime setter call and misleading toast, while documenting why a full restart is required.

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

VideoPlayer.javaEnforce low-latency setter thread affinity +1/-0

Enforce low-latency setter thread affinity

• Adds application-thread verification to setLowLatency, aligning it with other VideoPlayer configuration setters before invoking native code.

app/videonative/src/main/java/com/openipc/videonative/VideoPlayer.java

Refactor (1) +0 / -1
AndroidMediaFormatHelper.hRemove stale priority-key example +0/-1

Remove stale priority-key example

• Deletes a commented-out priority-key assignment because the active performance-parameter helper already writes the realtime priority key.

app/videonative/src/main/cpp/helper/AndroidMediaFormatHelper.h

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

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

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Toggle preference may revert ✓ Resolved 🐞 Bug ☼ Reliability
Description
The handler calls resetApp() immediately after asynchronously applying the preference, and
System.exit(0) can terminate the process before the disk write completes. Startup can therefore
read the previous value and continue using the old decoder configuration.
Code

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

+            resetApp();
Evidence
The handler asynchronously applies the value at lines 703-704 and immediately reaches the newly
added restart at line 707. resetApp() calls System.exit(0), while initialization reads this
preference to configure the native player; the analogous VR setter synchronously commits its
preference before restarting.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[156-170]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[204-212]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[358-362]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[700-707]

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 low-latency preference is written with asynchronous `SharedPreferences.Editor.apply()` immediately before `resetApp()` terminates the process. Ensure persistence completes successfully before restarting so startup cannot read the previous value.
## Issue Context
The existing VR restart path uses synchronous `commit()`. The low-latency setting is read during `initializeVideoPlayers()` in the newly launched process.
## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[700-707]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[204-212]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[358-362]

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


2. Restart corrupts active recording ✓ Resolved 🐞 Bug ≡ Correctness
Description
Toggling low latency while DVR recording is active calls resetApp() without stopping the DVR
writer. The forced process exit bypasses the native join and MP4 finalization path, potentially
leaving the recording incomplete or unplayable.
Code

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

+            resetApp();
Evidence
The changed handler reaches a forced process exit through resetApp(). Lifecycle shutdown only
stops the receiver/player, whereas orderly DVR shutdown is a separate path that invokes the native
stop operation; the native writer finalizes the MP4 only after its processing loop exits normally.

app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[204-212]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[700-707]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1333-1357]
app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1488-1515]
app/videonative/src/main/cpp/VideoPlayer.cpp[98-105]
app/videonative/src/main/cpp/VideoPlayer.h[115-126]

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 newly added low-latency restart can force-exit while DVR recording is active. Stop and finalize any active recording before restarting, or prevent the restart until recording has been stopped safely.
## Issue Context
The activity lifecycle does not call `stopDvr()`. Native DVR shutdown must signal and join the processing thread so its MP4 close/finalization code runs before process termination.
## Fix Focus Areas
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[700-707]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1333-1357]
- app/src/main/java/com/openipc/pixelpilot/VideoActivity.java[1488-1515]
- app/videonative/src/main/cpp/VideoPlayer.cpp[98-105]
- app/videonative/src/main/cpp/VideoPlayer.h[115-126]

ⓘ 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 turn on the rule miner and Qodo learns your standards from review history

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread app/src/main/java/com/openipc/pixelpilot/VideoActivity.java
Comment thread app/src/main/java/com/openipc/pixelpilot/VideoActivity.java
Two review findings on the restart path.

The preference was written with apply(), and resetApp() ends the process
with System.exit() a few lines later. apply() flushes to disk on a
background thread, so the new process could read the old value and come
up with the configuration the pilot just switched away from. The VR mode
toggle already uses commit() for the same reason; do the same here.

resetApp() also killed an active DVR recording without closing the MP4:
System.exit() skips every lifecycle callback, and the file is only
finalized when the DVR thread exits. stopDvr() joins that thread, so call
it first. This covers the VR mode toggle too, which had the same hole.
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