diff --git a/README.md b/README.md index 7318a00c..ea5710a8 100644 --- a/README.md +++ b/README.md @@ -2,28 +2,27 @@ - OpenStream V1.0.1 Banner + OpenStream banner ### Turn any Android phone into a wireless camera source for OBS Studio -[![Version](https://img.shields.io/badge/version-1.0.1-00D4AA?style=flat-square&labelColor=1a1a2e)](https://github.com/YashasVM/OpenStream/releases/tag/v1.0.1) +[Latest release](https://github.com/YashasVM/OpenStream/releases/latest) [![Platform](https://img.shields.io/badge/platform-Android%20%2B%20Windows%20%2B%20Linux-green?style=flat-square&labelColor=1a1a2e)](https://github.com/YashasVM/OpenStream) [![OBS](https://img.shields.io/badge/OBS-Studio%20Plugin-purple?style=flat-square&labelColor=1a1a2e)](https://obsproject.com) [![Website](https://img.shields.io/badge/website-openstream.pages.dev-00D4AA?style=flat-square&labelColor=1a1a2e)](https://openstream.pages.dev) -**Open-source** | **One phone, one camera** | **Hardware phone encoding** | **Local Wi-Fi** +**Source available** | **One phone, one camera** | **Hardware phone encoding** | **Local Wi-Fi** --- > [!IMPORTANT] -> This branch simplifies OpenStream to one Android camera per OBS process and adds a native Linux build. Published V1.0.1 downloads predate these changes. Candidate builds need the device acceptance checks in [docs/testing.md](docs/testing.md) before release. +> The repository branch may contain changes that are not in the latest published release. Candidate builds need the device acceptance checks in [docs/testing.md](docs/testing.md) before release. -The supported V1.0.1 path is the legacy Android app plus the legacy OBS camera -source/dock over local Wi-Fi. USB transport and USB camera-source support are -not included in this release. +The latest published release may not include the changes on this branch. USB +transport and USB camera-source support are not included in the published app. ## Quick Downloads @@ -32,7 +31,7 @@ not included in this release. | 1 | [`openstream-android.apk`](https://github.com/YashasVM/OpenStream/releases/latest/download/openstream-android.apk) | Android phone | | 2 | [`openstream-obs-plugin-installer-windows-x64.exe`](https://github.com/YashasVM/OpenStream/releases/latest/download/openstream-obs-plugin-installer-windows-x64.exe) | Windows OBS PC | | Fallback | [`openstream-obs-windows-x64.zip`](https://github.com/YashasVM/OpenStream/releases/latest/download/openstream-obs-windows-x64.zip) | Manual OBS plugin install | -| Linux candidate | [Build and install from source](docs/linux.md) | Native Linux OBS; the next tagged release will include a matched Linux package | +| Linux candidate | [Build and install from source](docs/linux.md) | Native Linux OBS; check the release assets for a published package | Need the non-technical walkthrough with screenshots? Start with [`docs/set-up.md`](docs/set-up.md). @@ -40,20 +39,19 @@ Need the non-technical walkthrough with screenshots? Start with [`docs/set-up.md ## What is OpenStream? -OpenStream V1.0.1 sends your Android phone camera directly into OBS Studio over local Wi-Fi. It uses Camera2, MediaCodec video/audio encoding, MPEG-TS muxing, SRT transport, two-way LAN discovery, single-camera pairing, and a native OBS source plugin. +OpenStream sends an Android phone camera to OBS Studio over local Wi-Fi. The app uses Camera2 and MediaCodec, then carries MPEG-TS video and audio over SRT to an OBS source plugin. ```text Phone camera -> hardware AVC/H.264 + AAC -> SRT over Wi-Fi -> OpenStream camera source in OBS ``` -### V1.0.1 Release +### Published V1.0.1 release | Area | V1.0.1 release detail | |---|---| -| **OBS setup** | The V1.0.1 release uses its legacy source controls. The current candidate exposes controls in source properties. | -| **Scene compatibility** | Existing `OpenStream V7` scene sources keep loading while new sources appear as `OpenStream V8`. | -| **Pairing clarity** | One camera per OBS process, with a simple computer picker on Android. | -| **Release polish** | Version defaults, release notes, and setup docs are aligned around V1.0.1. | +| **OBS setup** | The published plugin registers the source identifiers found in the historical release. Actual saved-scene settings and media compatibility still need device and OBS checks. | +| **Source identity** | The plugin displays the source name `shin`; the historical release registers `openstream_phone_v7_source` and `openstream_phone_v8_source`. | +| **Published assets** | The release includes the Android APK and Windows installer and plugin archive. | ## Quick Start @@ -66,7 +64,7 @@ Download the APK from the release, copy it to your Android phone, open it, and a Connect the phone with USB debugging enabled and run `cd android && ./gradlew installDebug`. Repeating this command installs the updated app in place when the installed build has the same application ID and signing key. The version -name and code come from `android/gradle.properties`; increase the code there +name and code come from `release/version.properties`; increase the code there for a release. Android rejects an in-place update if the APK uses a different signing key, such as when switching between a local debug build and the published release. Keep using the same build type for day-to-day updates. @@ -80,15 +78,15 @@ Download and run `openstream-obs-plugin-installer-windows-x64.exe` on the Window ### 3. Add One Camera in OBS -In OBS, click `+` in Sources, choose `OpenStream V8`, and press OK. Keep one OpenStream source. To use the camera in another scene, select **Add Existing**. If upgrading a scene with multiple camera sources, remove the extras; additional capture sessions are blocked with an explanation. +In OBS, click `+` in Sources and choose `OpenStream Camera`. Keep one OpenStream source. To use the camera in another scene, select **Add Existing**. If upgrading a scene with multiple camera sources, remove the extras; additional capture sessions are blocked with an explanation. ### 4. Pair the Phone -Open the OpenStream Android app on the same Wi-Fi network as the PC. Its discovery panel lists available OBS computers; tap yours to connect. The camera feed should appear in OBS within a few seconds. +Open the OpenStream Android app on the same Wi-Fi network as the PC. Its discovery panel lists available OBS computers; tap yours to connect. Check the source status and phone preview to confirm that the camera connected. ### 5. Stream -Use OBS as usual. Open **Docks → OpenStream Camera Control** for the camera controls. Phone audio appears on the OpenStream source’s OBS mixer channel. +Use the OpenStream source properties for camera controls. Phone audio appears on the OpenStream source’s OBS mixer channel. > [!TIP] > Use a 5 GHz or Wi-Fi 6 network, keep both devices on the same subnet, and disable VPNs or router client isolation during first setup. @@ -102,7 +100,7 @@ Use OBS as usual. Open **Docks → OpenStream Camera Control** for the camera co | **Phone OS** | Android 10+ with Camera2 and hardware MediaCodec support. | | **OBS host** | Windows x64: OBS Studio 32.2.1. Native Linux: build against the installed OBS, Qt, and FFmpeg packages; see [Linux setup](docs/linux.md). Flatpak/Snap builds are not covered. | | **Network** | Same LAN/subnet; guest Wi-Fi, VPNs, and client isolation can block discovery. | -| **Release maturity** | V1.0.1. Expect device-specific camera quirks and Wi-Fi-dependent latency. | +| **Release maturity** | Camera behavior varies by device, and network latency depends on Wi-Fi conditions. | --- @@ -124,7 +122,7 @@ Use OBS as usual. Open **Docks → OpenStream Camera Control** for the camera co | **Manual Connect** | Enable **Receive manual connection from phone** in OBS advanced settings, then enter the PC IP/port on the phone. Remote camera controls stay on the phone in this mode. | | **Live Stream Telemetry** | Shows frames, keyframes, transferred megabits, errors, and active lens while streaming. | | **Identify Overlay** | Displays a connection label on the phone when triggered from OBS. | -| **Reconnect Hold** | Keeps a reserved OBS connection for `45` seconds after a disconnect to make brief Wi-Fi drops less disruptive. | +| **Reconnect Hold** | Keeps a reserved OBS connection for `45` seconds after a disconnect. | ### OBS Plugin @@ -133,7 +131,7 @@ For native Linux build and installation instructions, see [docs/linux.md](docs/l | Feature | Details | |---|---| -| **Native OBS Source** | Adds an `OpenStream V8` source type inside OBS Studio while preserving V7 scene compatibility. | +| **Native OBS Source** | Adds an `OpenStream Camera` source type inside OBS Studio. Saved-scene settings and media compatibility with the published release still need verification. | | **OBS controls** | Connection, phone selection, zoom, torch, lens, and identify controls are exposed through source properties. | | **One-Click Installer** | Windows installer copies the plugin into the OBS plugin folder. | | **Phone ownership** | Each phone can be reserved by one OBS source at a time. Reuse that source across scenes. | @@ -267,7 +265,7 @@ This is a developer/debug tool only. Normal users should install the OBS plugin. | Link | URL | |---|---| -| Website | [OpenStream V1.0.1](https://openstream.pages.dev) | +| Website | [OpenStream](https://openstream.pages.dev) | | Releases | [github.com/YashasVM/OpenStream/releases](https://github.com/YashasVM/OpenStream/releases) | | Issues | [github.com/YashasVM/OpenStream/issues](https://github.com/YashasVM/OpenStream/issues) | diff --git a/docs/release-notes-template.md b/docs/release-notes-template.md index a8cb663d..a6e16f74 100644 --- a/docs/release-notes-template.md +++ b/docs/release-notes-template.md @@ -1,44 +1,28 @@ -# OpenStream V1.0.1 +# OpenStream {{VERSION}} -OpenStream V1.0.1 is a fixed Android-to-OBS camera release with V8 source naming, V7 scene compatibility, universal Android packaging, and install downloads for both surfaces. +{{SUMMARY_OF_THE_RELEASE}} -## What's New +## What's new -| Area | Change | -|---|---| -| OBS setup | Source properties now follow `1. Camera Slot`, `2. Live Camera Controls`, and `3. Network & Pairing (Advanced)`. | -| Scene compatibility | Existing `OpenStream V7` scene sources keep loading through a compatibility source id. | -| Pairing copy | OBS now explains the phone-side slot selection flow instead of leading with SRT details. | -| Setup docs | The walkthrough now includes stale-plugin checks for Program Files, ProgramData, and per-user OBS plugin folders. | -| OBS compatibility | Windows plugin releases target OBS Studio 32.2.1 x64 and its FFmpeg 62/62/60/9 ABI. | +- {{USER_VISIBLE_CHANGE}} ## Downloads | File | Use | |---|---| -| `openstream-android.apk` | Install this on your Android phone. | -| `openstream-android.apk.sha256` | Verify the downloaded APK before installation. | -| `openstream-obs-plugin-installer-windows-x64.exe` | Recommended Windows installer for the OBS plugin. | -| `openstream-obs-windows-x64.zip` | Manual plugin package with DLL and install scripts. | -| `openstream-obs-windows-x64.zip.sha256` | Verify the downloaded manual plugin package. | - -> [!NOTE] -> The Android APK is release-signed and accompanied by a SHA-256 checksum. Public releases fail instead of publishing a debug-signed fallback when signing inputs are unavailable. - -## Install +| `openstream-android.apk` | Install on an Android phone. | +| `openstream-obs-plugin-installer-windows-x64.exe` | Install the OBS plugin on Windows. | +| `openstream-obs-windows-x64.zip` | Install the Windows plugin manually. | +| `shin-obs-linux-x86_64.tar.gz` | Install the Linux plugin on a compatible system. | -1. Install the APK on your Android phone. -2. Run the OBS plugin installer on your Windows OBS PC. -3. Restart OBS Studio. -4. Add an `OpenStream V8` source. -5. Open the Android app on the same Wi-Fi network and tap the discovered OBS slot. +## Compatibility -For the screenshot walkthrough, read [`docs/set-up.md`](https://github.com/YashasVM/OpenStream/blob/main/docs/set-up.md). +- Android: {{ANDROID_COMPATIBILITY}} +- Windows OBS: {{WINDOWS_OBS_COMPATIBILITY}} +- Linux OBS: {{LINUX_OBS_COMPATIBILITY}} +- Scene compatibility: {{SCENE_COMPATIBILITY_EVIDENCE}} ## Notes -- Windows OBS plugin only. -- Existing source names may still say `OpenStream V7`; rename them in OBS if you want the scene label to match V1.0.1. -- Use 5 GHz or Wi-Fi 6 for best results. -- Keep both devices on the same subnet. -- Please report issues on GitHub. +- {{KNOWN_LIMITATION_OR_MIGRATION_NOTE}} +- See the [setup guide](https://github.com/YashasVM/OpenStream/blob/main/docs/set-up.md). diff --git a/docs/release.md b/docs/release.md index cc44b683..ede8bd40 100644 --- a/docs/release.md +++ b/docs/release.md @@ -36,27 +36,42 @@ The APK is universal with `minSdk 29` and native libraries for `arm64-v8a`, `armeabi-v7a`, `x86`, and `x86_64`. Camera lens availability and device-specific Camera2 behavior still require checking on target phones. -## Automated Release +## Build and publish a release -Set both `openstream.versionName` and `openstream.versionCode` in -`android/gradle.properties`, then create and push a matching `vMAJOR.MINOR.PATCH` -tag that points to that commit. The version name must equal the tag without its -leading `v`; the version code must be a positive integer greater than every -previous release code and no greater than Android's 2,100,000,000 limit. +Set `productVersion` and `androidVersionCode` in `release/version.properties`. +The version must match the `vMAJOR.MINOR.PATCH` tag without its leading `v`. +The version code must be greater than the previous published APK and no greater +than Android's 2,100,000,000 limit. ```powershell -git tag v1.0.1 -git push origin v1.0.1 +git tag vMAJOR.MINOR.PATCH +git push origin vMAJOR.MINOR.PATCH ``` -The same workflow can be run manually with an already pushed release tag. It -builds the commit referenced by that tag and rejects malformed or mismatched -tags. It does not create a release from the current default branch. Older -releases used the tag commit timestamp as the Android version code; the -workflow uses that value when checking historical tags without the canonical -`openstream.versionCode` property. +Pushing a tag runs the `Release candidate` workflow. It builds the Android APK, +the Windows installer and plugin archive, and the Linux plugin archive. It +checks the tag, APK package, version code, signing certificate, plugin versions, +installer version, and artifact hashes. It uploads a candidate bundle as a +GitHub Actions artifact with `release-manifest.json`; it does not publish a release. -The `Release` workflow builds: +After the candidate passes the device and OBS checks, dispatch the same workflow +in `publish` mode. Enter the tag and successful candidate run ID. The workflow +checks that the candidate belongs to the tagged commit, verifies its manifest +and hashes, then publishes those downloaded files without rebuilding them. +If an earlier published APK cannot be fetched, publication fails closed. + +The staged manifest records the product version, Android identity, protocol, +supported OBS ABIs, and each artifact's filename, size, and SHA-256 hash. The +website shows a version and version-specific download links only after the +latest published release contains this manifest and the GitHub release API +reports the matching size and SHA-256 digest for every listed asset. The +website also checks that each published checksum sidecar matches the manifest. +Until those checks pass, the website labels the links "Latest release". + +The website's npm package version is build-tool metadata. Keep it at `0.0.0`; +release version changes belong in `release/version.properties`. + +The candidate workflow builds these files: | Job | Output | |---|---| @@ -65,10 +80,16 @@ The `Release` workflow builds: | OBS plugin installer | `openstream-obs-plugin-installer-windows-x64.exe`, `openstream-obs-plugin-installer-windows-x64.exe.sha256` | | Linux OBS plugin | `shin-obs-linux-x86_64.tar.gz`, `shin-obs-linux-x86_64.tar.gz.sha256` | -The publish job runs only after repository tests, Android unit tests and lint, -the signed Android build, and both Windows and Linux plugin builds succeed. It -downloads all artifacts, verifies each published binary's SHA-256 sidecar, and -then runs `gh release create`. +The workflow requires repository tests, Android unit tests and lint, the signed +Android build, and both Windows and Linux plugin builds to pass before it stages +a candidate. The publish job verifies the staged SHA-256 sidecars before it +creates a GitHub release. + +Prepare new release notes for every release. Copy +[`release-notes-template.md`](release-notes-template.md) into the workflow's +`release_notes` input and replace every placeholder with details verified for +that release. Keep earlier published notes unchanged. The publish workflow +requires non-empty notes and uses the submitted text as the release body. Public releases require all Android signing secrets. Missing or incomplete signing inputs fail the workflow; it never publishes a debug-signed fallback. @@ -146,14 +167,15 @@ only; do not publish that APK as a release. ## Release Checklist -- Confirm the README links point to the release tag being published. +- Confirm the README links use the latest published assets. - Confirm the setup guide links to the same APK, installer EXE, and plugin zip. - Confirm pytest, Android unit tests, lint, and Android, Windows, and Linux release builds passed. - Confirm `openstream-android.apk.sha256` matches the APK. - Confirm `openstream-obs-plugin-installer-windows-x64.exe.sha256` matches the installer. - Confirm `openstream-obs-windows-x64.zip.sha256` matches the manual plugin package. - Confirm `shin-obs-linux-x86_64.tar.gz.sha256` matches the Linux package. -- Confirm OBS lists `OpenStream V8` and can still load saved `openstream_phone_v7_source` scenes. +- Confirm the published `release-manifest.json` matches each artifact's name, size, and SHA-256 hash. +- Confirm OBS lists `OpenStream Camera` and test saved scenes from each published plugin version. Record settings and media results; do not infer them from source registration alone. - Confirm the dependency report names `avformat-62.dll`, `avcodec-62.dll`, `avutil-60.dll`, and `swscale-9.dll`, and the clean OBS 32.2.1 log has no OpenStream module-load error. - Seed `openstream-obs.dll`, run both installer forms, and confirm stale Program Files, ProgramData, and AppData copies were migrated without touching OBS settings or scenes. - Confirm the Android APK is release-signed and installable over the previous public release. diff --git a/tests/test_production_contracts.py b/tests/test_production_contracts.py index 87ed924d..d2f8954b 100644 --- a/tests/test_production_contracts.py +++ b/tests/test_production_contracts.py @@ -140,7 +140,10 @@ def test_android_and_obs_release_artifacts_stay_atomic() -> None: assert "python -m pytest -q" in android_workflow assert ":app:lintDebug" in android_workflow assert "openstream-android.apk.sha256" in android_workflow - assert "android/gradle.properties" in release_docs + assert "release/version.properties" in release_docs + assert "android/gradle.properties" not in release_docs + assert "release-manifest.json" in release_docs + assert "inputs.release_notes" in release_workflow assert "release/version.properties" in release_workflow assert "productVersion=" in release_workflow assert "androidVersionCode=" in release_workflow diff --git a/tests/test_repo_contract.py b/tests/test_repo_contract.py index a4387ce3..3fe1a709 100644 --- a/tests/test_repo_contract.py +++ b/tests/test_repo_contract.py @@ -302,7 +302,9 @@ def test_release_workflows_build_streaming_apk_and_plugin_package() -> None: assert "release/version.properties" in release_workflow assert "productVersion=" in release_workflow assert "androidVersionCode=" in release_workflow - assert "android/gradle.properties" in release_docs + assert "release/version.properties" in release_docs + assert "android/gradle.properties" not in release_docs + assert "release-manifest.json" in release_docs assert "OPENSTREAM_SKIP_INSTALL=1" in obs_workflow assert "OPENSTREAM_PLUGIN_PACKAGE_DIR" in obs_workflow assert "openstream-obs-windows-x64.zip" in obs_workflow @@ -383,6 +385,25 @@ def test_website_package_version_is_tooling_only() -> None: assert package["version"] == "0.0.0" +def test_website_uses_only_published_manifest_version_for_release_copy() -> None: + website = read("website/src/main.jsx") + index = read("website/index.html") + readme = read("README.md") + + assert '"Latest release"' in website + assert "release-manifest.json" in website + assert "expected.sha256" in website + assert 'actual.digest !== `sha256:${expected.sha256}`' in website + assert "publishedRelease.version" in website + assert "V1.0.1" not in website + index + assert "releases/latest/download" in readme + assert "releases/tag/v1.0.1" not in readme + release_docs = read("docs/release.md") + assert "release_notes` input" in release_docs + assert "GitHub release API" in release_docs + assert "passes this template directly" not in release_docs + + def test_shin_linux_release_includes_native_obs_dock_dependencies() -> None: app_gradle = read("android/app/build.gradle.kts") cmake = read("obs-plugin/CMakeLists.txt") diff --git a/website/index.html b/website/index.html index 846c5e80..b9467c41 100644 --- a/website/index.html +++ b/website/index.html @@ -4,19 +4,19 @@ - + - - + + - - + + - OpenStream V1.0.1 | Wireless OBS Camera + OpenStream | Wireless OBS Camera diff --git a/website/package-lock.json b/website/package-lock.json index 501de779..7d81af83 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -1,19 +1,18 @@ { "name": "openstream-website", - "version": "1.0.1", + "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "openstream-website", - "version": "1.0.1", + "version": "0.0.0", "dependencies": { "@vitejs/plugin-react": "^6.0.3", "react": "^19.2.7", "react-dom": "^19.2.7", "vite": "^8.1.4" - }, - "devDependencies": {} + } }, "node_modules/@emnapi/core": { "version": "1.11.1", diff --git a/website/src/main.jsx b/website/src/main.jsx index 38992f46..f12c98a4 100644 --- a/website/src/main.jsx +++ b/website/src/main.jsx @@ -1,29 +1,107 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import { createRoot } from "react-dom/client"; import "./styles.css"; -const release = "https://github.com/YashasVM/OpenStream/releases/latest/download"; +const repository = "https://github.com/YashasVM/OpenStream"; +const latestReleaseApi = "https://api.github.com/repos/YashasVM/OpenStream/releases/latest"; +const release = `${repository}/releases/latest`; +const releaseDownload = `${release}/download`; const links = { - apk: `${release}/openstream-android.apk`, - installer: `${release}/openstream-obs-plugin-installer-windows-x64.exe`, - zip: `${release}/openstream-obs-windows-x64.zip`, - release: "https://github.com/YashasVM/OpenStream/releases/latest", - repo: "https://github.com/YashasVM/OpenStream", - issues: "https://github.com/YashasVM/OpenStream/issues", - setup: "https://github.com/YashasVM/OpenStream/blob/main/docs/set-up.md", + apk: `${releaseDownload}/openstream-android.apk`, + installer: `${releaseDownload}/openstream-obs-plugin-installer-windows-x64.exe`, + zip: `${releaseDownload}/openstream-obs-windows-x64.zip`, + release, + repo: repository, + issues: `${repository}/issues`, + setup: `${repository}/blob/main/docs/set-up.md`, }; -function Header() { +const expectedReleaseAssets = [ + "openstream-android.apk", + "openstream-obs-windows-x64.zip", + "openstream-obs-plugin-installer-windows-x64.exe", + "shin-obs-linux-x86_64.tar.gz", +]; + +function isRecord(value) { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +async function loadPublishedRelease() { + const response = await fetch(latestReleaseApi, { + headers: { Accept: "application/vnd.github+json" }, + }); + if (!response.ok) throw new Error("Latest release metadata is unavailable"); + + const releaseData = await response.json(); + if (!isRecord(releaseData) || typeof releaseData.tag_name !== "string" || + typeof releaseData.html_url !== "string" || !Array.isArray(releaseData.assets)) { + throw new Error("Latest release metadata is invalid"); + } + + const publishedAssets = new Map( + releaseData.assets + .filter((asset) => isRecord(asset) && typeof asset.name === "string") + .map((asset) => [asset.name, asset]), + ); + const manifestAsset = publishedAssets.get("release-manifest.json"); + if (!isRecord(manifestAsset) || typeof manifestAsset.browser_download_url !== "string") { + throw new Error("Latest release has no manifest"); + } + + const manifestResponse = await fetch(manifestAsset.browser_download_url); + if (!manifestResponse.ok) throw new Error("Published release manifest is unavailable"); + const manifest = await manifestResponse.json(); + if (!isRecord(manifest) || manifest.schemaVersion !== 1 || + typeof manifest.productVersion !== "string" || + !/^\d+\.\d+\.\d+$/.test(manifest.productVersion) || + manifest.tag !== releaseData.tag_name || manifest.tag !== `v${manifest.productVersion}` || + !isRecord(manifest.artifacts)) { + throw new Error("Published release manifest is invalid"); + } + + const matchedAssets = {}; + for (const name of expectedReleaseAssets) { + const expected = manifest.artifacts[name]; + const actual = publishedAssets.get(name); + const checksum = publishedAssets.get(`${name}.sha256`); + if (!isRecord(expected) || !Number.isSafeInteger(expected.size) || expected.size <= 0 || + typeof expected.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(expected.sha256) || + !isRecord(actual) || actual.size !== expected.size || + actual.digest !== `sha256:${expected.sha256}` || + typeof actual.browser_download_url !== "string" || !isRecord(checksum) || + typeof checksum.browser_download_url !== "string") { + throw new Error(`Published release asset does not match the manifest: ${name}`); + } + + const checksumResponse = await fetch(checksum.browser_download_url); + if (!checksumResponse.ok) throw new Error(`Published checksum is unavailable: ${name}`); + const checksumText = (await checksumResponse.text()).trim(); + if (checksumText !== `${expected.sha256} ${name}`) { + throw new Error(`Published checksum does not match the manifest: ${name}`); + } + matchedAssets[name] = actual.browser_download_url; + } + + return { + version: manifest.productVersion, + releaseUrl: releaseData.html_url, + assets: matchedAssets, + }; +} + +function Header({ publishedRelease }) { + const releaseLabel = publishedRelease ? `v${publishedRelease.version}` : "Latest release"; return (
- OPENSTREAMV1.0.1 + OPENSTREAM{releaseLabel} - Download V1.0.1 + Download latest
); } @@ -59,7 +137,7 @@ function Hero() {

Your Android phone.
Now an OBS camera.

-

Low-latency 1080p30 video, AAC audio, live camera controls, and reliable reconnects over local Wi-Fi.

+

1080p30 video, AAC audio, and live camera controls over local Wi-Fi.

Download for Android Install OBS plugin @@ -68,7 +146,7 @@ function Hero() {
-
▣ 1080p30⌁ SRT 120 ms▥ AAC audio⌁ Local Wi-Fi
+
▣ 1080p30⌁ SRT transport▥ AAC audio⌁ Local Wi-Fi
); } @@ -76,7 +154,7 @@ function Hero() { const benefits = [ ["⌕", "Smooth live zoom", "Zoom responds continuously while you drag the OBS slider, without stale commands piling up."], ["▤", "One phone camera", "Connect one phone to OBS and reuse its source across your scenes."], - ["↻", "Fast reconnects", "Discovery, bounded queues, and reservation-aware recovery keep brief Wi-Fi drops manageable."], + ["↻", "Connection recovery", "See connection status and reconnect after a network interruption."], ["▥", "Separate AAC audio", "Phone microphone audio arrives in its own OBS mixer channel, separate from desktop audio."], ]; @@ -95,20 +173,26 @@ function ControlDock() { function Features() { return (
-

Control the shot
without leaving OBS.

The native dock keeps the controls you touch most beside your preview—not buried in source properties.

+

Control the shot
without leaving OBS.

Use source properties to select the phone and control the camera. Linux also includes a dock beside the preview.

{benefits.map(([icon, title, copy]) =>

{title}

{copy}

)}
); } -function Downloads() { +function Downloads({ publishedRelease }) { + const assets = publishedRelease?.assets; + const releaseUrl = publishedRelease?.releaseUrl ?? links.release; + const releaseDescription = publishedRelease + ? `the matched ${publishedRelease.version} release` + : "the latest published release"; return (
-

Install both sides.
Start shooting.

Install OpenStream on your phone and the native plugin on your OBS computer from the same verified V1.0.1 release.

View V1.0.1 release notes →
+

Install both sides.
Start shooting.

Install OpenStream on your phone and the native plugin on your OBS computer from {releaseDescription}.

View release notes →
- - -

Release integrity included.
SHA-256 checksums are published alongside the V1.0.1 release.

+ + + {assets && } +

Release integrity included.
Published release downloads include SHA-256 checksums.

); @@ -119,7 +203,7 @@ const steps = [ ["02", "Install the OBS plugin", "Close OBS, run the Windows installer once, then reopen OBS Studio."], ["03", "Add your camera", "Add one OpenStream source in OBS. Use Add Existing to reuse it in other scenes."], ["04", "Pair on local Wi-Fi", "Choose your OBS computer on the phone. Enable manual receive in OBS if discovery is blocked."], - ["05", "Frame and go live", "Open the Camera Control dock, verify audio, adjust zoom or lens, and stream."], + ["05", "Frame and go live", "Open the source controls, verify audio, adjust zoom or lens, and stream."], ]; function Setup() { @@ -142,23 +226,62 @@ function Pipeline() { ); } -function Compatibility() { +function Compatibility({ publishedRelease }) { + const computerSupport = publishedRelease + ? "Windows x64 and Linux x86_64 with OBS Studio" + : "Windows x64 with OBS Studio"; + const currentLimits = publishedRelease + ? "macOS packages and adaptive bitrate are not included" + : "Linux and macOS packages and adaptive bitrate are not included in this release"; return (

Built for a strong LAN.

Use 5 GHz or Wi-Fi 6, keep both devices on the same subnet, and disable VPN or client isolation during first setup.

- +
); } function Footer() { return ( - + ); } function App() { - return <>Skip to content