Skip to content

feat: adaptive multi-view decoding (full rate or key frames per cell) and D for the debug text (#85) - #86

Merged
compot-dev merged 10 commits into
v2from
feat/adaptive-decoding
Oct 13, 2026
Merged

compot-dev merged 10 commits into
v2from
feat/adaptive-decoding

Conversation

@compot-dev

Copy link
Copy Markdown
Collaborator

Closes #85. Also fixes Qodo's post-merge findings on #84 (see the reply there).

Adaptive decoding: every camera stays on screen.

  • Cells start on key frames only (IDR/IRAP + parameter sets), which gives resolution and fps within a second or two at a fraction of the load. Corner text: WxH codec key frames · N fps.
  • DecodePlanner (pure Java, tested) promotes cells to full rate in priority order (the audio cell, then slot order) while the estimated load (macroblocks/s) fits the capacity. A promotion waits for the next key frame.
  • Decoder losses (queue overflow, no input buffer) for 3 s demote the lowest-priority playing full cell and lower the capacity. A load that plays 30 s without losses is proven; after a quiet period one more cell is probed, but never up to a load that failed before.
  • The capacity starts from the decoder's declared budget and the learnt value is stored per device (settings decoder_capacity). A pause remains only as a last resort, if even key frames are lost.

D in the menu (after U): debug text on/off, stored. Off: only cells that need attention show text (not connected, errors, decoder limit, paused, key frames).

Fixes from Qodo on #84: a decoder start failure shows Decoder error; decoders of a type start one at a time until the limit is known; the instance slot is returned after release(); a decoder limit message is not replaced by a connection error; only playing cells are demoted or paused.

READMEs (EN/RU/ZH): D in the menu, the adaptive grid, the measured AG-216 budget. Tests: DecodePlannerTest, CellStatsTest. Device test with the CI build follows.

@compot-dev

Copy link
Copy Markdown
Collaborator Author

/review

@compot-dev

Copy link
Copy Markdown
Collaborator Author

Device test on the AG-216 (CI builds), 3×3 grid with 8 cameras on main streams

Build Behaviour
first (d4b7d68) 1 camera at full rate, 7 on key frames, every camera on screen. But the second camera was re-promoted about once a minute and failed each time, and the learnt capacity jumped (302k → 507k blocks/s): noisy per-second fps, key-frame cells not counted in the load, and a failure could raise the capacity
95e4028 smoothed rates (~3 s), key-frame cells count in the load, a failure never raises the capacity: capacity steady (533k), still a re-probe about once a minute
5b3e1a5 probes stay 10 % below a failed load and back off (30 s → 10 min): one probe at start, then no repeats in 5 min

Final state: Quad-0: 2592×1536 H.264 full rate, the other 7 cells key frames · 0.1–1.0 fps (by GOP), no pauses, no decoder errors. Corner text with D off shows only the key-frame and status lines, as intended.

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

qodo-free-for-open-source-projects Bot commented Oct 12, 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. One offline camera keeps all lower cells on key frames ✓ Resolved
Description
DecodePlanner.step() returns Decision.NONE at the first non-paused key-frame cell with unknown
load, without checking whether that cell is playing. Because QuadCell.knowsLoad() requires a
decoder output width, an offline camera or a cell stopped at the decoder instance limit can remain
unknown indefinitely, blocking lower-priority connected cells from full-rate promotion—and, if the
blocker is the audio cell or slot 1, blocking every cell.
Code

android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[R156-158]

+        for (Cell c : cells) {
+            if (c.full || c.paused) continue;
+            if (!c.known) return Decision.NONE; // keep the priority order: wait for it
Evidence
The promotion loop exits at an unknown, non-paused cell even though the integration records whether
that cell is playing. knowsLoad() requires decoder.outWidth() > 0 and inAvg > 0, which a cell
without a running decoder cannot provide; plannerCell derives paused only from isPaused(), so
an offline or instance-limited cell is not skipped on that basis. The AG-216 case with eight H.265
cameras leaves four cells over the decoder instance limit, making this condition expected there.

android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[155-170]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[311-314]
android/app/src/main/java/com/openipc/decoder/Decoder.java[969-978]
android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[155-168]
android/app/src/main/java/com/openipc/decoder/Decoder.java[969-977]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[346-349]

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

## Issue description
`DecodePlanner.step()` stops promotions at an unknown-load key-frame cell even when it is not playing. Offline cameras and cells without a decoder because of the instance limit can therefore block connected, lower-priority cells from full-rate decoding.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[155-170]
- android/app/src/main/java/com/openipc/decoder/Decoder.java[969-977]
## Recommended Fix
In the promotion loop, skip cells with `!c.playing` and exclude them from promotion priority and load accounting, while preserving priority among playing cells. Return `Decision.NONE` to wait only when a playing cell's load is not yet known. Optionally add a timeout for skipping a playing cell whose load remains unknown. Add a `DecodePlannerTest` case with an unknown, non-playing high-priority cell and a connected later cell.

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


2. A full-rate cell can flip between full rate and key frames forever 🐞 Bug ≡ Correctness
Description
When a promotion fits and comes after 30 s without losses, the next lossless sample sets proven to
the new load B, because quietSinceMs is reset only for probes and not for fitting promotions. If B
then causes losses, the demotion computes capacity = max(proven, …) = B, so the same cell fits
again and is promoted again after SETTLE_MS, and the cycle repeats every few seconds while B is
saved as the learnt capacity.
Code

android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[R119-121]

+            if (nowMs - quietSinceMs >= PROBE_QUIET_MS && load > proven) {
+                proven = load;
+                probeWaitMs = PROBE_QUIET_MS;
Evidence
Line 119 only checks that the quiet period started 30 s ago. It does not check that the current load
itself has played for 30 s. Losses need LOSS_SAMPLES (3) samples to trigger a demotion, but proven
is raised on the first sample after the promotion. At line 139 the capacity floor is proven, so
the failure cannot push capacity below B. Line 160 (after <= capacity) then promotes the same cell
again. Decoder.planDecoding persists the learnt capacity, so the bad value survives restarts.

android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[113-141]
android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[159-166]
android/app/src/main/java/com/openipc/decoder/Decoder.java[962-966]

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

## Issue description
`proven` is set to a load that has only just been reached, because the 30 s quiet window is measured from the last loss and not from the last load change. A demotion then cannot lower capacity below that unproven load, so the planner promotes and demotes the same cell over and over.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[113-127]
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[159-166]
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[179-182]
## Recommended Fix
Measure 'proven' from the later of the last loss and the last change. For example, require `nowMs - max(quietSinceMs, lastChangeMs) >= PROBE_QUIET_MS`, or reset `quietSinceMs` in `change()` for every PROMOTE. Also, on DEMOTE, lower `proven` when it is >= the failing load. Add a test where a fitting promotion after 30 s of quiet causes losses, and assert that the cell is not promoted again.

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



Remediation recommended

3. The last working camera can be paused needlessly 📎 Requirement gap ≡ Correctness
Description
DecodePlanner.step() requires a pause candidate to be playing, but its countActive(cells) > 1
guard counts every non-paused cell, including cells that are connecting, buffering, or disconnected.
When the only playing cell loses frames even on key frames, any such inactive slot can make the
fallback pause that sole functioning camera despite there being no other stream contributing decode
load or providing a picture.
Code

android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[R145-149]

+            for (int i = cells.size() - 1; i >= 0; i--) {
+                Cell c = cells.get(i);
+                if (!c.paused && c.playing && countActive(cells) > 1) {
+                    return change(nowMs, Action.PAUSE, c.index);
+                }
Evidence
The pause fallback at DecodePlanner.java lines 144–150 requires c.playing for the candidate but
uses countActive(cells) > 1 to decide whether pausing is allowed. At lines 173–177,
countActive() excludes only paused cells, so it includes connecting, buffering, and disconnected
slots; the integration supplies a separate playing flag based on connection and decoder state.
Thus the guard can treat an inactive slot as an alternative to the only playing camera, contrary to
the stated last-resort requirement.

Keep overloaded cameras visible using key-frame decoding
android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[144-177]
android/app/src/main/java/com/openipc/decoder/Decoder.java[969-977]
android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[144-149]
android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[173-176]
android/app/src/main/java/com/openipc/decoder/Decoder.java[972-976]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[346-349]

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 pause-as-last-resort fallback can pause the sole playing camera when another configured cell is non-paused but not playing, such as a connecting, buffering, or disconnected cell. The guard should reflect streams actually playing rather than all non-paused slots.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[144-150]
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[173-177]
## Recommended Fix
Change `countActive(List<Cell>)` to count only cells where `c.playing` is true and `c.paused` is false, or use an equivalent playing-cell count at the pause-fallback call site. Add a test with one lossy playing key-frame cell and one disconnected cell to verify that the sole playing cell is not paused.

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


4. Lost key frames can go undetected ✓ Resolved
Description
plannerCell() requires at least two decoder drops per second before it marks a cell lossy. A
key-frame-only stream that drops every key frame at an interval of one second or longer never meets
that threshold, so the planner cannot take its last-resort pause action.
Code

android/app/src/main/java/com/openipc/decoder/Decoder.java[R970-971]

+        boolean lossy = c.isSettled(now) && c.inFps() >= 5
+                && c.lostFps() >= Math.max(2f, 0.1f * c.inFps());
Evidence
The planner receives a lossy flag only when the sampled drop rate reaches `max(2, 10% of input
fps)`; the key-frame path admits only parameter sets and key frames, while the planner's pause
fallback requires consecutive lossy samples.

android/app/src/main/java/com/openipc/decoder/Decoder.java[969-973]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[269-275]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[405-412]
android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[130-150]

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 two-drops-per-second threshold cannot detect loss of every key frame on streams with a one-second-or-longer key-frame interval.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/Decoder.java[969-973]
- android/app/src/main/java/com/openipc/decoder/QuadCell.java[265-275]
## Recommended Fix
Use a loss signal or sampling window suitable for key-frame-only cells, while retaining protection against isolated full-rate drops. Test a cell that loses one key frame per second.

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


5. Restarting retries a load that failed ✓ Resolved
Description
newPlanner() restores the learned capacity but creates a planner whose failedLoad is back at its
unrestricted default. After a restart and a quiet period, the probe check can promote a cell to the
same load that previously caused decoder losses, because only the capacity was persisted.
Code

android/app/src/main/java/com/openipc/decoder/Decoder.java[R909-914]

+    private DecodePlanner newPlanner() {
+        long learnt = getSharedPreferences("settings", MODE_PRIVATE)
+                .getLong(PREF_DECODER_CAPACITY, 0);
+        if (learnt > 0) return new DecodePlanner(learnt, true);
+        long declared = MediaCodecManager.declaredBlocksPerSec;
+        return new DecodePlanner(declared > 0 ? declared : DEFAULT_CAPACITY, false);
Evidence
A loss records failedLoad, and probing checks it; reconstruction restores only capacity, while the
new planner initializes failedLoad to Long.MAX_VALUE.

android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[73-88]
android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[130-141]
android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[159-166]
android/app/src/main/java/com/openipc/decoder/Decoder.java[909-914]
android/app/src/main/java/com/openipc/decoder/Decoder.java[962-965]

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

## Issue description
A restored capacity loses the failed-load boundary, allowing the next session to probe a configuration already known to lose frames.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/Decoder.java[909-914]
- android/app/src/main/java/com/openipc/decoder/Decoder.java[962-965]
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[73-88]
## Recommended Fix
Persist and restore the failed-load boundary alongside capacity, or derive a conservative probe ceiling from the saved failure state. Test a failed probe across planner reconstruction.

ⓘ 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 on lines +145 to +149
for (int i = cells.size() - 1; i >= 0; i--) {
Cell c = cells.get(i);
if (!c.paused && c.playing && countActive(cells) > 1) {
return change(nowMs, Action.PAUSE, c.index);
}

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. The last working camera can be paused needlessly 📎 Requirement gap ≡ Correctness

DecodePlanner.step() requires a pause candidate to be playing, but its countActive(cells) > 1
guard counts every non-paused cell, including cells that are connecting, buffering, or disconnected.
When the only playing cell loses frames even on key frames, any such inactive slot can make the
fallback pause that sole functioning camera despite there being no other stream contributing decode
load or providing a picture.
Agent Prompt
## Issue description
The pause-as-last-resort fallback can pause the sole playing camera when another configured cell is non-paused but not playing, such as a connecting, buffering, or disconnected cell. The guard should reflect streams actually playing rather than all non-paused slots.

## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[144-150]
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[173-177]

## Recommended Fix
Change `countActive(List<Cell>)` to count only cells where `c.playing` is true and `c.paused` is false, or use an equivalent playing-cell count at the pause-fallback call site. Add a test with one lossy playing key-frame cell and one disconnected cell to verify that the sole playing cell is not paused.

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

Comment thread android/app/src/main/java/com/openipc/decoder/Decoder.java Outdated
Comment thread android/app/src/main/java/com/openipc/decoder/Decoder.java Outdated
Comment thread android/app/src/main/java/com/openipc/decoder/DecodePlanner.java Outdated
Comment on lines +119 to +121
if (nowMs - quietSinceMs >= PROBE_QUIET_MS && load > proven) {
proven = load;
probeWaitMs = PROBE_QUIET_MS;

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. A full-rate cell can flip between full rate and key frames forever 🐞 Bug ≡ Correctness

When a promotion fits and comes after 30 s without losses, the next lossless sample sets proven to
the new load B, because quietSinceMs is reset only for probes and not for fitting promotions. If B
then causes losses, the demotion computes capacity = max(proven, …) = B, so the same cell fits
again and is promoted again after SETTLE_MS, and the cycle repeats every few seconds while B is
saved as the learnt capacity.
Agent Prompt
## Issue description
`proven` is set to a load that has only just been reached, because the 30 s quiet window is measured from the last loss and not from the last load change. A demotion then cannot lower capacity below that unproven load, so the planner promotes and demotes the same cell over and over.

## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[113-127]
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[159-166]
- android/app/src/main/java/com/openipc/decoder/DecodePlanner.java[179-182]

## Recommended Fix
Measure 'proven' from the later of the last loss and the last change. For example, require `nowMs - max(quietSinceMs, lastChangeMs) >= PROBE_QUIET_MS`, or reset `quietSinceMs` in `change()` for every PROMOTE. Also, on DEMOTE, lower `proven` when it is >= the failing load. Add a test where a fitting promotion after 30 s of quiet causes losses, and assert that the cell is not promoted again.

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

@compot-dev

Copy link
Copy Markdown
Collaborator Author

All five Qodo findings are accepted and listed in #85; they will be fixed in this PR before merging.

compot-dev added a commit that referenced this pull request Oct 13, 2026
…data, U view, write deadline

- IPv6 site-local (fec0::/10) is no longer a local client: isSiteLocalAddress() counts
  for IPv4 only.
- /log is served from LogBuffer, a ring of the last 500 redacted lines fed by a logcat
  reader that follows the app's own log for the life of the process.
- Each cell reports behind_s (seconds in a row the decoder made it lose frames), with
  overload_after_s for the threshold (#83). Full rate / key frames comes with #86.
- In the U view the JSON has a "udp" object instead of the last single-view state.
- A request has a 5 s deadline, writing included: a client that stops reading has its
  socket closed and cannot hold a worker.
@compot-dev
compot-dev force-pushed the feat/adaptive-decoding branch from 5b3e1a5 to 19dd5a9 Compare October 13, 2026 09:01
compot-dev added a commit that referenced this pull request Oct 13, 2026
…d and stored, last cell kept, key-frame losses detected; decoder state in stats.json, debug page addresses with D
compot-dev added a commit that referenced this pull request Oct 13, 2026
…d and stored, last cell kept, key-frame losses detected; decoder state in stats.json, debug page addresses with D
@compot-dev
compot-dev force-pushed the feat/adaptive-decoding branch from 19dd5a9 to d40650f Compare October 13, 2026 09:16
compot-dev added a commit that referenced this pull request Oct 13, 2026
…e lowers a proven load at or above it (Qodo on #86)
@compot-dev

Copy link
Copy Markdown
Collaborator Author

Qodo review of #86 — decisions (all five accepted, fixed after rebasing on v2):

  1. One offline camera keeps all lower cells on key frames — fixed: cells that are not playing are skipped; a playing cell with an unknown load is waited for at most 10 s. Device: Majestic stopped on slot 1 (.247) → slot 2 went to full rate at once; started again → slot 1 back to full rate.
  2. A full-rate cell can flip between full rate and key frames forever — fixed: every promotion stays below failedLoad × 0.9; every promotion starts a new quiet period, so a load counts as proven only after it has itself played 30 s without losses; a failure lowers a proven load at or above it. Test aLoadJustReachedIsNotProvenAndTheCellIsNotPromotedAgain.
  3. The last working camera can be paused needlessly — fixed: the last-resort pause counts only playing cells and never pauses the last one (countPlaying). Test theLastPlayingCellIsNeverPaused.
  4. Lost key frames can go undetected — fixed: on key frames every decoder loss counts, and lossy samples are counted over a 10 s window, not in a row. Test sparseLossesOfKeyFrameCellsAreDetected.
  5. Restarting retries a load that failed — fixed: the failed load is stored with the learnt capacity (decoder_failed_load_v2); a failed load is retried only after an hour without losses. Values learnt before Video: a NAL unit is dropped when the decoder has no free input buffer within 5 ms #89 (false losses) are discarded (_v2 keys).

Also in this PR now: /stats.json has decoder (capacity, learnt, failed_load) and per cell decode and load; with D on, the debug page addresses are shown in the corner; a key-frame cell counts as showing video for 10 s after a frame (no blinking on the debug page).
Depends on #106 (it is the first commit here): without it, NAL units dropped after a 5 ms wait looked like overload.

…85)

- Every camera stays on screen: cells start on key frames only (IDR/IRAP and parameter
  sets), then are promoted to full rate in priority order (the audio cell, then slot
  order) while the estimated load (macroblocks/s) fits the capacity. Promotion waits for
  the next key frame. Decoder losses (queue overflow, no input buffer) for 3 s demote the
  lowest-priority full cell and lower the capacity; a load that plays 30 s without losses
  is proven, and a probe up to 25 % above the capacity is tried. Pausing remains only as a
  last resort when even key frames are lost.
- DecodePlanner holds this logic (pure Java, tested). The capacity starts from the
  decoder's declared budget (getSupportedFrameRatesFor 1080p) and the learnt value is
  stored per device (settings 'decoder_capacity').
- Corner text: 'WxH codec key frames · N fps' for key-frame cells.
- D in the menu after U turns the debug text on/off (stored). Off: only cells that need
  attention (not connected, errors, limits, paused, key frames) show text.
- READMEs: D in the menu table, adaptive grid and the measured AG-216 budget.
- Tests: DecodePlannerTest; CellStatsTest updated (key-frame start, key NAL types).
… slots freed late

- A decoder that fails to start is reported to the cell ('Decoder error') instead of
  showing normal zero-fps stats
- While a codec's instance limit is unknown, its decoders start one at a time, so
  concurrent cells cannot exceed the limit before it is learnt (Android 5 and errors
  count as unlimited)
- The instance slot is given back only after the codec is released, so a waiting cell
  cannot start while the device still holds the maximum
- A decoder limit message is not replaced by a connection error
- Only playing cells are demoted or paused (#85 planner)
…ot by a fixed margin

A fixed 25 % margin kept a box that is stronger than declared from ever trying a
second 1080p30 camera (CI test). Probes now go up to any load below the smallest one at
which the decoder lost frames, so a failed configuration is not retried every 30 s.
…er raise capacity on failure (#85)

Device test: the second camera was promoted on a per-second fps that jumped, the load
ignored what the seven key-frame cells cost, and a failure at a higher estimated load
raised the learnt capacity (302k -> 507k blocks/s).
- Rates are smoothed over about 3 s for planning and for the key-frame display
- Key-frame cells add their measured cost to the load; a promotion replaces it
- After losses the capacity can only go down (never below a proven load)
…#85)

Device test: the second camera was re-probed about once a minute, because the noisy load
estimate dipped just under the failed one, and failed each time (a short glitch). Probes
now stay 10 % below the smallest failed load, and the wait between probes doubles after
every failure (30 s up to 10 min), resetting once a probe holds.
…d and stored, last cell kept, key-frame losses detected; decoder state in stats.json, debug page addresses with D
…e lowers a proven load at or above it (Qodo on #86)
@compot-dev
compot-dev force-pushed the feat/adaptive-decoding branch from 72d456b to 7afe60b Compare October 13, 2026 09:40
@compot-dev

Copy link
Copy Markdown
Collaborator Author

Qodo still lists findings 2 and 3 on 7afe60b, but the quoted code is from before the fixes: countActive no longer exists (the last-resort pause uses countPlaying, DecodePlanner.java:173), and every promotion resets the quiet period (:208) while a failure lowers a proven load at or above it (:163). Covered by theLastPlayingCellIsNeverPaused and aLoadJustReachedIsNotProvenAndTheCellIsNotPromotedAgain. Treated as resolved.

@compot-dev
compot-dev merged commit 688d8d6 into v2 Oct 13, 2026
2 checks passed
@compot-dev
compot-dev deleted the feat/adaptive-decoding branch October 13, 2026 09:42
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Qodo still lists findings 2 and 3 on 7afe60b, but the quoted code is from before the fixes: countActive no longer exists (the last-resort pause uses countPlaying, DecodePlanner.java:173), and every promotion resets the quiet period (:208) while a failure lowers a proven load at or above it (:163). Covered by theLastPlayingCellIsNeverPaused and aLoadJustReachedIsNotProvenAndTheCellIsNotPromotedAgain. Treated as resolved.

Agreed—the current code addresses both findings:

  • finding 2: promotions reset quietSinceMs in change(), and demotion lowers proven when the failed load reaches it; the regression test covers this.
  • finding 3: the fallback uses countPlaying(cells), so disconnected or non-playing cells cannot justify pausing the last working camera; the regression test covers this.

The published review is stale and still displays findings 2 and 3 as open, but I can’t update the review status from this thread without an explicit dismissal request.

compot-dev added a commit that referenced this pull request Oct 15, 2026
…data, U view, write deadline

- IPv6 site-local (fec0::/10) is no longer a local client: isSiteLocalAddress() counts
  for IPv4 only.
- /log is served from LogBuffer, a ring of the last 500 redacted lines fed by a logcat
  reader that follows the app's own log for the life of the process.
- Each cell reports behind_s (seconds in a row the decoder made it lose frames), with
  overload_after_s for the threshold (#83). Full rate / key frames comes with #86.
- In the U view the JSON has a "udp" object instead of the last single-view state.
- A request has a 5 s deadline, writing included: a client that stops reading has its
  socket closed and cannot hold a worker.
compot-dev added a commit that referenced this pull request Oct 15, 2026
…d and stored, last cell kept, key-frame losses detected; decoder state in stats.json, debug page addresses with D
compot-dev added a commit that referenced this pull request Oct 15, 2026
…e lowers a proven load at or above it (Qodo on #86)
compot-dev added a commit that referenced this pull request Oct 15, 2026
feat: adaptive multi-view decoding (full rate or key frames per cell) and D for the debug text (#85)
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: adaptive decoding — full rate or key frames per cell, learnt device capacity

1 participant