b2fac34a5a131a38c01602255033055d684c02c6
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6b12859887 |
Fix assertions that NDEBUG deleted, and match OBS's real macOS SDK regex
test_core.cpp used bare assert(). CI builds Release, Release defines NDEBUG, and NDEBUG compiles assert() out entirely -- so that suite had been passing unconditionally, checking nothing. It now uses the same always-live ST_ASSERT harness as the other suites and reports a count (10 checks), and additionally asserts that core_version() really is the version CMake injected rather than a stale literal. macOS SDK, third iteration. The previous fix assumed OBS only wanted a version-carrying SDK filename. Reading OBS 30.0.2's cmake/macos/compilerconfig.cmake shows the actual pattern is stricter: ".+/MacOSX.platform/Developer/SDKs/MacOSX([0-9]+\.[0-9])+\.sdk$" which only ever matches a full-Xcode SDK path. A Command-Line-Tools-only install keeps its SDK at /Library/Developer/CommandLineTools/SDKs/MacOSX<ver>.sdk, with no MacOSX.platform/Developer/SDKs segment at all, so it can never match however it is named -- which is why the second attempt got past the "REGEX needs at least 5 arguments" error and still landed on "Your macOS SDK version () is too low", with the version still empty. _resolve_versioned_macos_sdk now builds a symlink tree under .deps/ whose shape matches that pattern and which points at exactly the same SDK, and uses the toolchain's own path untouched when it already matches (i.e. when real Xcode is installed). Nothing about the compilation changes -- only the spelling of the path, which is all OBS's check reads. Also refreshes the scaffold-era comments in core.h and core_c.h, which still described this library as a placeholder that would one day talk to livekit-ffi. Linux CI is green on the previous commit: real libobs adapter linked (ldd shows libobs.so.0 plus liblivekit/liblivekit_ffi resolving from the staged package directory), obs_module_load exported, ctest 6/6, artifact uploaded. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RL8abRmgFXkVASHkkqiJbE |
||
|
|
80904a3e85 |
Add the LiveKit session wrapper, verified end-to-end against a real room
stplugin::LiveKitSession wraps livekit::Room for exactly one subscribed slot: connect with the wsUrl/lkToken the API client minted, find the chosen participant's camera (and microphone), and hand decoded frames to callback-shaped handlers the OBS adapter can consume directly. Two architectural decisions worth recording, both forced by reading the SDK rather than guessed: 1. Frames come from VideoStream/AudioStream::fromTrack with our own reader threads, NOT from Room::setOnVideoFrameCallback. The dispatcher API is keyed by (participant identity, track NAME), which we cannot know before the track is published -- and disassembling liblivekit.so confirms that both Room::setOnVideoFrameCallback and the dispatcher's own setOnVideoFrameCallback merely record the registration: neither starts a reader for a track that is already subscribed. Registering after the subscription event, which is the only time the track name exists, would therefore have silently produced no video. Taking the shared_ptr<Track> straight off the TrackSubscribedEvent sidesteps the name entirely, and lets us pick the camera by TrackSource (streamer-tools publishes cameras as Source.Camera and screenshares separately -- apps/web/src/avatar/ publish.ts), which is what we actually mean. 2. Every stream operation runs on one owned worker thread, never on a room event thread. The SDK documents that Room::disconnect() from inside a delegate callback deadlocks, and Room's own event dispatch holds a mutex, so delegate callbacks only ever enqueue a command here. VideoStream::Options::capacity is set (3 frames) so the SDK's queue is a drop-oldest ring buffer: a stalled consumer can only fall three frames behind, and what it then sees is the newest frame rather than a backlog. That is the structural answer to the stale-media bug that motivated this plugin. The pure decision-making -- the state machine, track selection, frame geometry validation -- lives in session_types.h/.cpp with no LiveKit or OBS types, so it is unit-testable headlessly (81 checks in test_session, including the publisher-swap and reconnect transitions, plus the real connect() failure paths against the real SDK: unreachable host, garbage token, incomplete config, and destruction mid-connect). test_integration_livekit is the test that proves media actually flows. It publishes a synthetic camera and microphone into a real LiveKit room using the same SDK, subscribes through LiveKitSession, and asserts on the exact fields the OBS adapter will dereference. It skips (exit 0) unless STPLUGIN_IT_* is set, so the three build runners stay green; scripts/livekit-dev-room.py mints the tokens for a local `livekit-server --dev`. Verified locally against livekit-server 1.13.6 in dev mode: integration_livekit: 36 video frames, 323 audio frames, 10 state changes integration_livekit: 32 checks passed covering: connect; subscribe to the named participant's camera; 320x240 I420 frames with three planes, non-null plane pointers and strides >= the frame's own width; 48kHz audio; unpublish -> hasVideo() false, state stays Connected (a dark camera is the placeholder state, never an error) and NO further frames arrive from the dead publisher; republish -> video resumes; clean disconnect. One real finding from that run, now handled: WebRTC ramps a new subscription up from a downscaled spatial layer, so the first frames after (re)subscribing legitimately arrive smaller than what is being published. The OBS adapter must cope with a mid-stream resolution change; the test asserts per-frame geometry rather than the publisher's, and separately asserts the stream does reach full size. Full suite: ctest -> 6/6 passed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RL8abRmgFXkVASHkkqiJbE |
||
|
|
bf33966a4e |
Add the streamer-tools API client, with a real HTTP backend per platform
Implements the two read-key-scoped calls in apps/server/src/obs/plugin.routes.ts: GET /api/obs/:slug/slots and POST /api/obs/:slug/token. Three pieces, all in core/ with no OBS dependency: - stplugin::json -- a small, strict JSON reader. Hand-rolled rather than vendoring nlohmann because the only JSON this plugin ever sees is two fixed-shape responses from its own server, and the parser has to build on three platforms with no package-manager step in CI. It never throws, bounds its recursion (kMaxDepth=32) so a hostile response cannot overflow the stack inside OBS, rejects trailing garbage, and returns the caller's fallback for wrong-typed access instead of aborting. - stplugin::HttpClient -- a two-method injectable interface, with libcurl behind it on Linux/macOS and WinHTTP on Windows. WinHTTP rather than curl on Windows because it ships with the OS and does TLS through SChannel: the self-hosted winvm-builder runner has no package manager, and per the scaffold README does not even have cmake preinstalled. Both backends cap the response body at 4 MiB, keep TLS verification on (the read key is a credential), and honour a whole-request timeout. - stplugin::ApiClient -- maps the responses onto an ApiStatus enum that distinguishes NotFound (404), Unavailable (503), NetworkError, MalformedResponse and InvalidConfig. It deliberately does not claim to know whether a 404 was a wrong key or an unknown slug, because the server deliberately does not say. Server URLs are normalised the way an operator actually pastes them, defaulting to https so the read key is never sent in the clear by accident, and redactedUrl() exists so a URL can be logged without the key. Tests (279 checks across two new suites) run at two levels: a fake HttpClient covering every response and error branch, and a real loopback HTTP server on 127.0.0.1 driving the actual platform backend -- so libcurl on Linux/macOS and WinHTTP on Windows are each exercised in CI rather than assumed. The loopback cases deliberately include the ones that must not hang OBS: a truncated JSON body, a connection accepted and closed without a reply, non-HTTP garbage, a dead port, and a stalled server that has to be cut off by the client's own timeout. Verified locally on Ubuntu 24.04: ctest --test-dir build --output-on-failure -> 4/4 passed test_json: 158 checks passed test_api_client: 121 checks passed Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RL8abRmgFXkVASHkkqiJbE |
||
|
|
e595173049 |
Link the pinned LiveKit C++ SDK into the core library
Adds cmake/LiveKitSDK.cmake, adapted from livekit-examples/cpp-example-collection's helper of the same name, with the VERSION="latest" GitHub-API resolution path removed: this project pins an exact client-sdk-cpp release (1.10.1, the newest tag as of today), and the pin should not be silently bypassable. The module also now exports the runtime shared libraries so packaging can stage liblivekit/liblivekit_ffi next to the plugin module later. core/ links LiveKit::livekit PUBLIC. A new smoke test proves the SDK is not just linked but loadable and callable: livekit::initialize()/shutdown() round-trip in-process, a second initialize() reports "already initialized", the log level round-trips, and the SDK's generated LIVEKIT_BUILD_VERSION is asserted equal to the version CMake pinned (so a stale extracted SDK directory fails loudly rather than being silently reused). Also adds core/tests/test_util.h, a dependency-free assertion harness that keeps running after a failure and prints a pass/fail count, so CI output says how much actually ran instead of dying on the first bare assert(). cmake_minimum_required goes 3.16 -> 3.19 (file(ARCHIVE_EXTRACT)). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RL8abRmgFXkVASHkkqiJbE |
||
|
|
105f1041ab |
Scaffold core/OBS-adapter split, prove CMake toolchain, add 3-platform CI
First pass on the streamer-tools OBS camera plugin: a minimal but real CMake project matching the design doc's core-library/OBS-adapter split (docs/superpowers/specs/2026-09-06-obs-camera-plugin-design.md in the streamer-tools repo). No LiveKit FFI integration yet -- this proves the toolchain works. - core/: dependency-free C++17 library (no OBS dependency), unit tested via CTest with no external test framework. - obs-adapter/: adapted from obsproject/obs-plugintemplate (commit 3e7d7ac, 2025-12-09). Registers a real, stubbed OBS source type; builds as a genuine dynamically-linked OBS module against Ubuntu's system libobs-dev (confirmed via ldd/nm, not a fake stand-in). - Simpler hand-written top-level CMakeLists.txt in place of the template's full buildspec-driven bootstrap (which downloads full OBS source + prebuilt deps) -- find_package(libobs) alone is enough on Linux; falls back to core-library-only when libobs isn't found (expected on macOS/Windows CI for now). - .gitea/workflows/build.yml: 3-platform matrix (ubuntu-latest, macos-latest, windows-latest) matching the runners confirmed available to this repo under the CyberCoveLLC org. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RL8abRmgFXkVASHkkqiJbE |