Skip to content

Commit 81e9433

Browse files
summeroffclaude
andcommitted
Merge branch 'staging' into auto_config_apiv2
Resolved conflicts: - obs-studio-client/source/video.cpp: keep staging's ValidateResponse check in Video::set; do not reintroduce the lastVideo cache (removed on this branch). - obs-studio-server/source/osn-simple-streaming.cpp: keep this branch's reconnect guard plus staging's GetCanvasVideo and encoder-compatibility guards. - js/module.js: regenerated from merged module.ts via build:javascript. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2 parents 0887359 + 506dae6 commit 81e9433

44 files changed

Lines changed: 1204 additions & 395 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Check Generated JS
2+
3+
on:
4+
push:
5+
paths-ignore: ['**.md']
6+
branches-ignore: [staging]
7+
pull_request:
8+
paths-ignore: ['**.md']
9+
branches-ignore: [staging]
10+
11+
permissions:
12+
contents: read
13+
14+
jobs:
15+
check-js-generated:
16+
runs-on: ubuntu-22.04
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v4
20+
21+
- name: Install Node.js
22+
uses: actions/setup-node@v4
23+
with:
24+
node-version: '20.x'
25+
26+
- name: Install dependencies
27+
run: |
28+
corepack enable
29+
corepack prepare yarn@4.14.1 --activate
30+
yarn install --immutable
31+
32+
- name: Regenerate js/ outputs
33+
run: yarn run build:javascript
34+
35+
- name: Fail if generated files are stale
36+
run: ./ci/check-js-generated.sh

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ env:
2525
SLGenerator: Visual Studio 17 2022
2626
SLDistributeDirectory: distribute
2727
SLFullDistributePath: "streamlabs-build.app/distribute" # The .app extension is required to run macOS tests correctly.
28-
LibOBSVersion: 31.1.2sl13
28+
LibOBSVersion: 31.1.2sl20
2929
PACKAGE_NAME: osn
3030

3131
jobs:

CMakeLists.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ include(ExternalProject)
8181
include(DownloadProject)
8282
include(FetchContent)
8383

84+
option(BUILD_TESTING "Build C++ unit tests" ON)
85+
include(CTest)
86+
8487
if(MSVC)
8588
add_definitions(/MP)
8689
add_link_options(/debug:fastlink)
@@ -118,6 +121,21 @@ if(NOT nlohmannjson_POPULATED)
118121
FetchContent_Populate(nlohmannjson)
119122
endif()
120123

124+
if(BUILD_TESTING)
125+
FetchContent_Declare(
126+
Catch2
127+
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
128+
GIT_TAG v3.11.0
129+
)
130+
131+
set(CATCH_INSTALL_DOCS OFF CACHE BOOL "" FORCE)
132+
set(CATCH_INSTALL_EXTRAS ON CACHE BOOL "" FORCE)
133+
set(CATCH_DEVELOPMENT_BUILD OFF CACHE BOOL "" FORCE)
134+
135+
FetchContent_MakeAvailable(Catch2)
136+
list(APPEND CMAKE_MODULE_PATH "${catch2_SOURCE_DIR}/extras")
137+
endif()
138+
121139
FetchContent_Declare(
122140
libcurl
123141
URL https://curl.se/download/curl-7.88.1.tar.gz

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,10 @@ scan-build --keep-empty -internal-stats -stats -v -v -v -o check ninja.exe
104104
```
105105
Step with `"fixing"` errors is important as code base and especially third-party code are not ready to be build with clang. And files which failed to compile will not be scanned for errors.
106106

107+
#### Generated JS surface (`js/module.{js,d.ts}`)
108+
109+
`js/module.ts` is the source of truth. `js/module.js` and `js/module.d.ts` (and the `index.*` / `type_check.*` siblings) are tsc outputs of `js/tsconfig.json` — the npm package ships them as-is, so they're committed. Regenerate with `yarn build:javascript`. `yarn local:build` already chains it. CI rejects stale outputs via `.github/workflows/check-js-generated.yml` (`ci/check-js-generated.sh`).
110+
107111
### Tests
108112

109113
The tests for obs studio node are written in Typescript and use Mocha as test framework, with electron-mocha pacakage to make Mocha run in Electron, and Chai as assertion framework.

ci/check-js-generated.sh

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/bash
2+
# Verify that the committed tsc outputs under js/ match what regenerating
3+
# from js/module.ts would produce. CI runs `yarn build:javascript` first,
4+
# so any change under js/ at this point is a stale generated file.
5+
dirty=$(git status --porcelain -- js/)
6+
7+
set +x
8+
if [[ $dirty ]]; then
9+
echo "================================================="
10+
echo "Generated JS files are stale. Run locally:"
11+
echo " yarn build:javascript"
12+
echo "and commit the regenerated files."
13+
echo ""
14+
echo "Stale files:"
15+
echo "$dirty"
16+
echo "================================================="
17+
exit 1
18+
fi

ci/run-unit-tests.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"use strict";
2+
3+
const { spawnSync } = require("child_process");
4+
const fs = require("fs");
5+
const path = require("path");
6+
7+
const buildDirectory =
8+
process.env.SLBUILDDIRECTORY ||
9+
process.env.SLBuildDirectory ||
10+
process.env.BUILD_DIRECTORY ||
11+
"build";
12+
const buildConfig = process.env.BUILD_CONFIG || process.env.BuildConfig || "RelWithDebInfo";
13+
const target = "obs_studio_client_unit_tests";
14+
// Match the TEST_PREFIX passed to catch_discover_tests so ctest only runs this unit-test suite.
15+
const testPattern = `^${target}::`;
16+
17+
function hasDiscoveredTests() {
18+
const testsDirectory = path.join(buildDirectory, "obs-studio-client");
19+
20+
try {
21+
return fs
22+
.readdirSync(testsDirectory)
23+
.some(fileName => fileName.startsWith(`${target}-`) && fileName.endsWith("_tests.cmake"));
24+
} catch {
25+
return false;
26+
}
27+
}
28+
29+
function run(command, args) {
30+
const result = spawnSync(command, args, { stdio: "inherit" });
31+
32+
if (result.error) {
33+
console.error(result.error.message);
34+
process.exit(1);
35+
}
36+
37+
if (result.status !== 0) {
38+
process.exit(result.status);
39+
}
40+
}
41+
42+
const skipBuild =
43+
process.env.OSN_SKIP_UNIT_TEST_BUILD === "1" ||
44+
// Test jobs run from uploaded build artifacts. Building again can force CMake
45+
// to reconfigure FetchContent checkouts whose hidden .git directories were not uploaded.
46+
(process.env.GITHUB_ACTIONS === "true" && hasDiscoveredTests());
47+
48+
if (skipBuild) {
49+
console.log("Skipping unit-test build; discovered CTest tests are already present.");
50+
} else {
51+
run("cmake", ["--build", buildDirectory, "--config", buildConfig, "--target", target]);
52+
}
53+
54+
run("ctest", ["--test-dir", buildDirectory, "-C", buildConfig, "--output-on-failure", "-R", testPattern]);

js/module.d.ts

Lines changed: 28 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,6 @@ export declare const InputFactory: IInputFactory;
296296
export declare const SceneFactory: ISceneFactory;
297297
export declare const FilterFactory: IFilterFactory;
298298
export declare const TransitionFactory: ITransitionFactory;
299-
export declare const DisplayFactory: IDisplayFactory;
300299
export declare const VolmeterFactory: IVolmeterFactory;
301300
export declare const FaderFactory: IFaderFactory;
302301
export declare const Audio: IAudio;
@@ -351,19 +350,15 @@ export interface IIPC {
351350
disconnect(): void;
352351
}
353352
export interface IGlobal {
354-
startup(locale: string, path?: string): void;
355-
shutdown(): void;
356353
getOutputFlagsFromId(id: string): number;
357354
setOutputSource(channel: number, input: ISource): void;
358355
getOutputSource(channel: number): ISource;
359356
addSceneToBackstage(input: ISource): void;
360357
removeSceneFromBackstage(input: ISource): void;
361358
readonly totalFrames: number;
362359
readonly laggedFrames: number;
363-
readonly initialized: boolean;
364360
locale: string;
365361
multipleRendering: boolean;
366-
readonly version: number;
367362
readonly cpuPercentage: number;
368363
readonly currentFrameRate: number;
369364
readonly averageFrameRenderTime: number;
@@ -424,7 +419,6 @@ export interface INumberDetails {
424419
readonly step: number;
425420
}
426421
export interface IProperty {
427-
readonly status: number;
428422
readonly name: string;
429423
readonly description: string;
430424
readonly longDescription: string;
@@ -433,11 +427,14 @@ export interface IProperty {
433427
readonly type: EPropertyType;
434428
readonly value: any;
435429
next(): IProperty;
430+
previous(): IProperty;
431+
is_first(): boolean;
432+
is_last(): boolean;
436433
modified(): boolean;
437434
}
438435
export interface IProperties {
439-
readonly status: number;
440436
first(): IProperty;
437+
last(): IProperty;
441438
count(): number;
442439
get(name: string): IProperty;
443440
}
@@ -522,8 +519,9 @@ export interface IInput extends ISource {
522519
sendFocus(focus: boolean): void;
523520
sendKeyClick(eventData: IKeyEvent, keyUp: boolean): void;
524521
setFilterOrder(filter: IFilter, movement: EOrderMovement): void;
525-
setFilterOrder(filter: IFilter, movement: EOrderMovement): void;
522+
copyFilters(other: IInput): boolean;
526523
readonly filters: IFilter[];
524+
readonly active: boolean;
527525
readonly width: number;
528526
readonly height: number;
529527
getDuration(): number;
@@ -532,6 +530,8 @@ export interface IInput extends ISource {
532530
pause(): void;
533531
restart(): void;
534532
stop(): void;
533+
getMediaState(): number;
534+
load(): void;
535535
}
536536
export interface ISceneFactory {
537537
create(name: string): IScene;
@@ -547,6 +547,13 @@ export interface IScene extends ISource {
547547
findItem(id: string | number): ISceneItem;
548548
getItemAtIdx(idx: number): ISceneItem;
549549
getItems(): ISceneItem[];
550+
getItemsInRange(fromIndex: number, toIndex: number): ISceneItem[];
551+
load(): void;
552+
sendMouseClick(eventData: IMouseEvent, type: EMouseButtonType, mouseUp: boolean, clickCount: number): void;
553+
sendMouseMove(eventData: IMouseEvent, mouseLeave: boolean): void;
554+
sendMouseWheel(eventData: IMouseEvent, x_delta: number, y_delta: number): void;
555+
sendFocus(focus: boolean): void;
556+
sendKeyClick(eventData: IKeyEvent, keyUp: boolean): void;
550557
}
551558
export interface ISceneItem {
552559
readonly source: IInput;
@@ -588,6 +595,12 @@ export interface ITransition extends ISource {
588595
clear(): void;
589596
set(input: ISource): void;
590597
start(ms: number, input: ISource): void;
598+
load(): void;
599+
sendMouseClick(eventData: IMouseEvent, type: EMouseButtonType, mouseUp: boolean, clickCount: number): void;
600+
sendMouseMove(eventData: IMouseEvent, mouseLeave: boolean): void;
601+
sendMouseWheel(eventData: IMouseEvent, x_delta: number, y_delta: number): void;
602+
sendFocus(focus: boolean): void;
603+
sendKeyClick(eventData: IKeyEvent, keyUp: boolean): void;
591604
}
592605
export interface IConfigurable {
593606
update(settings: ISettings): void;
@@ -631,26 +644,6 @@ export interface IVolmeter {
631644
}
632645
export interface ICallbackData {
633646
}
634-
export interface IDisplayFactory {
635-
create(source?: IInput): IDisplay;
636-
}
637-
export interface IDisplay {
638-
destroy(): void;
639-
setPosition(x: number, y: number): void;
640-
getPosition(): IVec2;
641-
setSize(x: number, y: number): void;
642-
getSize(): IVec2;
643-
getPreviewOffset(): IVec2;
644-
getPreviewSize(x: number, y: number): void;
645-
shouldDrawUI: boolean;
646-
paddingSize: number;
647-
setPaddingColor(r: number, g: number, b: number, a: number): void;
648-
setBackgroundColor(r: number, g: number, b: number, a: number): void;
649-
setOutlineColor(r: number, g: number, b: number, a: number): void;
650-
setGuidelineColor(r: number, g: number, b: number, a: number): void;
651-
setResizeBoxOuterColor(r: number, g: number, b: number, a: number): void;
652-
setResizeBoxInnerColor(r: number, g: number, b: number, a: number): void;
653-
}
654647
export interface IVideoInfo {
655648
fpsNum: number;
656649
fpsDen: number;
@@ -692,22 +685,18 @@ export interface IAudioFactory {
692685
disableAudioDucking: boolean;
693686
disableAudioDuckingLegacy: boolean;
694687
}
695-
export interface IModuleFactory extends IFactoryTypes {
688+
export interface IModuleFactory {
696689
open(binPath: string, dataPath: string): IModule;
697-
loadAll(): void;
698-
addPath(path: string, dataPath: string): void;
699-
logLoaded(): void;
700690
modules(): String[];
701691
}
702692
export interface IModule {
703693
initialize(): void;
704-
filename(): string;
705-
name(): string;
706-
author(): string;
707-
description(): string;
708-
binPath(): string;
709-
dataPath(): string;
710-
status(): number;
694+
readonly fileName: string;
695+
readonly name: string;
696+
readonly author: string;
697+
readonly description: string;
698+
readonly binaryPath: string;
699+
readonly dataPath: string;
711700
}
712701
export declare function addItems(scene: IScene, sceneItems: ISceneItemInfo[]): ISceneItem[];
713702
export interface FilterInfo {

js/module.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use strict";
22
Object.defineProperty(exports, "__esModule", { value: true });
3-
exports.NodeObs = exports.AdvancedReplayBufferFactory = exports.SimpleReplayBufferFactory = exports.AudioEncoderFactory = exports.AdvancedRecordingFactory = exports.SimpleRecordingFactory = exports.AudioTrackFactory = exports.NetworkFactory = exports.ReconnectFactory = exports.DelayFactory = exports.EnhancedBroadcastingSimpleStreamingFactory = exports.EnhancedBroadcastingAdvancedStreamingFactory = exports.AdvancedStreamingFactory = exports.SimpleStreamingFactory = exports.ServiceFactory = exports.VideoEncoderFactory = exports.IPC = exports.ModuleFactory = exports.AudioFactory = exports.Audio = exports.FaderFactory = exports.VolmeterFactory = exports.DisplayFactory = exports.TransitionFactory = exports.FilterFactory = exports.SceneFactory = exports.InputFactory = exports.VideoFactory = exports.Video = exports.Global = exports.DefaultPluginPathMac = exports.DefaultPluginDataPath = exports.DefaultPluginPath = exports.DefaultDataPath = exports.DefaultBinPath = exports.DefaultDrawPluginPath = exports.DefaultOpenGLPath = exports.DefaultD3D11Path = void 0;
3+
exports.NodeObs = exports.AdvancedReplayBufferFactory = exports.SimpleReplayBufferFactory = exports.AudioEncoderFactory = exports.AdvancedRecordingFactory = exports.SimpleRecordingFactory = exports.AudioTrackFactory = exports.NetworkFactory = exports.ReconnectFactory = exports.DelayFactory = exports.EnhancedBroadcastingSimpleStreamingFactory = exports.EnhancedBroadcastingAdvancedStreamingFactory = exports.AdvancedStreamingFactory = exports.SimpleStreamingFactory = exports.ServiceFactory = exports.VideoEncoderFactory = exports.IPC = exports.ModuleFactory = exports.AudioFactory = exports.Audio = exports.FaderFactory = exports.VolmeterFactory = exports.TransitionFactory = exports.FilterFactory = exports.SceneFactory = exports.InputFactory = exports.VideoFactory = exports.Video = exports.Global = exports.DefaultPluginPathMac = exports.DefaultPluginDataPath = exports.DefaultPluginPath = exports.DefaultDataPath = exports.DefaultBinPath = exports.DefaultDrawPluginPath = exports.DefaultOpenGLPath = exports.DefaultD3D11Path = void 0;
44
exports.addItems = addItems;
55
exports.createSources = createSources;
66
exports.getSourcesSize = getSourcesSize;
@@ -25,7 +25,6 @@ exports.InputFactory = obs.Input;
2525
exports.SceneFactory = obs.Scene;
2626
exports.FilterFactory = obs.Filter;
2727
exports.TransitionFactory = obs.Transition;
28-
exports.DisplayFactory = obs.Display;
2928
exports.VolmeterFactory = obs.Volmeter;
3029
exports.FaderFactory = obs.Fader;
3130
exports.Audio = obs.Audio;
@@ -66,7 +65,6 @@ function createSources(sources) {
6665
const items = [];
6766
if (Array.isArray(sources)) {
6867
sources.forEach(function (source) {
69-
var _a, _b, _c;
7068
let newSource = null;
7169
try {
7270
newSource = obs.Input.create(source.type, source.name, source.settings);
@@ -77,17 +75,16 @@ function createSources(sources) {
7775
}
7876
if (newSource) {
7977
if (newSource.audioMixers) {
80-
newSource.muted = (_a = source.muted) !== null && _a !== void 0 ? _a : false;
81-
newSource.volume = (_b = source.volume) !== null && _b !== void 0 ? _b : 1;
82-
newSource.syncOffset = (_c = source.syncOffset) !== null && _c !== void 0 ? _c : { sec: 0, nsec: 0 };
78+
newSource.muted = source.muted ?? false;
79+
newSource.volume = source.volume ?? 1;
80+
newSource.syncOffset = source.syncOffset ?? { sec: 0, nsec: 0 };
8381
}
8482
newSource.deinterlaceMode = source.deinterlaceMode;
8583
newSource.deinterlaceFieldOrder = source.deinterlaceFieldOrder;
8684
items.push(newSource);
8785
const filters = source.filters;
8886
if (Array.isArray(filters)) {
8987
filters.forEach(function (filter) {
90-
var _a;
9188
let ObsFilter = null;
9289
try {
9390
ObsFilter = obs.Filter.create(filter.type, filter.name, filter.settings);
@@ -96,7 +93,7 @@ function createSources(sources) {
9693
console.error(`[OSN] Failed to create filter "${filter.name}" for source "${source.name}":`, filterError instanceof Error ? filterError.message : filterError);
9794
}
9895
if (ObsFilter) {
99-
ObsFilter.enabled = (_a = filter.enabled) !== null && _a !== void 0 ? _a : true;
96+
ObsFilter.enabled = filter.enabled ?? true;
10097
newSource.addFilter(ObsFilter);
10198
ObsFilter.release();
10299
}

0 commit comments

Comments
 (0)