LiveKit integration: real camera feed pipeline #1
@@ -68,24 +68,29 @@ function(_check_deps_version version)
|
||||
return(PROPAGATE found CMAKE_PREFIX_PATH)
|
||||
endfunction()
|
||||
|
||||
# _resolve_versioned_macos_sdk: return an SDK path whose *filename* carries the
|
||||
# version number, e.g. .../MacOSX15.5.sdk.
|
||||
# _resolve_versioned_macos_sdk: return an SDK path that satisfies OBS's own
|
||||
# macOS SDK version check.
|
||||
#
|
||||
# Not upstream. OBS's own cmake/macos/compilerconfig.cmake reads the SDK
|
||||
# version by regex-matching "MacOSX<major>.<minor>.sdk" out of
|
||||
# CMAKE_OSX_SYSROOT, and hard-fails if that does not match:
|
||||
# Not upstream. OBS 30.0.2's cmake/macos/compilerconfig.cmake reads the SDK
|
||||
# version straight out of CMAKE_OSX_SYSROOT with this regex:
|
||||
#
|
||||
# string sub-command REGEX, mode MATCH needs at least 5 arguments
|
||||
# Your macOS SDK version () is too low.
|
||||
# ".+/MacOSX.platform/Developer/SDKs/MacOSX([0-9]+\\.[0-9])+\\.sdk$"
|
||||
#
|
||||
# With upstream's Xcode generator CMAKE_OSX_SYSROOT stays the literal string
|
||||
# "macosx" and Xcode resolves it late, so the regex never runs against a real
|
||||
# path. With Ninja -- which this project uses because CI has no Xcode -- CMake
|
||||
# resolves it eagerly to whatever `xcrun --show-sdk-path` returns, and on a
|
||||
# Command-Line-Tools-only install that is the UNVERSIONED symlink
|
||||
# /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk. Hence this: prefer a
|
||||
# versioned sibling if the toolchain ships one, and otherwise synthesise a
|
||||
# correctly-named symlink to the same SDK.
|
||||
# and hard-fails if it does not match ("Your macOS SDK version () is too low",
|
||||
# with an empty version, which is the tell). That pattern only ever matches a
|
||||
# full-Xcode SDK path; a Command-Line-Tools-only install has its SDK at
|
||||
# /Library/Developer/CommandLineTools/SDKs/MacOSX<ver>.sdk, with no
|
||||
# MacOSX.platform/Developer/SDKs segment at all, and can never match.
|
||||
#
|
||||
# Upstream never hits this because it uses the Xcode generator, where
|
||||
# CMAKE_OSX_SYSROOT stays the literal string "macosx" and Xcode resolves it
|
||||
# late. This project uses Ninja (the CI runner has no xcodebuild), so CMake
|
||||
# resolves the sysroot eagerly and the regex runs against a real path.
|
||||
#
|
||||
# So: if the toolchain's own SDK path already matches, use it untouched.
|
||||
# Otherwise build a symlink tree under .deps/ whose shape matches the regex
|
||||
# and which points at exactly the same SDK. Nothing about the compilation
|
||||
# changes -- only the spelling of the path, which is all the check reads.
|
||||
function(_resolve_versioned_macos_sdk out_path)
|
||||
set(${out_path} "" PARENT_SCOPE)
|
||||
|
||||
@@ -97,12 +102,11 @@ function(_resolve_versioned_macos_sdk out_path)
|
||||
ERROR_QUIET
|
||||
)
|
||||
if(NOT _rc EQUAL 0 OR NOT _sdk)
|
||||
message(WARNING "Could not determine the macOS SDK path via xcrun; leaving CMAKE_OSX_SYSROOT alone.")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Already versioned: nothing to do.
|
||||
get_filename_component(_sdk_name "${_sdk}" NAME)
|
||||
if(_sdk_name MATCHES "^MacOSX[0-9]+\\.[0-9]+\\.sdk$")
|
||||
if(_sdk MATCHES "/MacOSX\\.platform/Developer/SDKs/MacOSX[0-9]+\\.[0-9]+\\.sdk$")
|
||||
set(${out_path} "${_sdk}" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
@@ -115,27 +119,22 @@ function(_resolve_versioned_macos_sdk out_path)
|
||||
ERROR_QUIET
|
||||
)
|
||||
if(NOT _rc EQUAL 0 OR NOT _sdk_version MATCHES "^([0-9]+)\\.([0-9]+)")
|
||||
message(WARNING "Could not determine the macOS SDK version via xcrun (got '${_sdk_version}').")
|
||||
return()
|
||||
endif()
|
||||
set(_short "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}")
|
||||
|
||||
# A versioned sibling next to the symlink is the common layout.
|
||||
get_filename_component(_sdk_dir "${_sdk}" DIRECTORY)
|
||||
if(EXISTS "${_sdk_dir}/MacOSX${_short}.sdk")
|
||||
set(${out_path} "${_sdk_dir}/MacOSX${_short}.sdk" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Otherwise make one, inside our own dependency directory.
|
||||
set(_link_dir "${dependencies_dir}/sdk")
|
||||
file(MAKE_DIRECTORY "${_link_dir}")
|
||||
set(_link_dir "${dependencies_dir}/sdk/MacOSX.platform/Developer/SDKs")
|
||||
set(_link "${_link_dir}/MacOSX${_short}.sdk")
|
||||
file(MAKE_DIRECTORY "${_link_dir}")
|
||||
if(NOT EXISTS "${_link}")
|
||||
file(CREATE_LINK "${_sdk}" "${_link}" SYMBOLIC)
|
||||
endif()
|
||||
if(EXISTS "${_link}")
|
||||
message(STATUS "Using synthesised versioned macOS SDK path: ${_link}")
|
||||
message(STATUS "macOS SDK ${_short} at ${_sdk}; presenting it to OBS as ${_link}")
|
||||
set(${out_path} "${_link}" PARENT_SCOPE)
|
||||
else()
|
||||
message(WARNING "Could not create the versioned macOS SDK symlink at ${_link}.")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
|
||||
@@ -20,32 +20,23 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
#include <string>
|
||||
|
||||
// C++ API for the core library. Per the design doc
|
||||
// (docs/superpowers/specs/2026-09-06-obs-camera-plugin-design.md in the
|
||||
// streamer-tools repo), this library will eventually own: streamer-tools
|
||||
// API auth, LiveKit FFI session management (connect, subscribe, decode,
|
||||
// reconnect), and frame callbacks -- all with zero OBS dependency, so it
|
||||
// can be built and tested headlessly.
|
||||
//
|
||||
// THIS IS SCAFFOLDING. Nothing below talks to a real server or to
|
||||
// livekit-ffi yet. It exists to prove the core-library/OBS-adapter split
|
||||
// builds, links, and is unit-testable, ahead of a later phase that
|
||||
// implements the real logic.
|
||||
// The small shared pieces of the core library: its version string, and the
|
||||
// streamer-tools connection settings that both the API client and the OBS
|
||||
// adapter pass around. Everything substantial lives in its own header --
|
||||
// api_client.h, session.h, http.h, json.h -- and none of it depends on OBS,
|
||||
// so the whole library builds and tests headlessly on all three platforms.
|
||||
|
||||
namespace stplugin {
|
||||
|
||||
// Returns the core library's version string. Placeholder for a real
|
||||
// version scheme once the library does something.
|
||||
// The core library's version string, injected by CMake from the top-level
|
||||
// project() version, so what OBS logs on load is the actual build.
|
||||
const char *core_version();
|
||||
|
||||
// Minimal connection configuration the future core library will use to
|
||||
// authenticate against the streamer-tools API
|
||||
// (see apps/server/src/rooms/join.routes.ts and
|
||||
// apps/server/src/livekit/tokens.ts in the streamer-tools repo for the
|
||||
// existing read-key-authed token pattern this will follow) and mint a
|
||||
// scoped LiveKit subscriber token. Validation here is intentionally
|
||||
// trivial -- it exists to prove the core library is unit-testable
|
||||
// headlessly, not to implement the real API client.
|
||||
// What an operator types into the source's properties, and what ApiClient
|
||||
// needs to reach the two read-key-scoped endpoints in
|
||||
// apps/server/src/obs/plugin.routes.ts (streamer-tools repo). The read key is
|
||||
// a credential: it is masked in the properties UI and never logged (see
|
||||
// ApiClient::redactedUrl).
|
||||
struct ConnectionConfig {
|
||||
std::string server_url;
|
||||
std::string room_slug;
|
||||
|
||||
@@ -18,15 +18,13 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
#pragma once
|
||||
|
||||
// Minimal C ABI surface of the core library, for the OBS adapter (plain
|
||||
// C, per the obs-plugintemplate convention) to call into the core
|
||||
// library (C++) without needing a C++ compiler in that translation unit.
|
||||
// A minimal C ABI over the core library's version string.
|
||||
//
|
||||
// This mirrors the boundary the real integration will cross in the
|
||||
// other direction: livekit-ffi is a Rust library exposing a C ABI that
|
||||
// the C++ core library will link against. Proving a small, deliberate
|
||||
// C ABI seam works cleanly here is part of what this scaffold is for.
|
||||
|
||||
// The OBS adapter is C++ and calls stplugin::core_version() directly, so
|
||||
// nothing in this repository needs this header today. It is kept because it
|
||||
// is the seam a plain-C consumer would use, and because the unit tests assert
|
||||
// the two entry points agree -- which is a cheap check that the C++ library
|
||||
// really is linkable from a C translation unit.
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
@@ -15,6 +15,9 @@ function(stplugin_add_test name)
|
||||
endfunction()
|
||||
|
||||
stplugin_add_test(test_core)
|
||||
target_compile_definitions(test_core PRIVATE
|
||||
STPLUGIN_EXPECTED_CORE_VERSION="${PROJECT_VERSION}"
|
||||
)
|
||||
stplugin_add_test(test_json)
|
||||
stplugin_add_test(test_api_client)
|
||||
stplugin_add_test(test_session)
|
||||
|
||||
+31
-27
@@ -16,44 +16,48 @@ You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
// Deliberately dependency-free (no gtest/catch2 etc.) so this test target
|
||||
// has no network fetch or package-manager step in CI -- proving the
|
||||
// "core library builds and tests headlessly, no OBS required" claim
|
||||
// without adding another moving part to this scaffolding pass.
|
||||
// NOTE on why this file uses ST_ASSERT and not assert(): CI builds Release,
|
||||
// which defines NDEBUG, which compiles every bare assert() out entirely. This
|
||||
// suite previously passed unconditionally for exactly that reason. The
|
||||
// ST_ASSERT macros in test_util.h are always live and report a pass/fail
|
||||
// count.
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "stplugin/core.h"
|
||||
#include "stplugin/core_c.h"
|
||||
#include "test_util.h"
|
||||
|
||||
int main() {
|
||||
// core_version() should return a non-empty string, via both the
|
||||
// C++ API and the C ABI wrapper the OBS adapter will actually call.
|
||||
int main()
|
||||
{
|
||||
// The version string comes through both the C++ API and the C ABI
|
||||
// wrapper, and the two must agree.
|
||||
const char *cpp_version = stplugin::core_version();
|
||||
assert(cpp_version != nullptr);
|
||||
assert(std::strlen(cpp_version) > 0);
|
||||
ST_ASSERT(cpp_version != nullptr);
|
||||
ST_ASSERT(cpp_version != nullptr && std::strlen(cpp_version) > 0);
|
||||
|
||||
const char *c_version = stplugin_core_version();
|
||||
assert(c_version != nullptr);
|
||||
assert(std::strcmp(cpp_version, c_version) == 0);
|
||||
ST_ASSERT(c_version != nullptr);
|
||||
ST_ASSERT_EQ(std::string(cpp_version ? cpp_version : ""), std::string(c_version ? c_version : ""));
|
||||
|
||||
// ConnectionConfig::is_valid() -- trivial non-empty checks, but
|
||||
// exercised through all four combinations to prove the core library
|
||||
// is genuinely testable in isolation.
|
||||
stplugin::ConnectionConfig valid{"https://streamers.example.com", "main-room", "readkey123"};
|
||||
assert(valid.is_valid());
|
||||
// It is the version CMake injected, not a hand-maintained literal.
|
||||
ST_ASSERT_EQ(std::string(cpp_version ? cpp_version : ""), std::string(STPLUGIN_EXPECTED_CORE_VERSION));
|
||||
|
||||
stplugin::ConnectionConfig missing_url{"", "main-room", "readkey123"};
|
||||
assert(!missing_url.is_valid());
|
||||
// ConnectionConfig::is_valid(): all three fields are required. Named
|
||||
// locals rather than braced temporaries inline, because the commas inside
|
||||
// a braced initialiser would be read as macro argument separators.
|
||||
const stplugin::ConnectionConfig complete{"https://streamers.example.com", "main-room", "readkey123"};
|
||||
const stplugin::ConnectionConfig no_url{"", "main-room", "readkey123"};
|
||||
const stplugin::ConnectionConfig no_slug{"https://streamers.example.com", "", "readkey123"};
|
||||
const stplugin::ConnectionConfig no_key{"https://streamers.example.com", "main-room", ""};
|
||||
const stplugin::ConnectionConfig empty;
|
||||
|
||||
stplugin::ConnectionConfig missing_slug{"https://streamers.example.com", "", "readkey123"};
|
||||
assert(!missing_slug.is_valid());
|
||||
ST_ASSERT(complete.is_valid());
|
||||
ST_ASSERT(!no_url.is_valid());
|
||||
ST_ASSERT(!no_slug.is_valid());
|
||||
ST_ASSERT(!no_key.is_valid());
|
||||
ST_ASSERT(!empty.is_valid());
|
||||
|
||||
stplugin::ConnectionConfig missing_key{"https://streamers.example.com", "main-room", ""};
|
||||
assert(!missing_key.is_valid());
|
||||
|
||||
std::printf("core: all tests passed\n");
|
||||
return 0;
|
||||
return st_test_report("core");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user