Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abd4dc9aca | ||
|
|
1571d7ecea | ||
|
|
76b2942db1 | ||
|
|
af7d2c6d24 | ||
|
|
104326d05a | ||
|
|
e050b81d6c | ||
|
|
18610bd9dc | ||
|
|
43a501c06c | ||
|
|
14fa00af3f | ||
|
|
d287a90048 |
@@ -1,15 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Creates a (draft) Gitea Release for the tag that triggered
|
# Creates a (draft) Gitea Release for the tag that triggered
|
||||||
# .gitea/workflows/release.yml, and uploads every archive in $DIST_DIR as a
|
# .gitea/workflows/release.yml, and uploads every archive in $DIST_DIR as a
|
||||||
# release asset.
|
# release asset. Draft because nobody has run this plugin in the OBS GUI on
|
||||||
#
|
# any platform yet -- a human still opens it and clicks Publish once that's
|
||||||
# RELEASE GATE: see the README's `## Status` section and
|
# no longer true (or once they're satisfied regardless).
|
||||||
# third_party/livekit/README.md. The WebRTC/OpenH264 attribution question
|
|
||||||
# ("C1") is unresolved -- this script does not decide that question, it just
|
|
||||||
# makes sure the generated release notes put the reminder where whoever
|
|
||||||
# publishes the draft will actually read it. Pushing a version tag is the
|
|
||||||
# human decision this whole workflow hangs off of; this script does not add
|
|
||||||
# or remove any judgment about whether that decision was the right one.
|
|
||||||
#
|
#
|
||||||
# Required env: GITEA_TOKEN, SERVER, OWNER, REPO, TAG, SHA, DIST_DIR
|
# Required env: GITEA_TOKEN, SERVER, OWNER, REPO, TAG, SHA, DIST_DIR
|
||||||
# Optional env: MACOS_BUNDLE_FOUND ("true"/"false", default "false")
|
# Optional env: MACOS_BUNDLE_FOUND ("true"/"false", default "false")
|
||||||
@@ -27,27 +21,11 @@ MACOS_BUNDLE_FOUND="${MACOS_BUNDLE_FOUND:-false}"
|
|||||||
if [ "${MACOS_BUNDLE_FOUND}" = "true" ]; then
|
if [ "${MACOS_BUNDLE_FOUND}" = "true" ]; then
|
||||||
MACOS_NOTE="This archive contains a \`.plugin\` bundle."
|
MACOS_NOTE="This archive contains a \`.plugin\` bundle."
|
||||||
else
|
else
|
||||||
MACOS_NOTE="This archive is packaged as a bare \`streamer-tools-camera.so\` (the layout \`build/package/\` currently produces on macOS), **not** an OBS.app-loadable \`.plugin\` bundle. It will not load in the OBS GUI as-is."
|
MACOS_NOTE="This archive is packaged as a bare \`streamer-tools-camera.so\` (the layout \`build/package/\` currently produces on macOS), **not** an OBS.app-loadable \`.plugin\` bundle. It will not load in the OBS GUI as-is -- see the \"macOS packaging gap\" section of \`README.md\`."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
NOTES_FILE="$(mktemp)"
|
NOTES_FILE="$(mktemp)"
|
||||||
cat > "${NOTES_FILE}" <<EOF
|
cat > "${NOTES_FILE}" <<EOF
|
||||||
> **This build has not been cleared for redistribution.** The plugin
|
|
||||||
> statically/dynamically pulls in Google WebRTC and OpenH264 code through the
|
|
||||||
> LiveKit SDK, and whether that can be redistributed as a public download --
|
|
||||||
> the "C1" attribution/patent question -- has not been resolved. See the
|
|
||||||
> \`## Status\` section of \`README.md\` and \`third_party/livekit/README.md\`
|
|
||||||
> for the specifics. By publishing this release, you are personally taking on
|
|
||||||
> that open question -- if C1 hasn't been signed off on, don't publish it.
|
|
||||||
>
|
|
||||||
> (The separate GPLv2/Apache-2.0 license-compatibility question, "C2", is
|
|
||||||
> resolved: this project's own first-party code is Apache-2.0, matching the
|
|
||||||
> vendored LiveKit binaries.)
|
|
||||||
>
|
|
||||||
> This release was created as a **draft**. It stays invisible to anyone
|
|
||||||
> without write access to this repo until someone with write access opens it
|
|
||||||
> here and clicks Publish -- a second, deliberate step past pushing the tag.
|
|
||||||
|
|
||||||
# streamer-tools Camera Plugin -- ${TAG}
|
# streamer-tools Camera Plugin -- ${TAG}
|
||||||
|
|
||||||
Built from commit \`${SHA}\`.
|
Built from commit \`${SHA}\`.
|
||||||
@@ -55,49 +33,25 @@ Built from commit \`${SHA}\`.
|
|||||||
**Nobody has yet run this plugin in the OBS GUI, on any platform.** See "What
|
**Nobody has yet run this plugin in the OBS GUI, on any platform.** See "What
|
||||||
is verified, and how" in \`README.md\` for exactly what has and has not been
|
is verified, and how" in \`README.md\` for exactly what has and has not been
|
||||||
checked, including which claims are backed by automated tests versus a human
|
checked, including which claims are backed by automated tests versus a human
|
||||||
watching OBS.
|
watching OBS. This is why the release is a draft -- open it and click Publish
|
||||||
|
once you're satisfied.
|
||||||
|
|
||||||
| Platform | Archive | Notes |
|
| Platform | Archive | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Linux (x64) | \`streamer-tools-camera-${TAG}-linux-x64.zip\` | Functionally complete and verified end to end against a real LiveKit server and a real libobs (see README); OBS GUI itself still unverified |
|
| Linux (x64) | \`streamer-tools-camera-${TAG}-linux-x64.zip\` | Functionally complete and verified end to end against a real LiveKit server and a real libobs (see README); OBS GUI itself still unverified |
|
||||||
| Windows (x64) | \`streamer-tools-camera-${TAG}-windows-x64.zip\` | Built and tested by this workflow's Windows job; the WinHTTP backend has never been exercised against a real streamer-tools server, only a loopback test server -- see README's Windows CI section |
|
| Windows (x64) | \`streamer-tools-camera-${TAG}-windows-x64.zip\` | Built and tested by this workflow's Windows job; the WinHTTP backend has never been exercised against a real streamer-tools server, only a loopback test server -- see README's Windows CI section |
|
||||||
| macOS | \`streamer-tools-camera-${TAG}-macos.zip\` | Built and tested by this workflow's macOS job. ${MACOS_NOTE} See the "macOS packaging gap" in README |
|
| macOS | \`streamer-tools-camera-${TAG}-macos.zip\` | Built and tested by this workflow's macOS job. ${MACOS_NOTE} |
|
||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
|
|
||||||
### Linux
|
Extract the archive into your OBS plugins folder for your platform (the
|
||||||
|
default locations are easy to find online -- typically
|
||||||
\`\`\`
|
\`~/.config/obs-studio/plugins/\` on Linux, \`%APPDATA%\\obs-studio\\plugins\\\`
|
||||||
mkdir -p ~/.config/obs-studio/plugins/streamer-tools-camera
|
on Windows, \`~/Library/Application Support/obs-studio/plugins/\` on macOS).
|
||||||
unzip streamer-tools-camera-${TAG}-linux-x64.zip -d /tmp/stplugin-camera
|
Each archive's top-level folder already matches the shape OBS expects there,
|
||||||
cp -r /tmp/stplugin-camera/bin /tmp/stplugin-camera/data \\
|
so extracting is the whole install step. Then in OBS: Sources -> \`+\` ->
|
||||||
~/.config/obs-studio/plugins/streamer-tools-camera/
|
"streamer-tools Camera" -> fill in the server URL, room slug and read key
|
||||||
\`\`\`
|
from the room's settings page -> "Refresh camera list" -> pick a camera.
|
||||||
|
|
||||||
Start OBS, then Sources -> \`+\` -> "streamer-tools Camera" -> fill in the
|
|
||||||
server URL, room slug and read key from the room's settings page ->
|
|
||||||
"Refresh camera list" -> pick a camera. This is the same drop-in layout
|
|
||||||
README's "Testing this by hand" documents for a source build, adapted for a
|
|
||||||
downloaded zip -- known-good on Linux.
|
|
||||||
|
|
||||||
### Windows (installation path not yet verified in real OBS)
|
|
||||||
|
|
||||||
Per \`AddExtraModulePaths()\` in obs-studio's \`UI/window-basic-main.cpp\`, OBS
|
|
||||||
on Windows searches a plugins directory for \`bin\\64bit\\<name>.dll\` plus a
|
|
||||||
sibling \`data\\\`. Unzip the archive and copy its \`bin\\\` and \`data\\\` into
|
|
||||||
your OBS plugins directory (typically
|
|
||||||
\`%APPDATA%\\obs-studio\\plugins\\streamer-tools-camera\\\`), matching the
|
|
||||||
Linux layout above. This has not been confirmed against a real OBS install on
|
|
||||||
Windows -- report back if you try it.
|
|
||||||
|
|
||||||
### macOS (installation path not yet verified in real OBS; packaging gap)
|
|
||||||
|
|
||||||
OBS on macOS loads plugins as \`<name>.plugin\` bundles under
|
|
||||||
\`~/Library/Application Support/obs-studio/plugins/\`. As of this release,
|
|
||||||
this project's \`build/package/\` output on macOS is **not yet that bundle
|
|
||||||
shape** -- see the "macOS packaging gap" section of \`README.md\`. Treat the
|
|
||||||
macOS archive here as a build-verification artifact, not a working
|
|
||||||
drop-in, until that gap is closed.
|
|
||||||
|
|
||||||
## What this is
|
## What this is
|
||||||
|
|
||||||
|
|||||||
+83
-18
@@ -15,22 +15,22 @@ name: Build
|
|||||||
# .gitea/workflows/release.yml, so the two workflows can't drift apart --
|
# .gitea/workflows/release.yml, so the two workflows can't drift apart --
|
||||||
# edit the scripts, not either workflow, to change how a platform builds.
|
# edit the scripts, not either workflow, to change how a platform builds.
|
||||||
#
|
#
|
||||||
# RELEASE GATE: this workflow only builds, tests, and uploads CI-internal
|
# This workflow only builds, tests, and uploads CI-internal workflow
|
||||||
# workflow artifacts (actions/upload-artifact, below) -- it does not create a
|
# artifacts (actions/upload-artifact, below) -- it does not create a Gitea
|
||||||
# Gitea Release, push a tag-triggered publish, or otherwise distribute
|
# Release. .gitea/workflows/release.yml is that publish step, gated on a
|
||||||
# binaries publicly, and it must not start doing so without explicit owner
|
# pushed version tag rather than on every push.
|
||||||
# sign-off on the WebRTC/OpenH264 attribution question tracked in
|
|
||||||
# third_party/livekit/README.md and the README's top-level Status section.
|
|
||||||
# (The separate GPLv2/Apache-2.0 license-compatibility question is resolved:
|
|
||||||
# this project's own code is Apache-2.0.) If a real release/publish step is
|
|
||||||
# ever added here, it must carry that same gate.
|
|
||||||
#
|
|
||||||
# (.gitea/workflows/release.yml is that publish step, gated on a pushed
|
|
||||||
# version tag rather than on every push -- see the gate reminder baked into
|
|
||||||
# its generated release notes.)
|
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
# Excludes tag pushes -- a bare `push:` matches every ref push, tags
|
||||||
|
# included, which meant tagging a release triggered THIS workflow's full
|
||||||
|
# 3-platform build (Windows and all) at the same time as
|
||||||
|
# release.yml's own -- two full Windows builds serialized behind the
|
||||||
|
# runner's capacity:1, for one tag push. release.yml already covers
|
||||||
|
# exactly this build (plus packaging) on every `v*` tag; this workflow's
|
||||||
|
# job is ordinary commits.
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -80,11 +80,30 @@ jobs:
|
|||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: .deps
|
path: .deps
|
||||||
key: obs-deps-${{ runner.os }}-${{ hashFiles('cmake/macos/buildspec.cmake', 'cmake/common/buildspec_common.cmake', 'buildspec.json') }}
|
key: obs-deps-v2-${{ runner.os }}-${{ hashFiles('cmake/macos/buildspec.cmake', 'cmake/common/buildspec_common.cmake', 'buildspec.json') }}
|
||||||
|
|
||||||
- name: Configure, build, test, verify
|
- name: Configure, build, test, verify
|
||||||
run: .gitea/scripts/macos-build.sh
|
run: .gitea/scripts/macos-build.sh
|
||||||
|
|
||||||
|
- name: Drop non-relocatable OBS build tree before caching
|
||||||
|
# cmake/common/buildspec_common.cmake's obs-studio sub-build writes
|
||||||
|
# an out-of-source CMakeCache.txt (.deps/obs-studio-*/build_*) that
|
||||||
|
# bakes in this job's absolute checkout path. The next run's
|
||||||
|
# checkout lands at a *different* absolute path, so restoring that
|
||||||
|
# directory from the cache above makes CMake refuse to reconfigure
|
||||||
|
# it ("CMakeCache.txt directory ... is different than the directory
|
||||||
|
# ... where CMakeCache.txt was created"). Everything that actually
|
||||||
|
# needs to survive between runs -- the extracted source, and the
|
||||||
|
# already-installed libobs package under .deps/cmake, .deps/include,
|
||||||
|
# .deps/lib -- has no such path baked in and is unaffected. Delete
|
||||||
|
# only the intermediate build tree, after it has already done its
|
||||||
|
# job (libobs is built and installed by this point), so the cache
|
||||||
|
# saved at the end of this job contains nothing that requires the
|
||||||
|
# path it was created under.
|
||||||
|
if: always()
|
||||||
|
continue-on-error: true
|
||||||
|
run: rm -rf .deps/obs-studio-*/build_*
|
||||||
|
|
||||||
- name: Upload plugin
|
- name: Upload plugin
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
@@ -99,21 +118,51 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install build dependencies
|
- name: Verify build dependencies
|
||||||
# winvm-builder is a self-hosted act_runner labeled "windows-latest";
|
# winvm-builder is a self-hosted act_runner labeled "windows-latest";
|
||||||
# it is NOT the GitHub-hosted image, so none of that image's
|
# it is NOT the GitHub-hosted image, so none of that image's
|
||||||
# preinstalled tooling (cmake included) can be assumed present.
|
# preinstalled tooling can be assumed present. This used to be
|
||||||
uses: lukka/get-cmake@latest
|
# `uses: lukka/get-cmake@latest`, which re-downloaded and
|
||||||
|
# re-extracted CMake + Ninja on every single run -- its own cache
|
||||||
|
# (routed through this act_runner's cache server) reported a "cloud
|
||||||
|
# cache miss" on every run even immediately after a successful save,
|
||||||
|
# and separately the extraction step alone measured ~7.5 minutes on
|
||||||
|
# this VM (consistent with Defender real-time scanning, not raw I/O)
|
||||||
|
# -- together the dominant cost of every Windows CI run. CMake and
|
||||||
|
# Ninja are now installed once, directly on winvm-builder's system
|
||||||
|
# PATH (C:\BuildTools\cmake\bin, C:\BuildTools\ninja -- see the
|
||||||
|
# README's "Windows runner: persistent build tools" section for
|
||||||
|
# exactly what that machine has installed and how to redo it if the
|
||||||
|
# VM is ever rebuilt). This step just fails loudly if that ever
|
||||||
|
# stops being true, rather than silently falling back to a slow
|
||||||
|
# re-download.
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
cmake --version
|
||||||
|
ninja --version
|
||||||
|
|
||||||
- name: Cache OBS SDK bootstrap deps
|
- name: Cache OBS SDK bootstrap deps
|
||||||
# See the matching step in the macOS job above for why this is
|
# See the matching step in the macOS job above for why this is
|
||||||
# needed: cmake/windows/buildspec.cmake's own download logic is
|
# needed: cmake/windows/buildspec.cmake's own download logic is
|
||||||
# already idempotent, it just never gets the chance because .deps/
|
# already idempotent, it just never gets the chance because .deps/
|
||||||
# lives inside the checkout and is wiped by every fresh clone.
|
# lives inside the checkout and is wiped by every fresh clone.
|
||||||
|
#
|
||||||
|
# The `v2` in the key: actions/cache never overwrites an existing
|
||||||
|
# key -- once a key has a saved entry, every later job's save step is
|
||||||
|
# skipped as a no-op, cache hit or not. The very first job ever
|
||||||
|
# to populate this cache did so BEFORE the "Drop non-relocatable OBS
|
||||||
|
# build tree" step below existed, so its save included the bad
|
||||||
|
# build_x86 directory -- and because saves under an existing key are
|
||||||
|
# permanently skipped, every run after that kept restoring that same
|
||||||
|
# bad entry forever, not "one more transitional run" as it looked at
|
||||||
|
# the time. Bumping the key is what actually forces a fresh save;
|
||||||
|
# bump it again (v3, ...) if this cache is ever found to be stale in
|
||||||
|
# a way a workflow change alone can't fix.
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: .deps
|
path: .deps
|
||||||
key: obs-deps-${{ runner.os }}-${{ hashFiles('cmake/windows/buildspec.cmake', 'cmake/common/buildspec_common.cmake', 'buildspec.json') }}
|
key: obs-deps-v2-${{ runner.os }}-${{ hashFiles('cmake/windows/buildspec.cmake', 'cmake/common/buildspec_common.cmake', 'buildspec.json') }}
|
||||||
|
|
||||||
- name: Configure, build, test, verify
|
- name: Configure, build, test, verify
|
||||||
# Windows PowerShell (powershell.exe), not PowerShell Core (pwsh) --
|
# Windows PowerShell (powershell.exe), not PowerShell Core (pwsh) --
|
||||||
@@ -123,6 +172,22 @@ jobs:
|
|||||||
shell: powershell
|
shell: powershell
|
||||||
run: ./.gitea/scripts/windows-build.ps1
|
run: ./.gitea/scripts/windows-build.ps1
|
||||||
|
|
||||||
|
- name: Drop non-relocatable OBS build tree before caching
|
||||||
|
# See the matching step in the macOS job above. Confirmed live on
|
||||||
|
# this runner: caching .deps/obs-studio-30.0.2/build_x86 as-is made
|
||||||
|
# every run's first configure attempt fail with a path mismatch
|
||||||
|
# against the job that populated the cache, falling back to
|
||||||
|
# -DSTPLUGIN_BOOTSTRAP_OBS=OFF and only succeeding because the
|
||||||
|
# already-installed libobs package (path-independent) was still
|
||||||
|
# found. That fallback masked the problem behind a misleading
|
||||||
|
# "::warning::OBS SDK bootstrap failed" every run instead of fixing
|
||||||
|
# it. Deleting the build tree here, after libobs is already built
|
||||||
|
# and installed, is the actual fix.
|
||||||
|
if: always()
|
||||||
|
continue-on-error: true
|
||||||
|
shell: powershell
|
||||||
|
run: Remove-Item -Recurse -Force .deps\obs-studio-*\build_* -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
- name: Upload plugin
|
- name: Upload plugin
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
|
|||||||
@@ -4,24 +4,11 @@ name: Release
|
|||||||
# a (draft) Gitea Release for it, so the project owner and other directors
|
# a (draft) Gitea Release for it, so the project owner and other directors
|
||||||
# can grab a ready-to-use build instead of compiling from source.
|
# can grab a ready-to-use build instead of compiling from source.
|
||||||
#
|
#
|
||||||
# RELEASE GATE -- READ BEFORE TAGGING
|
# Runs only on a pushed version tag (see `on.push.tags` below) -- never on an
|
||||||
# ------------------------------------------------------------------
|
# ordinary push or PR, unlike build.yml. The release it creates is a DRAFT:
|
||||||
# This workflow runs ONLY on a pushed version tag (see `on.push.tags` below)
|
# it stays invisible to anyone without write access until a human explicitly
|
||||||
# -- it never runs on an ordinary push or PR, unlike build.yml. Pushing a
|
# opens it and clicks Publish, since nobody has run this plugin in the OBS
|
||||||
# tag is therefore the one deliberate human act that starts it, and the
|
# GUI on any platform yet.
|
||||||
# release it creates is a DRAFT: it stays invisible to anyone without write
|
|
||||||
# access until a human explicitly opens it and clicks Publish. That is a
|
|
||||||
# second deliberate act past the tag push.
|
|
||||||
#
|
|
||||||
# Both of those are process, not a legal opinion. The actual open question --
|
|
||||||
# whether this plugin's bundled WebRTC/OpenH264 code (via LiveKit) can be
|
|
||||||
# redistributed as a public download at all -- is tracked as "C1" in the
|
|
||||||
# README's `## Status` section and in third_party/livekit/README.md, and it
|
|
||||||
# is NOT resolved. Nothing here resolves it; the generated release notes put
|
|
||||||
# a reminder of that fact at the top of every release this workflow creates,
|
|
||||||
# specifically so nobody publishes a draft without seeing it again first.
|
|
||||||
# (The separate GPLv2/Apache-2.0 question, "C2", *is* resolved -- see
|
|
||||||
# README.)
|
|
||||||
#
|
#
|
||||||
# The actual per-platform build commands live in .gitea/scripts/ and are the
|
# The actual per-platform build commands live in .gitea/scripts/ and are the
|
||||||
# same scripts .gitea/workflows/build.yml uses, so this workflow can't drift
|
# same scripts .gitea/workflows/build.yml uses, so this workflow can't drift
|
||||||
@@ -55,7 +42,19 @@ jobs:
|
|||||||
command -v zip >/dev/null || sudo apt-get install -y -qq zip
|
command -v zip >/dev/null || sudo apt-get install -y -qq zip
|
||||||
out="streamer-tools-camera-${GITEA_REF_NAME}-linux-x64.zip"
|
out="streamer-tools-camera-${GITEA_REF_NAME}-linux-x64.zip"
|
||||||
root="$(pwd)"
|
root="$(pwd)"
|
||||||
( cd build/package && zip -r "${root}/${out}" . )
|
|
||||||
|
# Wrap build/package/'s bin/+data/ inside a top-level
|
||||||
|
# streamer-tools-camera/ directory, matching the plugin directory
|
||||||
|
# name OBS itself expects under <config>/obs-studio/plugins/ (see
|
||||||
|
# obs-adapter/CMakeLists.txt's staging comment). This makes the
|
||||||
|
# archive a straight `unzip -d ~/.config/obs-studio/plugins/`
|
||||||
|
# drop-in -- no manual `cp -r bin data` step required.
|
||||||
|
stage="$(mktemp -d)"
|
||||||
|
mkdir -p "${stage}/streamer-tools-camera"
|
||||||
|
cp -r build/package/. "${stage}/streamer-tools-camera/"
|
||||||
|
( cd "${stage}" && zip -r "${root}/${out}" streamer-tools-camera )
|
||||||
|
rm -rf "${stage}"
|
||||||
|
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
mv "${out}" "dist/${out}"
|
mv "${out}" "dist/${out}"
|
||||||
ls -la dist
|
ls -la dist
|
||||||
@@ -91,13 +90,18 @@ jobs:
|
|||||||
root="$(pwd)"
|
root="$(pwd)"
|
||||||
mkdir -p dist
|
mkdir -p dist
|
||||||
|
|
||||||
# macOS packaging is being fixed separately (see the "macOS
|
# Look for a *.plugin bundle rather than assuming its exact final
|
||||||
# packaging gap" in README.md). Once it lands, build/package/ (or
|
# location, falling back to packaging build/package/ as-is (a
|
||||||
# wherever that work stages its output) should contain a
|
# bare .so, not a loadable bundle) only if the bundle step didn't
|
||||||
# `<name>.plugin` bundle directory -- look for one rather than
|
# run or produced nothing -- see the macOS packaging gap in
|
||||||
# assuming its exact final location, and fall back to packaging
|
# README.md for when that fallback path is actually live. The
|
||||||
# build/package/ as-is (today's actual, non-bundle output) if none
|
# bundle itself is zipped at the archive's top level (cd into its
|
||||||
# is found yet.
|
# parent, zip just the bundle dir) so the archive is already a
|
||||||
|
# straight `unzip -d ~/Library/Application\ Support/obs-studio/
|
||||||
|
# plugins/` drop-in -- no wrapping needed here, unlike
|
||||||
|
# Linux/Windows above, because OBS wants the whole *.plugin
|
||||||
|
# bundle directly under plugins/, not nested under a named
|
||||||
|
# subdirectory.
|
||||||
bundle="$(find build -maxdepth 4 -type d -name '*.plugin' 2>/dev/null | head -n1 || true)"
|
bundle="$(find build -maxdepth 4 -type d -name '*.plugin' 2>/dev/null | head -n1 || true)"
|
||||||
if [ -n "${bundle}" ]; then
|
if [ -n "${bundle}" ]; then
|
||||||
echo "Found macOS .plugin bundle: ${bundle}"
|
echo "Found macOS .plugin bundle: ${bundle}"
|
||||||
@@ -127,8 +131,16 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install build dependencies
|
- name: Verify build dependencies
|
||||||
uses: lukka/get-cmake@latest
|
# See build.yml's Windows job for why this is no longer
|
||||||
|
# lukka/get-cmake@latest -- CMake and Ninja are installed once,
|
||||||
|
# directly on winvm-builder's system PATH; this just fails loudly
|
||||||
|
# if that ever stops being true.
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
cmake --version
|
||||||
|
ninja --version
|
||||||
|
|
||||||
- name: Configure, build, test, verify
|
- name: Configure, build, test, verify
|
||||||
# Windows PowerShell (powershell.exe), not PowerShell Core (pwsh) --
|
# Windows PowerShell (powershell.exe), not PowerShell Core (pwsh) --
|
||||||
@@ -143,7 +155,23 @@ jobs:
|
|||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
$out = "streamer-tools-camera-$env:GITEA_REF_NAME-windows-x64.zip"
|
$out = "streamer-tools-camera-$env:GITEA_REF_NAME-windows-x64.zip"
|
||||||
New-Item -ItemType Directory -Force -Path dist | Out-Null
|
New-Item -ItemType Directory -Force -Path dist | Out-Null
|
||||||
Compress-Archive -Path build\package\* -DestinationPath "dist\$out" -Force
|
|
||||||
|
# Wrap build\package\'s bin\+data\ inside a top-level
|
||||||
|
# streamer-tools-camera\ directory, matching the plugin directory
|
||||||
|
# name OBS itself expects under %APPDATA%\obs-studio\plugins\ (see
|
||||||
|
# obs-adapter/CMakeLists.txt's staging comment). This makes the
|
||||||
|
# archive a straight `Expand-Archive -DestinationPath
|
||||||
|
# $env:APPDATA\obs-studio\plugins\` drop-in -- no manual copy step
|
||||||
|
# required. Compress-Archive includes the source folder's own name
|
||||||
|
# as the archive root when given a single directory path, so
|
||||||
|
# staging under a streamer-tools-camera\ dir is enough on its own.
|
||||||
|
$stage = Join-Path $env:TEMP "stplugin-stage-$([guid]::NewGuid())"
|
||||||
|
$pluginDir = Join-Path $stage "streamer-tools-camera"
|
||||||
|
New-Item -ItemType Directory -Force -Path $pluginDir | Out-Null
|
||||||
|
Copy-Item -Path build\package\* -Destination $pluginDir -Recurse
|
||||||
|
Compress-Archive -Path $pluginDir -DestinationPath "dist\$out" -Force
|
||||||
|
Remove-Item -Recurse -Force $stage
|
||||||
|
|
||||||
Get-ChildItem dist
|
Get-ChildItem dist
|
||||||
env:
|
env:
|
||||||
GITEA_REF_NAME: ${{ github.ref_name }}
|
GITEA_REF_NAME: ${{ github.ref_name }}
|
||||||
|
|||||||
@@ -8,43 +8,36 @@ Media-Source path for directors. Full design:
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
**Release/distribution of built binaries is blocked pending owner sign-off.**
|
This project's own code is Apache-2.0 (relicensed from GPL-2.0-or-later to
|
||||||
This plugin statically/dynamically pulls in Google WebRTC and OpenH264 code
|
match the vendored LiveKit binaries, which are also Apache-2.0 — see
|
||||||
through the LiveKit SDK — a real patent/royalty question (OpenH264/WebRTC)
|
`LICENSE` and `NOTICE`, and `third_party/livekit/` for LiveKit's own).
|
||||||
that only the project owner can decide. Nothing in this repo should be built
|
|
||||||
into a package and handed out, posted, or attached to a public release until
|
|
||||||
that sign-off happens. See `third_party/livekit/README.md` for the specifics
|
|
||||||
of what is and is not currently known/shipped on the licensing side. (CI in
|
|
||||||
`.gitea/workflows/build.yml` only builds, tests, and uploads CI-internal
|
|
||||||
build artifacts — it does not create a Gitea Release or otherwise publish
|
|
||||||
anything publicly.
|
|
||||||
|
|
||||||
`.gitea/workflows/release.yml` is the mechanism that *would* publish a
|
`.gitea/workflows/build.yml` builds, tests, and uploads CI-internal build
|
||||||
release, but it does not run automatically: it is gated on someone pushing a
|
artifacts on every push. `.gitea/workflows/release.yml` packages a tagged
|
||||||
`v*` tag, which is the actual sign-off gate in practice — don't push one
|
build (`v*`) into a **draft** Gitea Release; a human still needs to open it
|
||||||
until the owner has actually signed off on C1. When it does run, it packages
|
and click Publish.
|
||||||
each platform's `build/package/` (or macOS's bundle output, once that lands)
|
|
||||||
into a zip and creates a **draft** Gitea Release, whose generated release
|
|
||||||
notes lead with the same C1 reminder as this section, so whoever opens the
|
|
||||||
draft to publish it sees the open question again before doing so. Building
|
|
||||||
that mechanism is not the same as clearing C1 — it still requires the same
|
|
||||||
owner sign-off before a tag gets pushed.)
|
|
||||||
|
|
||||||
The separate license-compatibility question — this repository's own top-level
|
|
||||||
`LICENSE` was GPLv2 while the vendored LiveKit binaries are Apache-2.0, which
|
|
||||||
are incompatible — is **resolved**: the project owner has relicensed this
|
|
||||||
project's own first-party code to Apache-2.0, matching LiveKit. Everything in
|
|
||||||
this repo is now Apache-2.0, so there is no remaining GPL/Apache
|
|
||||||
incompatibility.
|
|
||||||
|
|
||||||
The plugin is **functionally complete on Linux and verified end to end there**
|
The plugin is **functionally complete on Linux and verified end to end there**
|
||||||
(module loads into real libobs, connects to a real LiveKit server through the
|
(module loads into real libobs, connects to a real LiveKit server through the
|
||||||
real streamer-tools API shape, and pushes decoded frames into
|
real streamer-tools API shape, and pushes decoded frames into
|
||||||
`obs_source_output_video`/`_audio`).
|
`obs_source_output_video`/`_audio`).
|
||||||
|
|
||||||
It has **not been run in the OBS GUI on any platform.** macOS builds the real
|
**First confirmed OBS GUI load: Windows, 2026-09-09** — the v0.1.0 release
|
||||||
module in CI but its artifact is not yet loadable (see the macOS packaging gap
|
artifact loaded into OBS 32.2.2 on Windows 11 (build 26200) on a director's
|
||||||
under CI).
|
machine, from
|
||||||
|
`C:\ProgramData\obs-studio\plugins\streamer-tools-camera\bin\64bit\`.
|
||||||
|
That retires "the module will not even load in a real OBS" for Windows. It
|
||||||
|
does **not** yet cover whether video renders correctly, colours, A/V sync or
|
||||||
|
latency — see "Not verified anywhere" below for what is still open. Linux and
|
||||||
|
macOS have still never been opened in the GUI; macOS builds the real module in
|
||||||
|
CI but its artifact is not yet loadable (see the macOS packaging gap under CI).
|
||||||
|
|
||||||
|
⚠️ **The install directory is not the same on every platform, and getting it
|
||||||
|
wrong fails silently.** On Windows it is
|
||||||
|
`C:\ProgramData\obs-studio\plugins\` (`GetProgramDataPath` →
|
||||||
|
`CSIDL_COMMON_APPDATA`), **not** `%APPDATA%\obs-studio\` — see the packaging
|
||||||
|
section. That mistake cost the director above an evening: OBS logs nothing at
|
||||||
|
all for a plugin it never finds.
|
||||||
|
|
||||||
**Windows CI is now green.** The run at `f27b1c0` is the first completed
|
**Windows CI is now green.** The run at `f27b1c0` is the first completed
|
||||||
green Windows job on this repository: the from-source libobs bootstrap
|
green Windows job on this repository: the from-source libobs bootstrap
|
||||||
@@ -54,8 +47,8 @@ suites pass, and `build\package\bin\64bit\streamer-tools-camera.dll`
|
|||||||
out of the job's own log body, not inferred from the job status. That also
|
out of the job's own log body, not inferred from the job status. That also
|
||||||
retires three previously-unproven items in one go: the `-A x64` argument fix,
|
retires three previously-unproven items in one go: the `-A x64` argument fix,
|
||||||
the PowerShell rewrite of the Windows steps, and the `add_subdirectory`
|
the PowerShell rewrite of the Windows steps, and the `add_subdirectory`
|
||||||
patch for `OBS::w32-pthreads`. Windows is still **unverified in the OBS GUI**,
|
patch for `OBS::w32-pthreads`. Windows has since been **loaded in the real OBS
|
||||||
exactly like the other two platforms. See "Where the Windows bootstrap got
|
GUI** (see above); Linux and macOS have not. See "Where the Windows bootstrap got
|
||||||
to" under CI below for the whole trace, and check current CI status rather
|
to" under CI below for the whole trace, and check current CI status rather
|
||||||
than trusting this paragraph's age.
|
than trusting this paragraph's age.
|
||||||
|
|
||||||
@@ -179,8 +172,10 @@ macOS packaging gap under CI.
|
|||||||
|
|
||||||
## Testing this by hand
|
## Testing this by hand
|
||||||
|
|
||||||
**Nobody has yet run this in the OBS GUI. That test is still outstanding on
|
**The module has been loaded in the OBS GUI on Windows once (2026-09-09, OBS
|
||||||
all three platforms.** To do it on Linux:
|
32.2.2 / Windows 11 26200) — nothing beyond "it loads and registers its source"
|
||||||
|
is confirmed there, and Linux and macOS have never been opened in the GUI at
|
||||||
|
all.** To do it on Linux:
|
||||||
|
|
||||||
```
|
```
|
||||||
mkdir -p ~/.config/obs-studio/plugins/streamer-tools-camera
|
mkdir -p ~/.config/obs-studio/plugins/streamer-tools-camera
|
||||||
@@ -236,7 +231,11 @@ livekit-server 1.13.6 in dev mode):
|
|||||||
| Two sources in one OBS process | same harness with a second source added: both connect with distinct nonce identities, both receive frames, both tear down cleanly |
|
| Two sources in one OBS process | same harness with a second source added: both connect with distinct nonce identities, both receive frames, both tear down cleanly |
|
||||||
|
|
||||||
**Not verified anywhere:**
|
**Not verified anywhere:**
|
||||||
- The OBS GUI, on any platform. No human has looked at this in OBS.
|
- Anything past module load in the OBS GUI. Windows 2026-09-09 confirms the
|
||||||
|
module loads and its source type appears; whether video actually renders
|
||||||
|
(right way up, right colours), what the A/V sync and latency look like, and
|
||||||
|
whether a publisher restarting mid-show recovers on screen are all still
|
||||||
|
unanswered. Linux and macOS have not been opened in the GUI at all.
|
||||||
- macOS beyond "CI builds and links the real module and the core tests pass".
|
- macOS beyond "CI builds and links the real module and the core tests pass".
|
||||||
Its artifact is a bare `.so` with a relative libobs install name and will
|
Its artifact is a bare `.so` with a relative libobs install name and will
|
||||||
not load in OBS.app — see the macOS packaging gap under CI.
|
not load in OBS.app — see the macOS packaging gap under CI.
|
||||||
@@ -284,6 +283,51 @@ and a permanently red CI teaches people to ignore CI. **Do not remove the
|
|||||||
warning:** a green job that quietly stopped building the plugin is worse than
|
warning:** a green job that quietly stopped building the plugin is worse than
|
||||||
a red one.
|
a red one.
|
||||||
|
|
||||||
|
### Windows runner: persistent build tools (2026-09-07)
|
||||||
|
|
||||||
|
`winvm-builder`'s Windows job used to install its own CMake + Ninja on every
|
||||||
|
single run via `uses: lukka/get-cmake@latest`. That action has its own
|
||||||
|
caching (routed through this act_runner's built-in cache server, the same
|
||||||
|
mechanism `.deps/`'s `actions/cache` step above relies on and that one does
|
||||||
|
work) but it never hit: every run logged `Cloud cache miss` against the same
|
||||||
|
cache key, even immediately after a run that logged a successful save under
|
||||||
|
that exact key -- some incompatibility between `lukka/get-cmake`'s bundled
|
||||||
|
cache client and this act_runner's cache-server implementation, not
|
||||||
|
"caching isn't configured." Separately, and the larger cost: the archive
|
||||||
|
extraction step alone measured **~7.5 minutes** for a 45MB zip on this VM
|
||||||
|
(13:11:14 to 13:18:48 in one captured run) -- consistent with Windows
|
||||||
|
Defender real-time-scanning every extracted file, not raw disk I/O, though
|
||||||
|
that specific cause is not confirmed. Together this was the dominant cost of
|
||||||
|
every Windows CI run, cold cache or not.
|
||||||
|
|
||||||
|
Fix: CMake 4.4.2 and Ninja 1.12.1 are now installed once, directly on the
|
||||||
|
`winvm-builder` VM (Proxmox VMID 110, host pve4/192.168.1.145), not fetched
|
||||||
|
per-run:
|
||||||
|
|
||||||
|
- `C:\BuildTools\cmake\` (from
|
||||||
|
`cmake-4.4.2-windows-x86_64.zip`, Kitware's GitHub releases) and
|
||||||
|
`C:\BuildTools\ninja\` (from `ninja-win.zip`, `ninja-build/ninja` v1.12.1
|
||||||
|
release) — plain `Expand-Archive` drops, nothing installed via an
|
||||||
|
installer/MSI.
|
||||||
|
- Both added to the **Machine**-level `PATH`
|
||||||
|
(`[Environment]::SetEnvironmentVariable('PATH', ..., 'Machine')`, not
|
||||||
|
`setx`, which silently truncates a `PATH` this long).
|
||||||
|
- The `GiteaRunner-winvm-builder` scheduled task (`C:\gitea-runner\
|
||||||
|
gitea-runner.exe daemon`, runs as SYSTEM) was stopped and restarted after
|
||||||
|
the `PATH` change — a already-running process does not pick up an updated
|
||||||
|
Machine environment variable, only processes started after the change do,
|
||||||
|
and every CI job is a child process of this one long-running daemon.
|
||||||
|
|
||||||
|
Both workflows' Windows jobs now just run `cmake --version` / `ninja
|
||||||
|
--version` as a "Verify build dependencies" step and fail loudly if either
|
||||||
|
is missing, instead of silently falling back to the slow per-run install.
|
||||||
|
|
||||||
|
**This is VM state, not something `git clone` reproduces.** If
|
||||||
|
`winvm-builder` is ever rebuilt or reimaged, redo the three steps above
|
||||||
|
(download+extract both zips under `C:\BuildTools\`, extend the Machine
|
||||||
|
`PATH`, restart the scheduled task) before expecting Windows CI to pass
|
||||||
|
again — there is nothing in this repo that does it automatically.
|
||||||
|
|
||||||
### Where the macOS bootstrap actually got to
|
### Where the macOS bootstrap actually got to
|
||||||
|
|
||||||
Six CI iterations, each fixing a real failure visible in the logs:
|
Six CI iterations, each fixing a real failure visible in the logs:
|
||||||
|
|||||||
@@ -43,6 +43,13 @@ struct SessionConfig {
|
|||||||
|
|
||||||
bool subscribe_audio = true;
|
bool subscribe_audio = true;
|
||||||
|
|
||||||
|
/// False for an audio-only source (the soundboard, say): the wanted
|
||||||
|
/// video track is never attached (no AttachVideo command posted), and
|
||||||
|
/// its publication is explicitly disabled server-side (RemoteTrack-
|
||||||
|
/// Publication::setEnabled(false)) so the SFU stops sending it at all --
|
||||||
|
/// not just "decoded and discarded here", genuinely not delivered.
|
||||||
|
bool subscribe_video = true;
|
||||||
|
|
||||||
/// How long connect() waits for the room to come up before giving up.
|
/// How long connect() waits for the room to come up before giving up.
|
||||||
int connect_timeout_ms = 15000;
|
int connect_timeout_ms = 15000;
|
||||||
};
|
};
|
||||||
|
|||||||
+48
-2
@@ -218,6 +218,52 @@ struct LiveKitSession::Impl : public livekit::RoomDelegate {
|
|||||||
queue_cv.notify_one();
|
queue_cv.notify_one();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handles the wanted video track once matched, shared by onTrackSubscribed
|
||||||
|
// (a fresh subscription) and attachExistingTracks (one already up when
|
||||||
|
// this session started watching). Two responsibilities that only make
|
||||||
|
// sense together, both keyed off the SAME publication:
|
||||||
|
//
|
||||||
|
// - subscribe_video: an audio-only source (the soundboard) never wants
|
||||||
|
// this video at all. Rather than attach it and let the OBS adapter
|
||||||
|
// discard every decoded frame, disable the publication itself
|
||||||
|
// (RemoteTrackPublication::setEnabled(false)) so the SFU stops
|
||||||
|
// sending it -- real bandwidth saved, not just wasted decode.
|
||||||
|
// - Fixed video quality: LiveKit's default subscriber behaviour lets
|
||||||
|
// the SFU switch simulcast layers per its own adaptive/bandwidth
|
||||||
|
// logic, which for a source with no rendered-size hint (this is a
|
||||||
|
// native C++ subscriber, not a sized <video> element) means the
|
||||||
|
// received resolution can hop between layers -- observed live as OBS
|
||||||
|
// source geometry visibly changing size mid-show. Pinning to HIGH
|
||||||
|
// asks the SFU to always send the top layer, which is what a fixed
|
||||||
|
// OBS source needs regardless of bandwidth (the plugin has no
|
||||||
|
// picture-in-picture tier to fall back to the way a browser grid
|
||||||
|
// view would).
|
||||||
|
void handleWantedVideoTrack(const std::shared_ptr<livekit::Track> &track,
|
||||||
|
const std::shared_ptr<livekit::RemoteTrackPublication> &publication)
|
||||||
|
{
|
||||||
|
if (!config.subscribe_video) {
|
||||||
|
if (publication) {
|
||||||
|
try {
|
||||||
|
publication->setEnabled(false);
|
||||||
|
} catch (const std::exception &) {
|
||||||
|
// Best-effort: worst case this track keeps being
|
||||||
|
// delivered and decoded, wasting bandwidth -- it is
|
||||||
|
// still never attached to OBS below.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (publication) {
|
||||||
|
try {
|
||||||
|
publication->setVideoQuality(livekit::VideoQuality::HIGH);
|
||||||
|
} catch (const std::exception &) {
|
||||||
|
// Best-effort: worst case this track keeps whatever quality
|
||||||
|
// it already had, which is the pre-existing behaviour.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
post(CommandType::AttachVideo, track);
|
||||||
|
}
|
||||||
|
|
||||||
// --- RoomDelegate ------------------------------------------------------
|
// --- RoomDelegate ------------------------------------------------------
|
||||||
|
|
||||||
void onTrackSubscribed(livekit::Room &, const livekit::TrackSubscribedEvent &event) override
|
void onTrackSubscribed(livekit::Room &, const livekit::TrackSubscribedEvent &event) override
|
||||||
@@ -230,7 +276,7 @@ struct LiveKitSession::Impl : public livekit::RoomDelegate {
|
|||||||
event.publication ? toMediaSource(event.publication->source()) : MediaSource::Unknown;
|
event.publication ? toMediaSource(event.publication->source()) : MediaSource::Unknown;
|
||||||
|
|
||||||
if (isWantedVideoTrack(config.participant_identity, identity, kind, source))
|
if (isWantedVideoTrack(config.participant_identity, identity, kind, source))
|
||||||
post(CommandType::AttachVideo, event.track);
|
handleWantedVideoTrack(event.track, event.publication);
|
||||||
else if (config.subscribe_audio && isWantedAudioTrack(config.participant_identity, identity, kind, source))
|
else if (config.subscribe_audio && isWantedAudioTrack(config.participant_identity, identity, kind, source))
|
||||||
post(CommandType::AttachAudio, event.track);
|
post(CommandType::AttachAudio, event.track);
|
||||||
}
|
}
|
||||||
@@ -551,7 +597,7 @@ struct LiveKitSession::Impl : public livekit::RoomDelegate {
|
|||||||
const MediaKind kind = toMediaKind(track->kind());
|
const MediaKind kind = toMediaKind(track->kind());
|
||||||
const MediaSource source = toMediaSource(publication->source());
|
const MediaSource source = toMediaSource(publication->source());
|
||||||
if (isWantedVideoTrack(config.participant_identity, identity, kind, source))
|
if (isWantedVideoTrack(config.participant_identity, identity, kind, source))
|
||||||
post(CommandType::AttachVideo, track);
|
handleWantedVideoTrack(track, publication);
|
||||||
else if (config.subscribe_audio && isWantedAudioTrack(config.participant_identity, identity, kind, source))
|
else if (config.subscribe_audio && isWantedAudioTrack(config.participant_identity, identity, kind, source))
|
||||||
post(CommandType::AttachAudio, track);
|
post(CommandType::AttachAudio, track);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ RoomSlug="Room"
|
|||||||
ReadKey="Read key"
|
ReadKey="Read key"
|
||||||
Camera="Camera"
|
Camera="Camera"
|
||||||
RefreshCameras="Refresh camera list"
|
RefreshCameras="Refresh camera list"
|
||||||
|
AudioOnly="Audio only (no video)"
|
||||||
Status="Status"
|
Status="Status"
|
||||||
NoCameraSelected="(no camera selected)"
|
NoCameraSelected="(no camera selected)"
|
||||||
OfflineSuffix=" (offline)"
|
OfflineSuffix=" (offline)"
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ constexpr const char *kSettingServerUrl = "server_url";
|
|||||||
constexpr const char *kSettingRoomSlug = "room_slug";
|
constexpr const char *kSettingRoomSlug = "room_slug";
|
||||||
constexpr const char *kSettingReadKey = "read_key";
|
constexpr const char *kSettingReadKey = "read_key";
|
||||||
constexpr const char *kSettingCamera = "camera";
|
constexpr const char *kSettingCamera = "camera";
|
||||||
|
constexpr const char *kSettingAudioOnly = "audio_only";
|
||||||
constexpr const char *kSettingStatus = "status";
|
constexpr const char *kSettingStatus = "status";
|
||||||
constexpr const char *kPropRefresh = "refresh";
|
constexpr const char *kPropRefresh = "refresh";
|
||||||
|
|
||||||
@@ -96,6 +97,10 @@ struct CameraSource {
|
|||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
ConnectionConfig config;
|
ConnectionConfig config;
|
||||||
std::string camera_identity;
|
std::string camera_identity;
|
||||||
|
/// True hides video entirely for this source (the soundboard, typically)
|
||||||
|
/// -- see SessionConfig::subscribe_video for what that actually does at
|
||||||
|
/// the LiveKit level.
|
||||||
|
bool audio_only = false;
|
||||||
/// Bumped every time settings change; the worker compares it to what it
|
/// Bumped every time settings change; the worker compares it to what it
|
||||||
/// last connected with, so a stale in-flight connect is abandoned rather
|
/// last connected with, so a stale in-flight connect is abandoned rather
|
||||||
/// than fought over.
|
/// than fought over.
|
||||||
@@ -219,6 +224,7 @@ void workerLoop(CameraSource *self)
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
ConnectionConfig config;
|
ConnectionConfig config;
|
||||||
std::string camera;
|
std::string camera;
|
||||||
|
bool audio_only = false;
|
||||||
std::uint64_t generation = 0;
|
std::uint64_t generation = 0;
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(self->mutex);
|
std::unique_lock<std::mutex> lock(self->mutex);
|
||||||
@@ -226,6 +232,7 @@ void workerLoop(CameraSource *self)
|
|||||||
break;
|
break;
|
||||||
config = self->config;
|
config = self->config;
|
||||||
camera = self->camera_identity;
|
camera = self->camera_identity;
|
||||||
|
audio_only = self->audio_only;
|
||||||
generation = self->generation;
|
generation = self->generation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,6 +289,7 @@ void workerLoop(CameraSource *self)
|
|||||||
session_config.ws_url = token.ws_url;
|
session_config.ws_url = token.ws_url;
|
||||||
session_config.token = token.lk_token;
|
session_config.token = token.lk_token;
|
||||||
session_config.participant_identity = camera;
|
session_config.participant_identity = camera;
|
||||||
|
session_config.subscribe_video = !audio_only;
|
||||||
|
|
||||||
if (self->session->connect(session_config)) {
|
if (self->session->connect(session_config)) {
|
||||||
connected = true;
|
connected = true;
|
||||||
@@ -330,6 +338,7 @@ void sourceGetDefaults(obs_data_t *settings)
|
|||||||
obs_data_set_default_string(settings, kSettingRoomSlug, "");
|
obs_data_set_default_string(settings, kSettingRoomSlug, "");
|
||||||
obs_data_set_default_string(settings, kSettingReadKey, "");
|
obs_data_set_default_string(settings, kSettingReadKey, "");
|
||||||
obs_data_set_default_string(settings, kSettingCamera, "");
|
obs_data_set_default_string(settings, kSettingCamera, "");
|
||||||
|
obs_data_set_default_bool(settings, kSettingAudioOnly, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void applySettings(CameraSource *self, obs_data_t *settings)
|
void applySettings(CameraSource *self, obs_data_t *settings)
|
||||||
@@ -339,16 +348,19 @@ void applySettings(CameraSource *self, obs_data_t *settings)
|
|||||||
config.room_slug = settingString(settings, kSettingRoomSlug);
|
config.room_slug = settingString(settings, kSettingRoomSlug);
|
||||||
config.read_key = settingString(settings, kSettingReadKey);
|
config.read_key = settingString(settings, kSettingReadKey);
|
||||||
const std::string camera = settingString(settings, kSettingCamera);
|
const std::string camera = settingString(settings, kSettingCamera);
|
||||||
|
const bool audio_only = obs_data_get_bool(settings, kSettingAudioOnly);
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> guard(self->mutex);
|
std::lock_guard<std::mutex> guard(self->mutex);
|
||||||
const bool changed = config.server_url != self->config.server_url ||
|
const bool changed = config.server_url != self->config.server_url ||
|
||||||
config.room_slug != self->config.room_slug ||
|
config.room_slug != self->config.room_slug ||
|
||||||
config.read_key != self->config.read_key || camera != self->camera_identity;
|
config.read_key != self->config.read_key || camera != self->camera_identity ||
|
||||||
|
audio_only != self->audio_only;
|
||||||
if (!changed)
|
if (!changed)
|
||||||
return;
|
return;
|
||||||
self->config = config;
|
self->config = config;
|
||||||
self->camera_identity = camera;
|
self->camera_identity = camera;
|
||||||
|
self->audio_only = audio_only;
|
||||||
++self->generation;
|
++self->generation;
|
||||||
}
|
}
|
||||||
self->wake.notify_all();
|
self->wake.notify_all();
|
||||||
@@ -385,6 +397,7 @@ void *sourceCreate(obs_data_t *settings, obs_source_t *source)
|
|||||||
self->config.room_slug = settingString(settings, kSettingRoomSlug);
|
self->config.room_slug = settingString(settings, kSettingRoomSlug);
|
||||||
self->config.read_key = settingString(settings, kSettingReadKey);
|
self->config.read_key = settingString(settings, kSettingReadKey);
|
||||||
self->camera_identity = settingString(settings, kSettingCamera);
|
self->camera_identity = settingString(settings, kSettingCamera);
|
||||||
|
self->audio_only = obs_data_get_bool(settings, kSettingAudioOnly);
|
||||||
self->generation = 1;
|
self->generation = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,6 +540,14 @@ obs_properties_t *sourceGetProperties(void *data)
|
|||||||
|
|
||||||
obs_properties_add_button(props, kPropRefresh, obs_module_text("RefreshCameras"), refreshButtonClicked);
|
obs_properties_add_button(props, kPropRefresh, obs_module_text("RefreshCameras"), refreshButtonClicked);
|
||||||
|
|
||||||
|
// For a picked slot with no visual content worth showing (the
|
||||||
|
// soundboard, which publishes a throwaway black keep-alive frame purely
|
||||||
|
// because RTMP egress needs a video track -- see Soundboard.tsx in the
|
||||||
|
// streamer-tools repo). Disables the video track at the LiveKit level
|
||||||
|
// (RemoteTrackPublication::setEnabled(false), see session.cpp), not just
|
||||||
|
// locally: the SFU stops sending it.
|
||||||
|
obs_properties_add_bool(props, kSettingAudioOnly, obs_module_text("AudioOnly"));
|
||||||
|
|
||||||
// An OBS_TEXT_INFO property renders its *description* as the visible
|
// An OBS_TEXT_INFO property renders its *description* as the visible
|
||||||
// label, so the status line goes there rather than into a tooltip an
|
// label, so the status line goes there rather than into a tooltip an
|
||||||
// operator would never hover over mid-show.
|
// operator would never hover over mid-show.
|
||||||
|
|||||||
Vendored
-25
@@ -10,28 +10,3 @@ module — so the SDK's licence and notice files ship with it.
|
|||||||
by `obs-adapter/CMakeLists.txt` on every build, alongside this plugin's own
|
by `obs-adapter/CMakeLists.txt` on every build, alongside this plugin's own
|
||||||
Apache-2.0 `LICENSE` (this project's own first-party code was relicensed from
|
Apache-2.0 `LICENSE` (this project's own first-party code was relicensed from
|
||||||
GPL-2.0 to Apache-2.0 to match).
|
GPL-2.0 to Apache-2.0 to match).
|
||||||
|
|
||||||
## A correction to the design doc
|
|
||||||
|
|
||||||
The design doc's open questions say:
|
|
||||||
|
|
||||||
> `client-sdk-cpp`'s bundled `LICENSE.md` (~28 distinct third-party license
|
|
||||||
> blocks — Google WebRTC, OpenH264, etc.) must ship inside the plugin package
|
|
||||||
|
|
||||||
**No such file exists at `v1.10.1`.** Checked, on 2026-09-06:
|
|
||||||
|
|
||||||
- The five release archives for this tag (`livekit-sdk-<triple>-1.10.1.tar.gz`
|
|
||||||
/ `.zip`) contain only `include/`, `lib/`, `bin/` and
|
|
||||||
`share/livekit/build-info.json`. No licence file of any kind.
|
|
||||||
- The repository at tag `v1.10.1` has `LICENSE` (Apache-2.0, 10142 bytes) and
|
|
||||||
`NOTICE` (553 bytes) at its root. There is no `LICENSE.md`, no `NOTICE.md`,
|
|
||||||
and no `THIRD_PARTY_LICENSES` file.
|
|
||||||
|
|
||||||
So what ships here is the Apache-2.0 licence and notice, which is what
|
|
||||||
actually exists upstream. **The aggregated third-party notice the design doc
|
|
||||||
expected — covering the WebRTC/OpenH264/etc. code statically linked inside
|
|
||||||
`liblivekit_ffi.so` — has not been located and is not being shipped.** That
|
|
||||||
is a real, open licensing question for whoever signs off on distributing
|
|
||||||
release binaries, not something this packaging step has resolved. Worth
|
|
||||||
raising upstream, or asking counsel whether the Apache-2.0 NOTICE alone
|
|
||||||
suffices for a binary redistribution of that library.
|
|
||||||
|
|||||||
Reference in New Issue
Block a user