Skip to content

Fix MJPEG fallback glitch: serialize pipeline + guard decoder#44

Merged
Suprhimp merged 2 commits into
masterfrom
devplanningo/fix-mirror-glitch
Apr 24, 2026
Merged

Fix MJPEG fallback glitch: serialize pipeline + guard decoder#44
Suprhimp merged 2 commits into
masterfrom
devplanningo/fix-mirror-glitch

Conversation

@Suprhimp

Copy link
Copy Markdown
Owner

Summary

  • Server race fix: rebuildPipeline is now serialized with a Mutex, and currentCodecMode is @Volatile. Previously, a viewport resize and a codec: mjpeg switch could run concurrently on Dispatchers.Default, leaving both VideoEncoder and JpegEncoder alive while the VD surface was last-write-wins — clients requested MJPEG but received H.264 (or vice versa).
  • Single code path for codec switch: onCodecModeRequest now just sets the mode and delegates to rebuildPipeline under the same lock, removing the duplicated encoder/VD bring-up logic that diverged from the primary path.
  • MJPEG decoder guard: fallback.js drops payloads that don't start with the JPEG SOI (FF D8) to silence the InvalidStateError spam that appeared during the brief startup window before the server acknowledged codec: mjpeg.
  • Client connection order for MJPEG: open the control socket and wait for it before opening the video socket when the client picks MJPEG — so the server has the codec preference before streaming starts.
  • Split drawer UX: group apps by Navigation / Video / Music / Apps with colored section headers.

Test plan

  • Install via Android Studio; load the web viewer on a browser without WebCodecs (e.g. older Safari) and confirm MJPEG frames render without InvalidStateError spam.
  • On a WebCodecs browser, change the viewport rapidly while the codec is being switched; confirm no dual-encoder state and no blank screen.
  • Open the split drawer and verify apps are grouped by category with colored bars.

🤖 Generated with Claude Code

- Serialize rebuildPipeline with a Mutex and make currentCodecMode
  @volatile so viewport rebuilds and codec switches can't race into
  dual encoders (VideoEncoder + JpegEncoder) with a last-write-wins
  VD surface.
- Collapse onCodecModeRequest into a delegate that sets the mode and
  reruns rebuildPipeline under the lock — single code path for
  encoder/VD setup.
- Client: MJPEG decoder drops non-JPEG payloads (magic-byte check) to
  avoid InvalidStateError spam during the codec-switch startup window.
- Client: for MJPEG mode, open the control socket and wait for it
  before connecting video, so the server receives the codec preference
  before streaming starts.
- Split drawer: group apps by category (Navigation/Video/Music/Apps)
  with colored section headers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Suprhimp Suprhimp added the patch Version bump: patch (1.2.3 → 1.2.4) label Apr 24, 2026
@vercel

vercel Bot commented Apr 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
website Ready Ready Preview, Comment Apr 24, 2026 8:53am

The "should a codec-mode request trigger a rebuild?" decision was
inline inside MirrorForegroundService.onCodecModeRequest, which is
an Android Service and therefore not reachable from JVM unit tests.
Moving the three-line guard into a CodecModeTransition pure object
lets the decision table be covered directly (non-mjpeg rejected,
already-mjpeg-with-encoder no-op, and the stale-mode recovery case
where the mode flag is "mjpeg" but the JpegEncoder is missing).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Suprhimp Suprhimp merged commit 1658ea5 into master Apr 24, 2026
3 checks passed
Suprhimp added a commit that referenced this pull request Apr 24, 2026
* Fix MJPEG fallback glitch: serialize pipeline + guard decoder

- Serialize rebuildPipeline with a Mutex and make currentCodecMode
  @volatile so viewport rebuilds and codec switches can't race into
  dual encoders (VideoEncoder + JpegEncoder) with a last-write-wins
  VD surface.
- Collapse onCodecModeRequest into a delegate that sets the mode and
  reruns rebuildPipeline under the lock — single code path for
  encoder/VD setup.
- Client: MJPEG decoder drops non-JPEG payloads (magic-byte check) to
  avoid InvalidStateError spam during the codec-switch startup window.
- Client: for MJPEG mode, open the control socket and wait for it
  before connecting video, so the server receives the codec preference
  before streaming starts.
- Split drawer: group apps by category (Navigation/Video/Music/Apps)
  with colored section headers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* Extract codec-mode transition guard into testable pure policy

The "should a codec-mode request trigger a rebuild?" decision was
inline inside MirrorForegroundService.onCodecModeRequest, which is
an Android Service and therefore not reachable from JVM unit tests.
Moving the three-line guard into a CodecModeTransition pure object
lets the decision table be covered directly (non-mjpeg rejected,
already-mjpeg-with-encoder no-op, and the stale-mode recovery case
where the mode flag is "mjpeg" but the JpegEncoder is missing).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
@Suprhimp Suprhimp deleted the devplanningo/fix-mirror-glitch branch April 24, 2026 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch Version bump: patch (1.2.3 → 1.2.4)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant