Skip to content

refactor: shared RtspSession for single view and Quad (#33, #9, #18, #19, #27) - #45

Merged
compot-dev merged 3 commits into
v2from
refactor/rtsp-session
Oct 12, 2026
Merged

compot-dev merged 3 commits into
v2from
refactor/rtsp-session

Conversation

@compot-dev

Copy link
Copy Markdown
Collaborator

Fixes #33, fixes #9, fixes #18, fixes #19, fixes #27.

Stacked on #44 (tests + CI): this PR targets ci/tests so that only its two commits are shown. After #44 is merged, the base becomes v2.

What changes

RtspClient and QuadCell each had their own copy of the RTSP client (handshake, SDP, interleaved read, watchdog, reconnect), and the copies had already drifted apart. Both now use one class.

Class Role
RtspSession (new) One RTSP-over-TCP session: DESCRIBE → SETUP → PLAY, interleaved receive loop, 8 s watchdog, reconnect with back-off from 1 s up to 30 s. It never gives up. stop() does not block; threads of an older generation deliver no callbacks.
Sdp (new) SDP parsing: track URLs, video codec, AudioConfig
RtspClient Single view only: NAL assembly, audio routing, jitter, on top of RtspSession (595 → 165 lines)
QuadCell Decoding only; its copy of the RTSP client is removed (449 → 259 lines)

Issues fixed

Issue How
#9 The video codec comes from a=rtpmap (H.265 on PT 96, H.264 on PT 97 and so on). Without rtpmap the old 96/97 convention applies. If m=video lists several PTs, the first H.264/H.265 one is used.
#18 Audio is set up only when the SDP has an audio track and its codec is supported. A failed audio SETUP is not fatal: video plays without sound.
#19 Track URLs are resolved per RFC 2326: session-level *, Content-Base, absolute and root-relative (/trackID=0) controls.
#27 A Quad cell, like the single view, reconnects forever with back-off capped at 30 s.
#33 One RTSP implementation for both modes.

Other changes

  • URL parsing. The last @ separates the credentials, so a password may contain @. Percent-escapes are decoded, and IPv6 [::1] hosts work. Credentials do not appear in the request line.
  • Response parsing. A response is read as status + headers + Content-Length body. Interleaved RTP frames that arrive before a reply are skipped.
  • Packet matching. Packets are matched by channel (0 video, 2 audio) and by the video PT from the SDP.
  • Sleeping. The worker threads use Thread.sleep (interruptible) instead of SystemClock.sleep. As a result, stopping the RTSP part no longer blocks the UI. onPause can block the UI thread for seconds (ANR risk), worst in Quad mode #32 is not closed by this: onPause still waits for the decode executors.

Tests

51 JVM tests pass in total; 19 of them are new.

Known limitation

stop() no longer waits for the network thread. One packet from the old session may still land in the NAL queue after nalQueue.clear(). Every callback checks the session generation, so the window is a few instructions; at worst one frame of the previous camera reaches the new decoder.

Not tested on a device. Things to check:

  • single view and Quad with H.264 and H.265 cameras;
  • a camera without audio;
  • unplugging and replugging a camera (reconnect).

RtspSession is one RTSP-over-TCP session: DESCRIBE/SETUP/PLAY, the
interleaved receive loop, an 8 s watchdog and reconnection with capped
back-off that never gives up. Stopping is non-blocking, and threads of an
older generation deliver no callbacks.

- Sdp: track URLs resolved per RFC 2326 (session-level "*",
  Content-Base, absolute and root-relative controls), the video codec
  from a=rtpmap with the legacy 96/97 fallback, the first supported
  payload of the m=video line, and audioUrl only when there is an audio
  track.
- The audio SETUP is skipped without an audio track or for unsupported
  codecs, and its failure is not fatal.
- Responses are parsed into status + headers + Content-Length body;
  interleaved frames before a reply are skipped.
- Lenient URL parsing: the last '@' separates credentials, percent
  decoding, IPv6; credentials are kept out of the request line.

Tests: Sdp, URL parsing, responses, and RtspSession against a scripted
server on localhost (handshake, no audio track, failed audio SETUP,
video-only mode, more than 10 failures, reconnect, non-blocking stop).
RtspClient keeps only what is specific to the single view (NAL assembly,
audio routing, jitter) on top of RtspSession; QuadCell drops its copy of
the RTSP client and keeps only decoding. Packets are matched by channel
and by the video payload type from the SDP.

Fixes #33, fixes #9, fixes #18, fixes #19, fixes #27.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Share RTSP sessions across single view and Quad

✨ Enhancement 🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Unify single-view and Quad RTSP handling in a reconnecting, non-blocking session.
• Improve SDP, URL, and response parsing so video and optional audio negotiate correctly.
• Add localhost and parser tests for handshakes, failures, reconnection, and stopping.
Diagram

graph TD
  Camera["RTSP camera"] -->|"RTSP and RTP"| Session(["Shared session"]) -->|"parse DESCRIBE"| Sdp["SDP parser"]
  Session -->|"SDP and packets"| Single["Single view"] -->|"audio packets"| Audio["Audio manager"]
  Session -->|"video packets"| Quad["Quad cell"]
  Decoder["Decoder UI"] -->|"manage"| Single
  Decoder -->|"manage"| Quad
Loading
High-Level Assessment

Keep the shared session with callback-based, mode-specific consumers: it removes divergent protocol implementations without coupling Quad decoding to single-view audio and quality handling. A common playback superclass would mix those responsibilities, while retaining separate network clients would preserve the drift this PR addresses. Device testing remains important for codec playback and reconnect behavior.

Files changed (12) +1243 / -832

Enhancement (1) +517 / -0
RtspSession.javaIntroduce shared reconnecting RTSP-over-TCP session +517/-0

Introduce shared reconnecting RTSP-over-TCP session

• Implements DESCRIBE, optional audio SETUP, PLAY, interleaved packet delivery, an inactivity watchdog, and uncapped retry attempts with capped back-off. Adds non-blocking, generation-guarded stopping plus URL, response, and RTP parsing helpers.

android/app/src/main/java/com/openipc/decoder/RtspSession.java

Bug fix (1) +124 / -0
Sdp.javaExtract SDP track, codec, and URL resolution +124/-0

Extract SDP track, codec, and URL resolution

• Selects the first supported video payload using rtpmap or the legacy fallback, parses audio configuration, and leaves audio absent when no track exists. Resolves session and media controls against Content-Base, including absolute and root-relative URLs.

android/app/src/main/java/com/openipc/decoder/Sdp.java

Refactor (4) +128 / -754
AudioManager.javaUse shared RTP helpers for audio +2/-2

Use shared RTP helpers for audio

• Reads RTP header and payload boundaries through RtspSession instead of the former RtspClient helpers.

android/app/src/main/java/com/openipc/decoder/AudioManager.java

Decoder.javaRely on session stop to invalidate callbacks +0/-3

Rely on session stop to invalidate callbacks

• Removes separate generation invalidation when switching cameras, entering Quad mode, or pausing. RtspClient.stop() now delegates that responsibility to RtspSession.

android/app/src/main/java/com/openipc/decoder/Decoder.java

QuadCell.javaReplace Quad networking with a shared video-only session +42/-232

Replace Quad networking with a shared video-only session

• Removes the cell's RTSP handshake, receive loop, watchdog, and retry threads. QuadCell consumes session callbacks, matches video packets to the SDP payload type, and retains its decoder worker.

android/app/src/main/java/com/openipc/decoder/QuadCell.java

RtspClient.javaReduce single-view client to stream-specific processing +84/-517

Reduce single-view client to stream-specific processing

• Delegates RTSP transport, lifecycle, and protocol parsing to RtspSession. Retains NAL assembly, audio routing, and jitter reporting while using SDP-selected codec and video payload type.

android/app/src/main/java/com/openipc/decoder/RtspClient.java

Tests (5) +471 / -59
RtpTest.javaRetarget RTP tests to the shared session +12/-12

Retarget RTP tests to the shared session

• Runs existing RTP header, padding, interleaved-frame, and truncation checks against the helpers moved to RtspSession.

android/app/src/test/java/com/openipc/decoder/RtpTest.java

RtspResponseTest.javaTest structured RTSP response parsing +37/-13

Test structured RTSP response parsing

• Covers case-insensitive headers, Content-Length bodies, interleaved frames before responses, error statuses, and malformed responses.

android/app/src/test/java/com/openipc/decoder/RtspResponseTest.java

RtspSessionTest.javaExercise session lifecycle against a localhost server +294/-0

Exercise session lifecycle against a localhost server

• Uses a scripted RTSP server to verify handshakes, optional or failed audio SETUP, video-only mode, repeated connection failures, and reconnection. Checks that stop returns quickly and suppresses later status and SDP callbacks.

android/app/src/test/java/com/openipc/decoder/RtspSessionTest.java

RtspUrlTest.javaCover RTSP URL and credential parsing +58/-0

Cover RTSP URL and credential parsing

• Tests credential removal from request URLs, passwords containing @, percent-decoding, IPv6 hosts, defaults, and invalid URLs.

android/app/src/test/java/com/openipc/decoder/RtspUrlTest.java

SdpParserTest.javaExpand SDP parser and negotiation tests +70/-34

Expand SDP parser and negotiation tests

• Moves existing audio and track checks to Sdp and adds session-level controls, root-relative URLs, absent audio, rtpmap codec selection, and multiple video payloads.

android/app/src/test/java/com/openipc/decoder/SdpParserTest.java

Other (1) +3 / -19
lint-baseline.xmlRefresh lint baseline after the RTSP rewrite +3/-19

Refresh lint baseline after the RTSP rewrite

• Removes the resolved RtspClient indentation warning and adjusts existing Decoder warning locations.

android/app/lint-baseline.xml

@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 (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Multi-track cameras show no video ✓ Resolved
Description
Sdp.parse() accumulates payload types across video sections but overwrites the video control URL
with each later section. When an SDP offers two video tracks, SETUP can select the later track while
both clients accept only the earlier track’s payload type, dropping its video packets.
Code

android/app/src/main/java/com/openipc/decoder/Sdp.java[R47-50]

+                    for (int i = 3; i < parts.length; i++) {
+                        try {
+                            videoPts.add(Integer.parseInt(parts[i]));
+                        } catch (NumberFormatException ignored) {}
Evidence
Every video section appends to one list, but later controls replace the single stored video control.
The first supported payload is selected from that list, while SETUP uses the independently stored
URL and the client filters incoming packets by the selected payload type.

android/app/src/main/java/com/openipc/decoder/Sdp.java[38-50]
android/app/src/main/java/com/openipc/decoder/Sdp.java[64-69]
android/app/src/main/java/com/openipc/decoder/Sdp.java[90-104]
android/app/src/main/java/com/openipc/decoder/RtspSession.java[202-204]
android/app/src/main/java/com/openipc/decoder/RtspClient.java[116-123]

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

## Issue description
Multiple video sections can produce a payload type from one track and a control URL from another, leaving the selected stream undecodable.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/Sdp.java[38-50]
- android/app/src/main/java/com/openipc/decoder/Sdp.java[64-69]
- android/app/src/main/java/com/openipc/decoder/Sdp.java[90-104]
## Recommended Fix
Represent each video media section with its own payloads, codec mappings, and control URL. Select a supported payload and resolve the URL from that same section; add a two-video-track test.

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


2. Some cameras show 'connected' but never display video ✓ Resolved
Description
When Sdp.parse finds no H.264/H.265 payload type (empty body, missing Content-Length, or no rtpmap
for a non-96/97 PT), videoPt stays -1 and RtspClient.processVideo / QuadCell.onPacket drop
every video packet as an "unexpected PT". Meanwhile RtspSession.receive reports "connected" and
refreshes the watchdog on any channel-0 packet regardless of PT, so the session never reconnects and
the user gets a permanent black view; previously such streams still decoded PT 96/97 and the old
reader fell back to already-buffered bytes when Content-Length was missing.
Code

android/app/src/main/java/com/openipc/decoder/Sdp.java[R91-101]

+        for (int pt : videoPts) {
+            String codec = videoCodecs.get(pt);
+            if (codec == null) {
+                codec = pt == PT_H265 ? "H265" : pt == PT_H264 ? "H264" : null;
+            }
+            if ("H264".equals(codec) || "H265".equals(codec) || "HEVC".equals(codec)) {
+                out.videoPt = pt;
+                out.videoH265 = !"H264".equals(codec);
+                break;
+            }
+        }
Evidence
Sdp.videoPt defaults to -1 and is only set inside the loop over videoPts. readResponse returns
an empty body when there is no Content-Length header, so the SDP is empty. Both callbacks return
early when pt != videoPt, but receive() sets lastPacketMs and the "connected" status for every
channel-0 packet, so neither the watchdog nor a reconnect ever fires.

android/app/src/main/java/com/openipc/decoder/Sdp.java[24-24]
android/app/src/main/java/com/openipc/decoder/RtspSession.java[259-268]
android/app/src/main/java/com/openipc/decoder/RtspSession.java[339-357]
android/app/src/main/java/com/openipc/decoder/RtspClient.java[116-123]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[194-201]

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

## Issue description
If the SDP yields no supported video PT, `Sdp.videoPt` stays -1. All video packets are then dropped, but the session still looks healthy ("connected", and the watchdog keeps being reset), so it never recovers.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/Sdp.java[91-101]
- android/app/src/main/java/com/openipc/decoder/RtspSession.java[198-201]
- android/app/src/main/java/com/openipc/decoder/RtspSession.java[259-266]
## Recommended Fix
After `Sdp.parse` in `RtspSession.connect`, throw an IOException when `sdp.videoPt < 0` so the reconnect back-off applies. Alternatively, fall back to the legacy 96/97 convention, for example by accepting either PT and taking the codec from it. Also refresh the watchdog and send the "connected" status only for packets whose PT matches `sdp.videoPt`.

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



Remediation recommended

3. Cameras without body length stay offline ✓ Resolved
Description
readResponse() consumes a DESCRIBE body only when the reply has a Content-Length header, leaving
the response body empty otherwise. If an SDP body arrives without that header, SETUP proceeds with
fallback track settings and the unread SDP can be interpreted as the next RTSP status line; the
removed client had a fallback for already-available body bytes.
Code

android/app/src/main/java/com/openipc/decoder/RtspSession.java[R339-340]

+        String length = r.header("content-length");
+        if (length != null) {
Evidence
A missing Content-Length leaves Response.body at its empty default. The handshake immediately
parses that value and sends SETUP; readResponse() then rejects an unread SDP line because it is
not an RTSP status line. The deleted implementation attempted to consume available body bytes in
this case.

android/app/src/main/java/com/openipc/decoder/RtspSession.java[274-277]
android/app/src/main/java/com/openipc/decoder/RtspSession.java[339-357]
android/app/src/main/java/com/openipc/decoder/RtspSession.java[196-203]
android/app/src/main/java/com/openipc/decoder/RtspSession.java[315-320]

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

## Issue description
DESCRIBE replies without Content-Length leave their SDP unread, so the subsequent SETUP handshake can fail.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/RtspSession.java[339-357]
- android/app/src/main/java/com/openipc/decoder/RtspSession.java[196-203]
## Recommended Fix
Restore a bounded fallback for an already-buffered DESCRIBE body when Content-Length is absent, and test that the body is consumed before reading the SETUP response.

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


4. Invalid video packets prevent reconnects ✓ Resolved
Description
receive() refreshes lastPacketMs and reports a connection on every channel-zero packet before
either client validates its payload type or RTP header. A camera that keeps sending wrong-payload or
otherwise rejected channel-zero packets therefore remains active indefinitely without decodable
video, because the watchdog never reaches its inactivity threshold.
Code

android/app/src/main/java/com/openipc/decoder/RtspSession.java[R259-264]

+            int channel = buf[1] & 0xFF;
+            if (channel == CHANNEL_VIDEO) {
+                lastPacketMs = System.nanoTime() / 1_000_000;
+                if (first) {
+                    first = false;
+                    status(gen, "connected");
Evidence
The session updates activity solely from the interleaved channel; both consumers subsequently reject
packets with unsupported headers or payload types. The watchdog tests only the resulting activity
timestamp.

android/app/src/main/java/com/openipc/decoder/RtspSession.java[157-165]
android/app/src/main/java/com/openipc/decoder/RtspSession.java[255-268]
android/app/src/main/java/com/openipc/decoder/RtspClient.java[111-125]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[190-200]

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

## Issue description
Rejected channel-zero packets refresh the video watchdog and can prevent recovery from a stream with no usable video.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/RtspSession.java[255-268]
- android/app/src/main/java/com/openipc/decoder/RtspClient.java[111-125]
- android/app/src/main/java/com/openipc/decoder/QuadCell.java[190-206]
## Recommended Fix
Update video activity and first-video status only after validating that a packet is usable by the selected video track. Share that validation or return an acceptance result from the callback, and test a continuing stream of rejected packets.

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


5. Camera recovery can take 45 seconds ✓ Resolved
Description
networkLoop() adds jitter of up to half of retryMs after retryMs has already reached the
30-second maximum. Following repeated connection failures, a camera that comes back online can
consequently wait nearly 45 seconds for the next attempt rather than the configured maximum delay.
Code

android/app/src/main/java/com/openipc/decoder/RtspSession.java[R148-149]

+            try {
+                Thread.sleep(retryMs + (long) (Math.random() * retryMs / 2));
Evidence
The sleep adds up to 50% of retryMs, while the following line caps only retryMs at maxRetryMs;
the configured maximum is 30,000 ms.

android/app/src/main/java/com/openipc/decoder/RtspSession.java[39-41]
android/app/src/main/java/com/openipc/decoder/RtspSession.java[143-153]

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

## Issue description
Adding jitter after capping the base back-off makes the actual reconnect delay exceed 30 seconds.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/RtspSession.java[143-153]
## Recommended Fix
Cap the final sleep duration at maxRetryMs, or apply bounded jitter within that limit. Test the delay calculation after the back-off reaches its maximum.

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


View review recommended (1)
6. Stopped Quad cell can keep a hardware decoder ✓ Resolved
Description
QuadCell.onSdp now calls initDecoder() on the session network thread, and stop() no longer
waits for that thread, so a thread that has already passed the generation check in
RtspSession.connect can finish createDecoder after stop() has closed the decoder. When
mediaCodecManager was still null (first connect), the new codec is stored after stop, bound to the
surface being torn down, and stays open while Quad mode is stopped until the cell's next stop().
Code

android/app/src/main/java/com/openipc/decoder/QuadCell.java[R181-186]

+    public void onSdp(Sdp sdp) {
+        nalAssembler.reset();
+        lastUnknownPayload = -1;
+        videoPt = sdp.videoPt;
+        codecH265 = sdp.videoH265;
+        initDecoder();
Evidence
RtspSession.connect checks gen != generation only before callback.onSdp; RtspSession.stop
returns immediately. QuadCell.stop closes the decoder under decoderLock and nulls surface, but
initDecoder copies surface beforehand and assigns mediaCodecManager = mgr without checking
running. Before this change, stop() waited for the network thread on the executor before closing
the decoder.

android/app/src/main/java/com/openipc/decoder/RtspSession.java[223-224]
android/app/src/main/java/com/openipc/decoder/RtspSession.java[101-111]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[147-173]
android/app/src/main/java/com/openipc/decoder/QuadCell.java[237-258]

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

## Issue description
`QuadCell.initDecoder` can run on the RtspSession network thread after `QuadCell.stop()` has returned. It then installs a new MediaCodec that nothing releases until the next stop.
## Fix Focus Areas
- android/app/src/main/java/com/openipc/decoder/QuadCell.java[181-187]
- android/app/src/main/java/com/openipc/decoder/QuadCell.java[237-258]
## Recommended Fix
Inside the final `synchronized (decoderLock)` block of `initDecoder`, also check `!running`. If the cell has stopped, close `mgr` and return. Alternatively, have `onSdp` only record the PT and codec, and leave decoder creation to the decode thread, which `stop()` waits for.

ⓘ 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 commit Qodo's fix in one click with committable suggestions (GitHub & GitLab)

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread android/app/src/main/java/com/openipc/decoder/Sdp.java
Comment thread android/app/src/main/java/com/openipc/decoder/RtspSession.java
Comment thread android/app/src/main/java/com/openipc/decoder/RtspSession.java
Comment thread android/app/src/main/java/com/openipc/decoder/RtspSession.java Outdated
Comment thread android/app/src/main/java/com/openipc/decoder/Sdp.java Outdated
Comment thread android/app/src/main/java/com/openipc/decoder/QuadCell.java
- Sdp: each m=video section keeps its own payloads, rtpmap and control;
  the payload type and SETUP URL come from the same section.
- No H.264/H.265 video track in the SDP fails the session, so it is
  retried with back-off instead of showing "connected" with no picture.
- DESCRIBE replies without Content-Length: the body is read until 300 ms
  of silence (at most 64 KiB), as the old client did with buffered bytes.
- onPacket() reports whether a video packet was usable; only usable
  packets feed the watchdog and the "connected" status.
- The reconnect delay including jitter is capped at maxRetryMs.
- QuadCell: a decoder created on the network thread after stop() is
  released instead of kept.
@compot-dev
compot-dev changed the base branch from ci/tests to v2 October 12, 2026 11:20
@compot-dev
compot-dev merged commit a27adae into v2 Oct 12, 2026
4 checks passed
compot-dev added a commit that referenced this pull request Oct 12, 2026
- Screenshots on Android 6-9 request WRITE_EXTERNAL_STORAGE first and
  continue once it is granted; the PNG is encoded and saved on a
  background thread instead of the UI thread (fixes #28).
- Back is no longer intercepted in onKeyDown(): the menu PopupWindow and
  the dialogs handle it themselves, including predictive back gestures;
  enableOnBackInvokedCallback opts Android 13-15 into the same behaviour
  (fixes #30).
- Cleanup (fixes #35):
  - removed the ProGuard rule for the no longer existing Decoder$Frame;
  - FramePool keeps small and large frames in separate, bounded classes
    with rounded capacities, so small requests no longer take key-frame
    buffers and key frames reuse them;
  - the single-view NAL buffer and KEY_MAX_INPUT_SIZE are both 2 MiB, so
    4K key frames fit;
  - (the 5-thread pool and the suspicious indentation went away with
    RtspSession, #45.)
- Lint baseline refreshed: no errors left in it.
@compot-dev
compot-dev deleted the refactor/rtsp-session branch October 13, 2026 17:16
compot-dev added a commit that referenced this pull request Oct 15, 2026
refactor: shared RtspSession for single view and Quad (#33, #9, #18, #19, #27)
compot-dev added a commit that referenced this pull request Oct 15, 2026
- Screenshots on Android 6-9 request WRITE_EXTERNAL_STORAGE first and
  continue once it is granted; the PNG is encoded and saved on a
  background thread instead of the UI thread (fixes #28).
- Back is no longer intercepted in onKeyDown(): the menu PopupWindow and
  the dialogs handle it themselves, including predictive back gestures;
  enableOnBackInvokedCallback opts Android 13-15 into the same behaviour
  (fixes #30).
- Cleanup (fixes #35):
  - removed the ProGuard rule for the no longer existing Decoder$Frame;
  - FramePool keeps small and large frames in separate, bounded classes
    with rounded capacities, so small requests no longer take key-frame
    buffers and key frames reuse them;
  - the single-view NAL buffer and KEY_MAX_INPUT_SIZE are both 2 MiB, so
    4K key frames fit;
  - (the 5-thread pool and the suspicious indentation went away with
    RtspSession, #45.)
- Lint baseline refreshed: no errors left in it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant