Stabilize OpenStream streaming and release lifecycle#6
Conversation
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
📝 WalkthroughWalkthroughThe 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. ChangesOpenStream hardening
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
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 winDocument all fields sent by the OBS plugin in the
/reserverequest example.The example only shows
sourceInstanceId, butreserve_phoneinopenstream-source.cpp(lines 937–940) also sendsslotId,slotLabel, andbitrateMbps. The Android handler readsslotLabelandbitrateMbpsas 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.mdaround lines 152 - 157, Update the/reserveHTTP request
example in the protocol documentation to include every field sent by
reserve_phone:sourceInstanceId,slotId,slotLabel, andbitrateMbps,
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 winRestore 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 winRemove the tampered APK after verification failure.
showVerificationFailureclears pending state but does not calldownloadManager.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
📒 Files selected for processing (25)
.github/workflows/android.yml.github/workflows/obs-plugin-windows.yml.github/workflows/release.ymlandroid/app/build.gradle.ktsandroid/app/src/main/cpp/openstream_srt.cppandroid/app/src/main/java/dev/openstream/app/MainActivity.ktandroid/app/src/main/java/dev/openstream/app/SettingsActivity.ktandroid/app/src/main/java/dev/openstream/app/SettingsValidator.ktandroid/app/src/main/java/dev/openstream/app/control/CameraControlServer.ktandroid/app/src/main/java/dev/openstream/app/discovery/ObsDiscoveryClient.ktandroid/app/src/main/java/dev/openstream/app/encoder/MediaCodecAudioEncoder.ktandroid/app/src/main/java/dev/openstream/app/stream/SrtStreamClient.ktandroid/app/src/main/java/dev/openstream/app/stream/StreamConfig.ktandroid/app/src/main/java/dev/openstream/app/update/AppUpdater.ktandroid/app/src/main/res/layout/activity_main.xmlandroid/app/src/main/res/layout/activity_settings.xmlandroid/app/src/main/res/values/strings.xmlandroid/app/src/test/java/dev/openstream/app/SettingsValidatorTest.ktdocs/protocol.mddocs/release-notes-template.mddocs/release.mddocs/testing.mdobs-plugin/src/openstream-source.cpptests/test_production_contracts.pytests/test_repo_contract.py
Summary
This is a clean stabilization pass built directly on the latest
main(60711f8) and supersedes the integration approach in #5.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_64git diff --check— passedRemaining 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