Skip to content

fix: no startup pause from the shared loss burst, and a paused cell gets another try (#148) - #149

Merged
compot-dev merged 3 commits into
v2from
fix/startup-pause
Oct 13, 2026
Merged

compot-dev merged 3 commits into
v2from
fix/startup-pause

Conversation

@compot-dev

Copy link
Copy Markdown
Collaborator

Closes #148.

  • Warm-up: for the first 10 s after the grid starts (DecodePlanner.WARMUP_MS, set by the app; 0 in tests) the planner takes no loss-based decision — every decoder starts at once and loses frames for a second or two. That time counts neither as lossy nor as quiet.
  • Resume: a cell paused as a last resort (Paused: decoder overloaded) gets another try on key frames once there were no losses for resumeWaitMs (30 s, doubled with every pause, at most 10 min) — counted from the last loss, so promotion probes do not hold it back. New action RESUME → QuadCell.resume().
  • Unit tests: lossesDuringTheWarmupDecideNothing, aPausedCellPlaysAgainAfterAQuietPeriod.

Device test (AG-216, CI build 8fd516c, 3×3 + U): 3 cold starts in a row, 40 s each — no cell paused. Before (v2 ea5c093 and #146 builds): slot 8 paused at startup in 4 of 5 starts and stayed paused.

compot-dev and others added 2 commits October 13, 2026 16:46
…d cell gets another try after a quiet period (#148)

Co-Authored-By: Claude <noreply@anthropic.com>
…m the last promotion (#148)

Co-Authored-By: Claude <noreply@anthropic.com>
@compot-dev

Copy link
Copy Markdown
Collaborator Author

/review

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

qodo-free-for-open-source-projects Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. Startup losses can still pause cells 📎 Requirement gap ≡ Correctness
Description
The planner suppresses only the current lossy sample during one global warm-up window, while each
cell reports loss-rate deltas accumulated since its previous sample. When cells settle at different
times or a loss interval crosses the 10-second boundary, transient startup losses enter lossyTimes
after warm-up and can trigger demotion or pausing within the loss window.
Code

android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[R169-170]

+        boolean warming = nowMs - startMs < warmupMs;
+        if (warming) quietSinceMs = nowMs;
Evidence
The checklist requires transient losses immediately after a multi-view grid starts, including
staggered cell settling, to be excluded from overload decisions. The changed planner uses one global
warm-up flag to suppress only the current loss sample, while the cited cell sampling and planner
logic allow startup-derived interval losses to be recorded after warm-up and reach the demotion or
pause thresholds.

Avoid overload pauses caused by startup loss bursts
android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[166-187]
android/app/src/main/java/com/openipc/decoder/Decoder.java[1161-1166]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[314-330]

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 losses can be sampled after the planner-wide warm-up expires because each cell's loss-rate interval is independent and is not reset or discarded at the warm-up boundary. Staggered decoder startup can therefore produce enough post-warm-up samples to trigger overload actions for a transient startup burst.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[166-187]
- android/app/src/main/java/com/openipc/decoder/Decoder.java[1161-1166]
- android/app/src/main/java/com/openipc/decoder/QuadCell.java[314-330]
## Recommended Fix
Ensure loss samples covering each decoder's startup period are discarded rather than counted after the global warm-up expires. Reset or baseline the per-cell loss counters at the warm-up boundary, or otherwise tag and ignore startup-derived samples until each cell has completed its startup settling interval, while continuing to accept losses that occur after startup as overload evidence.

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


2. Selected audio stays muted after recovery ✓ Resolved
Description
QuadCell.resume() restarts a paused cell without restoring the audioSink that stop() cleared.
When the selected audio cell is paused by the planner, its video can return but incoming audio
packets are discarded until the user changes the audio selection.
Code

android/app/src/main/java/com/openipc/decoder/QuadCell.java[R516-521]

+    void resume() {
+        if (pausedReason == null) return;
+        pausedReason = null;
+        problem = null;
+        setKeyFramesOnly(true);
+        start();
Evidence
Pause calls stop, which nulls the sink. The new resume path starts the cell without invoking the
audio-routing method, while packet handling forwards audio only when the sink is non-null.

android/app/src/main/java/com/openipc/decoder/QuadCell.java[232-238]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[509-522]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[575-584]
android/app/src/main/java/com/openipc/decoder/Decoder.java[903-919]
android/app/src/main/java/com/openipc/decoder/Decoder.java[1123-1135]

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

## Issue description
Stopping an overload-paused cell clears its audio sink, and resuming it never restores the selected audio route.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/Decoder.java[1132-1135]
- android/app/src/main/java/com/openipc/decoder/QuadCell.java[515-522]
- android/app/src/main/java/com/openipc/decoder/QuadCell.java[232-238]
## Recommended Fix
Reapply the current quad audio selection after resuming a cell, including its route and audio configuration, without leaving other cells on stale routes.

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



Remediation recommended

3. Old stream callbacks can stall resumed video 🐞 Bug ☼ Reliability
Description
QuadCell.resume() restarts the same cell while an SDP callback from its stopped session can still
reach onSdp() and reset the resumed decoder's state. If the old callback passes its generation
check just before the pause and runs after the new session starts, it can clear the new stream's
queues or close its decoder.
Code

android/app/src/main/java/com/openipc/decoder/QuadCell.java[521]

+        start();
Evidence
The new resume path reuses the callback target after stop. The RTSP generation check and callback
invocation are separate operations, while onSdp mutates shared decoder and stream state without
checking which connection supplied the SDP.

android/app/src/main/java/com/openipc/decoder/QuadCell.java[510-522]
android/app/src/main/java/com/openipc/decoder/RtspSession.java[178-190]
android/app/src/main/java/com/openipc/decoder/RtspSession.java[443-445]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[525-558]

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

## Issue description
Resuming a paused cell reuses its `QuadCell`, but an SDP callback from the stopped RTSP generation can run after the new session starts and reset its decoder state.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/QuadCell.java[515-522]
- android/app/src/main/java/com/openipc/decoder/RtspSession.java[443-445]
## Recommended Fix
Make SDP callback delivery atomic with respect to session stop and generation replacement, or tag callbacks with their generation and reject stale callbacks before they mutate the cell.

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


4. Video stays choppy for ten seconds ✓ Resolved
Description
DecodePlanner.step() returns NONE throughout warmup, suppressing PROMOTE along with loss-based
decisions. New cells start in key-frame-only mode, so their non-key frames remain filtered until the
first planning step after warmup, roughly eleven seconds after grid startup.
Code

android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[R167-170]

+        if (nowMs - startMs < warmupMs) {
+            // Starting decoders lose frames together; that time is neither lossy nor quiet
+            quietSinceMs = nowMs;
+            return Decision.NONE;
Evidence
The warmup return precedes the planner's promotion logic. Cells default to key-frame-only decoding,
which filters non-key frames; the first stats tick is scheduled one second after cell creation.

android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[164-170]
android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[248-292]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[74-75]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[610-618]
android/app/src/main/java/com/openipc/decoder/Decoder.java[881-886]
android/app/src/main/java/com/openipc/decoder/Decoder.java[1023-1029]

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 warmup early return prevents safe promotions, leaving newly started cells on key frames for the whole interval.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[164-171]
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[248-292]
## Recommended Fix
During warmup, ignore loss samples and prohibit loss-based demotion or pause, but continue evaluating eligible promotions. Add a test that verifies a known, fitting cell can be promoted before warmup ends.

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


5. Slides exit ignores overload for ten seconds ✓ Resolved
Description
newPlanner() enables warmup every time it creates a planner, including when setSlides(false)
replaces the planner for an already-running grid. Returning from Slides therefore discards
decoder-loss samples for ten seconds even though the decoders did not start together again.
Code

android/app/src/main/java/com/openipc/decoder/Decoder.java[R1056-1057]

+        // The decoders start together and lose frames for a moment (#148)
+        p.setWarmup(DecodePlanner.WARMUP_MS);
Evidence
Exiting Slides calls newPlanner without recreating the cells. The newPlanner change unconditionally
enables warmup, and step returns before it records losses while warmup is active.

android/app/src/main/java/com/openipc/decoder/Decoder.java[379-392]
android/app/src/main/java/com/openipc/decoder/Decoder.java[1053-1058]
android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[164-170]
android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[181-188]

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

## Issue description
Leaving Slides creates a fresh planner and unintentionally enables the startup loss guard on decoders that are already running.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/Decoder.java[379-392]
- android/app/src/main/java/com/openipc/decoder/Decoder.java[1023-1058]
## Recommended Fix
Configure warmup only when creating the planner for a newly started grid, not when recreating it on exit from Slides. Test that loss samples are processed immediately after that mode switch.

ⓘ 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 these tips off under Display > Comment parts

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread android/app/src/main/java/com/openipc/decoder/DecodePlanner.java Outdated
Comment thread android/app/src/main/java/com/openipc/decoder/QuadCell.java
Comment thread android/app/src/main/java/com/openipc/decoder/Decoder.java Outdated
…fit go on), only when the grid starts, and a resumed cell gets its audio route back

Co-Authored-By: Claude <noreply@anthropic.com>
@compot-dev

Copy link
Copy Markdown
Collaborator Author

/review

Comment on lines +169 to +170
boolean warming = nowMs - startMs < warmupMs;
if (warming) quietSinceMs = nowMs;

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. Startup losses can still pause cells 📎 Requirement gap ≡ Correctness

The planner suppresses only the current lossy sample during one global warm-up window, while each
cell reports loss-rate deltas accumulated since its previous sample. When cells settle at different
times or a loss interval crosses the 10-second boundary, transient startup losses enter lossyTimes
after warm-up and can trigger demotion or pausing within the loss window.
Agent Prompt
## Issue description
Startup losses can be sampled after the planner-wide warm-up expires because each cell's loss-rate interval is independent and is not reset or discarded at the warm-up boundary. Staggered decoder startup can therefore produce enough post-warm-up samples to trigger overload actions for a transient startup burst.

## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[166-187]
- android/app/src/main/java/com/openipc/decoder/Decoder.java[1161-1166]
- android/app/src/main/java/com/openipc/decoder/QuadCell.java[314-330]

## Recommended Fix
Ensure loss samples covering each decoder's startup period are discarded rather than counted after the global warm-up expires. Reset or baseline the per-cell loss counters at the warm-up boundary, or otherwise tag and ignore startup-derived samples until each cell has completed its startup settling interval, while continuing to accept losses that occur after startup as overload evidence.

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

pausedReason = null;
problem = null;
setKeyFramesOnly(true);
start();

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

3. Old stream callbacks can stall resumed video 🐞 Bug ☼ Reliability

QuadCell.resume() restarts the same cell while an SDP callback from its stopped session can still
reach onSdp() and reset the resumed decoder's state. If the old callback passes its generation
check just before the pause and runs after the new session starts, it can clear the new stream's
queues or close its decoder.
Agent Prompt
## Issue description
Resuming a paused cell reuses its `QuadCell`, but an SDP callback from the stopped RTSP generation can run after the new session starts and reset its decoder state.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/QuadCell.java[515-522]
- android/app/src/main/java/com/openipc/decoder/RtspSession.java[443-445]
## Recommended Fix
Make SDP callback delivery atomic with respect to session stop and generation replacement, or tag callbacks with their generation and reject stale callbacks before they mutate the cell.

ⓘ 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 bb7dc58

@compot-dev

Copy link
Copy Markdown
Collaborator Author

Qodo on bb7dc58:

  • Old stream callbacks can stall resumed video — declined: a cell is resumed only after resumeWaitMs (≥ 30 s, doubled per pause) without losses, and stop() bumps the session generation when it pauses. A stale onSdp would have to sit between its generation check and the call for more than 30 s; the session delivers it right after the check on its network thread.
  • Startup losses can still pause cells — declined: a cell's loss sample covers one second, so at most one sample per cell straddles the warm-up boundary, while a pause needs LOSS_SAMPLES (3) lossy samples within 10 s. A cell that connects late and loses frames then is a real load change, not the shared startup burst; if that ever pauses it, the resume path brings it back.

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

Copy link
Copy Markdown

Qodo on bb7dc58:

  • Old stream callbacks can stall resumed video — declined: a cell is resumed only after resumeWaitMs (≥ 30 s, doubled per pause) without losses, and stop() bumps the session generation when it pauses. A stale onSdp would have to sit between its generation check and the call for more than 30 s; the session delivers it right after the check on its network thread.
  • Startup losses can still pause cells — declined: a cell's loss sample covers one second, so at most one sample per cell straddles the warm-up boundary, while a pause needs LOSS_SAMPLES (3) lossy samples within 10 s. A cell that connects late and loses frames then is a real load change, not the shared startup burst; if that ever pauses it, the resume path brings it back.

The rationale does not fully close either open concern:

  • finding 3 — Old stream callbacks can stall resumed video: The risk is not that an SDP callback waits 30 seconds. It can pass the generation check immediately before stop() invalidates the session, then execute onSdp() after resume() starts the replacement session. The generation check and callback delivery are separate, so the timing remains racy regardless of resumeWaitMs.
  • finding 1 — Startup losses can still pause cells: A single cell may contribute only one boundary-straddling sample, but staggered cells can contribute separate startup-derived samples at different times. Those samples are aggregated in lossyTimes, so three samples within the loss window can still trigger a pause. The resume path mitigates the consequence but does not ensure startup losses are excluded as required.

The available review context does not include management URLs for these findings.

@compot-dev
compot-dev merged commit ff3667f into v2 Oct 13, 2026
4 checks passed
@compot-dev
compot-dev deleted the fix/startup-pause branch October 13, 2026 17:27
compot-dev added a commit that referenced this pull request Oct 15, 2026
…fit go on), only when the grid starts, and a resumed cell gets its audio route back

Co-Authored-By: Claude <noreply@anthropic.com>
compot-dev added a commit that referenced this pull request Oct 15, 2026
fix: no startup pause from the shared loss burst, and a paused cell gets another try (#148)
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.

Multi-view: the startup loss burst pauses the last cell for good ("Paused: decoder overloaded")

1 participant