Skip to content

Stabilize OpenStream streaming and release lifecycle#6

Merged
YashasVM merged 2 commits into
mainfrom
codex/stabilize-openstream
Jul 10, 2026
Merged

Stabilize OpenStream streaming and release lifecycle#6
YashasVM merged 2 commits into
mainfrom
codex/stabilize-openstream

Conversation

@YashasVM

@YashasVM YashasVM commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Summary

This is a clean stabilization pass built directly on the latest main (60711f8) and supersedes the integration approach in #5.

  • fix Settings/navigation teardown, black preview recovery, listener cancellation, caller/listener exclusivity, microphone-optional video streaming, and delayed lens restart races
  • serialize native MPEG-TS mux/send/teardown, fix exact-socket ownership, strict SRT parsing, FFmpeg backpressure handling, and OBS shared-state races
  • fix bounded UTF-8 control HTTP framing, stale discovery pruning, complete control responses, and discovery validation
  • restore 48 kHz / 192 kbps audio; unify latency to 80–200 ms; add validated Save & Connect settings and real Kotlin unit tests
  • gate CI on pytest, Android unit tests, lint, APK/native builds, and OBS builds
  • make updates follow coherent full Android+OBS releases with signed APKs and SHA-256 verification

Root cause

The Neo-Brutalist UI update added a separate Settings activity without refactoring the existing stream lifecycle. Opening Config stopped the listener/camera, an accept in progress could not be cancelled, and rapid restart created overlapping generations against singleton native SRT state. Android-only auto-publishing could also update phones ahead of installed OBS plugins.

Validation

  • python -m pytest -q — 27 passed
  • :app:testDebugUnitTest — passed (real SettingsValidator tests)
  • :app:lintDebug — passed
  • :app:assembleDebug — passed
  • :app:externalNativeBuildDebug — passed for arm64-v8a, armeabi-v7a, x86, x86_64
  • Windows OBS Release DLL build — passed
  • workflow YAML parse and git diff --check — passed

Remaining validation

No Android device/emulator was connected locally, so hardware camera/SRT soak testing should run before marking this ready to merge.

Summary by CodeRabbit

  • New Features
    • Added streamlined settings with validation and a “Save & Connect” option.
    • Added automatic APK checksum verification before installation.
    • Added clearer screen-off controls and improved lens-switching behavior.
  • Bug Fixes
    • Improved stream connection, shutdown, audio permission handling, and reconnection reliability.
    • Tightened SRT, discovery, and control request validation.
    • Standardized latency handling to 80–200 ms.
  • Release & Documentation
    • Release APKs now include SHA-256 metadata and require proper signing.
    • Expanded protocol, testing, and release guidance.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 63af09fa-9e8c-405a-986f-8b92e4eeaf81

📥 Commits

Reviewing files that changed from the base of the PR and between c532c07 and 7517377.

📒 Files selected for processing (8)
  • .github/CODEX_REVIEW_REQUEST.md
  • AGENTS.md
  • android/app/src/main/java/dev/openstream/app/MainActivity.kt
  • android/app/src/main/java/dev/openstream/app/SettingsValidator.kt
  • android/app/src/main/java/dev/openstream/app/stream/SrtStreamClient.kt
  • android/app/src/main/java/dev/openstream/app/update/AppUpdater.kt
  • android/app/src/test/java/dev/openstream/app/SettingsValidatorTest.kt
  • docs/protocol.md

📝 Walkthrough

Walkthrough

The PR hardens Android streaming lifecycle and SRT handling, validates settings and control requests, adds APK checksum verification, tightens OBS discovery and decoding, updates signed release workflows, and documents protocol compatibility and release requirements.

Changes

OpenStream hardening

Layer / File(s) Summary
CI and signed release pipeline
.github/workflows/*, android/app/build.gradle.kts, tests/*, AGENTS.md
CI now runs repository and Android checks, release workflows require signing inputs, APK checksums are published, and workflow credentials are restricted.
Settings and activity control flow
android/app/src/main/java/dev/openstream/app/{MainActivity.kt,SettingsActivity.kt,SettingsValidator.kt}, android/app/src/main/java/dev/openstream/app/control/*, android/app/src/main/res/*, android/app/src/test/*
Settings validation, save-and-connect behavior, lifecycle-controlled listener shutdown, audio permission handling, bounded HTTP parsing, and revised controls are implemented and tested.
SRT and media streaming synchronization
android/app/src/main/cpp/openstream_srt.cpp, android/app/src/main/java/dev/openstream/app/{stream,encoder,discovery}/*, tests/test_production_contracts.py
SRT URLs and latency values use strict parsing, native send/teardown operations are serialized, stream sessions use generation checks, and audio parameters and permissions are centralized.
Updater lifecycle and APK verification
android/app/src/main/java/dev/openstream/app/update/AppUpdater.kt
Updates use latest-release metadata, validate APK SHA-256 digests before installation, and prevent callbacks and executor work after disposal.
OBS discovery and control robustness
obs-plugin/src/openstream-source.cpp
Discovery data, control HTTP requests, packet decoding, and property state snapshots now use stricter validation and synchronization.
Protocol and release documentation
docs/protocol.md, docs/release.md, docs/release-notes-template.md, docs/testing.md
Protocol framing, control endpoints, fixed audio and latency requirements, checksum assets, signing rules, release ordering, and Android/OBS compatibility matrices are documented.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant GitHubRelease
  participant AppUpdater
  participant DownloadManager
  participant APKVerifier
  participant PackageInstaller
  GitHubRelease->>AppUpdater: Fetch latest release metadata and apkSha256
  AppUpdater->>DownloadManager: Enqueue APK download
  DownloadManager-->>AppUpdater: Report completed download
  AppUpdater->>APKVerifier: Compute downloaded APK SHA-256
  APKVerifier-->>AppUpdater: Return digest match result
  AppUpdater->>PackageInstaller: Request installation after successful verification
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main focus on stabilizing streaming and release lifecycle behavior across the Android and release workflows.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/stabilize-openstream

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
docs/protocol.md (1)

152-157: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document all fields sent by the OBS plugin in the /reserve request example.

The example only shows sourceInstanceId, but reserve_phone in openstream-source.cpp (lines 937–940) also sends slotId, slotLabel, and bitrateMbps. The Android handler reads slotLabel and bitrateMbps as optional fields. Including them in the example would prevent confusion for protocol implementers.

📝 Suggested update
 ```http
 POST /reserve
 Content-Type: application/json
 
-{"sourceInstanceId":"openstream-..."}
+{"sourceInstanceId":"openstream-...","slotId":"...","slotLabel":"CAM A","bitrateMbps":50}
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/protocol.md around lines 152 - 157, Update the /reserve HTTP request
example in the protocol documentation to include every field sent by
reserve_phone: sourceInstanceId, slotId, slotLabel, and bitrateMbps,
using representative placeholder values.


</details>

<!-- cr-comment:v1:a511cc21bc57ea90aed61ce4 -->

</blockquote></details>
<details>
<summary>android/app/src/main/java/dev/openstream/app/update/AppUpdater.kt (1)</summary><blockquote>

`191-226`: _🎯 Functional Correctness_ | _🟡 Minor_ | _⚡ Quick win_

**Clear pending state when download fails.**

When `isSuccessfulDownload()` returns `false` (line 192), the method shows a toast and returns without clearing `pendingDownloadId` or `pendingRelease`. This leaves stale state that causes `resumePendingInstallIfAllowed()` to retry the same failed download indefinitely, and `pendingRelease` to hold a reference to the failed release data.




<details>
<summary>🛡️ Proposed fix to clear state on download failure</summary>

```diff
 private fun installDownloadedApk() {
     if (!isSuccessfulDownload()) {
         Toast.makeText(activity, "Update download failed", Toast.LENGTH_LONG).show()
+        pendingDownloadId = NO_DOWNLOAD
+        pendingRelease = null
         return
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@android/app/src/main/java/dev/openstream/app/update/AppUpdater.kt` around
lines 191 - 226, Clear the pending update state in installDownloadedApk when
isSuccessfulDownload() returns false: reset pendingDownloadId to NO_DOWNLOAD and
pendingRelease to null before showing the failure toast and returning, using the
same state fields consumed by resumePendingInstallIfAllowed().
android/app/src/main/java/dev/openstream/app/MainActivity.kt (1)

641-645: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Restore listener mode when caller connection fails.

Line 642 stops the failed caller stream but does not restart the listener disabled at Line 622. Since the Stop button remains hidden, the app cannot accept an OBS listener connection again until another lifecycle event.

Proposed recovery
         }.onFailure { error ->
             stopStream()
+            startPreviewIfAllowed()
+            startPhoneServerIfAllowed()
             statusText.text = "Connection failed"
             statusDetail.text = error.message ?: "Unknown error"
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@android/app/src/main/java/dev/openstream/app/MainActivity.kt` around lines
641 - 645, Update the failure handler in MainActivity’s caller connection flow
to restore listener mode after stopStream() runs: restart or re-enable the
listener that was disabled before connecting, and ensure the UI returns to the
state where a new OBS listener connection can be accepted while preserving the
existing failure status messages.
🧹 Nitpick comments (1)
android/app/src/main/java/dev/openstream/app/update/AppUpdater.kt (1)

239-246: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Remove the tampered APK after verification failure.

showVerificationFailure clears pending state but does not call downloadManager.remove(downloadId) to delete the downloaded file. The APK is stored in the app's external files directory and remains accessible via a file manager, allowing a user to manually install a tampered APK that bypassed SHA-256 verification.

🔒️ Proposed fix to remove tampered download
 private fun showVerificationFailure(downloadId: Long) {
     Toast.makeText(activity, "Update verification failed", Toast.LENGTH_LONG).show()
     if (pendingDownloadId == downloadId) {
+        downloadManager.remove(downloadId)
         pendingDownloadId = NO_DOWNLOAD
         pendingRelease = null
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@android/app/src/main/java/dev/openstream/app/update/AppUpdater.kt` around
lines 239 - 246, Update showVerificationFailure to call
downloadManager.remove(downloadId) before or while clearing the pending state,
ensuring the failed APK is deleted even when the download is no longer the
current pending download.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@android/app/src/main/java/dev/openstream/app/MainActivity.kt`:
- Around line 660-662: The listener start path currently drops requests when the
previous SRT listener thread is still alive. Add pending-start state around the
listener startup logic, record the requested start when returning from the
listenerThread?.isAlive guard, and have the thread’s finally/termination cleanup
retry that pending start after clearing listenerThread, while preserving
lifecycle/stop state checks.

In `@android/app/src/main/java/dev/openstream/app/SettingsValidator.kt`:
- Around line 5-8: The isValidHost function currently accepts malformed host
values containing URL delimiters or ports. Update it to validate hostname, IPv4,
and bracketed IPv6 literals only, rejecting paths, query/fragment delimiters,
schemes, and embedded ports while retaining the existing required/blank
behavior. Add tests covering valid host forms and invalid values such as
obs.local/path, obs.local?x=1, and obs.local:9000.

In `@android/app/src/main/java/dev/openstream/app/stream/SrtStreamClient.kt`:
- Around line 18-19: Make session state transitions atomic in SrtStreamClient by
serializing connect/listen attempts and using the same state lock as
disconnect() to validate sessionGeneration and commit connected = true. Capture
the generation while holding the lock, recheck it before committing, and ensure
stale cleanup only disconnects the native session it owns rather than a newer
one; keep SrtNativeBridge.disconnect() outside the operation lock so blocking
accept() remains cancellable.

In `@docs/protocol.md`:
- Around line 237-239: Update the Response section to document that successful
POST control endpoints return HTTP 200 OK with a JSON body containing {"ok":
true}. Reference the send_control_command implementation and clarify that the
OBS plugin uses the ok field to determine request success.

---

Outside diff comments:
In `@android/app/src/main/java/dev/openstream/app/MainActivity.kt`:
- Around line 641-645: Update the failure handler in MainActivity’s caller
connection flow to restore listener mode after stopStream() runs: restart or
re-enable the listener that was disabled before connecting, and ensure the UI
returns to the state where a new OBS listener connection can be accepted while
preserving the existing failure status messages.

In `@android/app/src/main/java/dev/openstream/app/update/AppUpdater.kt`:
- Around line 191-226: Clear the pending update state in installDownloadedApk
when isSuccessfulDownload() returns false: reset pendingDownloadId to
NO_DOWNLOAD and pendingRelease to null before showing the failure toast and
returning, using the same state fields consumed by
resumePendingInstallIfAllowed().

In `@docs/protocol.md`:
- Around line 152-157: Update the `/reserve` HTTP request example in the
protocol documentation to include every field sent by `reserve_phone`:
`sourceInstanceId`, `slotId`, `slotLabel`, and `bitrateMbps`, using
representative placeholder values.

---

Nitpick comments:
In `@android/app/src/main/java/dev/openstream/app/update/AppUpdater.kt`:
- Around line 239-246: Update showVerificationFailure to call
downloadManager.remove(downloadId) before or while clearing the pending state,
ensuring the failed APK is deleted even when the download is no longer the
current pending download.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7cb04848-95e7-436e-9f5c-65aaa92b25c5

📥 Commits

Reviewing files that changed from the base of the PR and between 60711f8 and c532c07.

📒 Files selected for processing (25)
  • .github/workflows/android.yml
  • .github/workflows/obs-plugin-windows.yml
  • .github/workflows/release.yml
  • android/app/build.gradle.kts
  • android/app/src/main/cpp/openstream_srt.cpp
  • android/app/src/main/java/dev/openstream/app/MainActivity.kt
  • android/app/src/main/java/dev/openstream/app/SettingsActivity.kt
  • android/app/src/main/java/dev/openstream/app/SettingsValidator.kt
  • android/app/src/main/java/dev/openstream/app/control/CameraControlServer.kt
  • android/app/src/main/java/dev/openstream/app/discovery/ObsDiscoveryClient.kt
  • android/app/src/main/java/dev/openstream/app/encoder/MediaCodecAudioEncoder.kt
  • android/app/src/main/java/dev/openstream/app/stream/SrtStreamClient.kt
  • android/app/src/main/java/dev/openstream/app/stream/StreamConfig.kt
  • android/app/src/main/java/dev/openstream/app/update/AppUpdater.kt
  • android/app/src/main/res/layout/activity_main.xml
  • android/app/src/main/res/layout/activity_settings.xml
  • android/app/src/main/res/values/strings.xml
  • android/app/src/test/java/dev/openstream/app/SettingsValidatorTest.kt
  • docs/protocol.md
  • docs/release-notes-template.md
  • docs/release.md
  • docs/testing.md
  • obs-plugin/src/openstream-source.cpp
  • tests/test_production_contracts.py
  • tests/test_repo_contract.py

Comment thread android/app/src/main/java/dev/openstream/app/MainActivity.kt
Comment thread android/app/src/main/java/dev/openstream/app/SettingsValidator.kt
Comment thread docs/protocol.md Outdated
@YashasVM YashasVM marked this pull request as ready for review July 10, 2026 09:50
@YashasVM YashasVM merged commit 642b148 into main Jul 10, 2026
3 checks passed
@YashasVM YashasVM deleted the codex/stabilize-openstream branch July 13, 2026 09:35
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