noobs 0.0.6 → 0.0.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -4
- package/dist/noobs.node +0 -0
- package/index.d.ts +2 -13
- package/package.json +2 -2
- package/src/main.cpp +41 -187
- package/src/obs_interface.cpp +69 -111
- package/src/obs_interface.h +3 -6
- package/src/utils.cpp +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
# No(de)obs
|
|
2
|
+
> Previously named "warcraft-recorder-obs-engine"
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
This library is still very young, you probably don't want to use it.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Native node bindings to libobs, built for Warcraft Recorder.
|
|
7
|
+
|
|
8
|
+
An alternative but less mature `obs-studio-node` with a focus on recording gameplay from a replay buffer.
|
|
9
|
+
|
|
10
|
+
Uses a [custom](https://github.com/aza547/warcraft-recorder-obs-studio) fork of libobs to:
|
|
11
|
+
- Enable "replay_buffer to recording".
|
|
12
|
+
- Allow libobs to be used as a library easier.
|
|
6
13
|
|
|
7
14
|
## Installation
|
|
8
15
|
|
|
@@ -37,8 +44,12 @@ TODO
|
|
|
37
44
|
## Building from Source
|
|
38
45
|
|
|
39
46
|
```bash
|
|
40
|
-
npm install
|
|
41
|
-
npm run build
|
|
47
|
+
npm install # install deps
|
|
48
|
+
npm run build # compile it
|
|
49
|
+
|
|
50
|
+
npm version patch # version bump
|
|
51
|
+
npm pack # build tgz locally
|
|
52
|
+
npm publish # publish to npm
|
|
42
53
|
```
|
|
43
54
|
|
|
44
55
|
## License
|
package/dist/noobs.node
CHANGED
|
Binary file
|
package/index.d.ts
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
|
-
interface ProcessInfo {
|
|
2
|
-
name: string;
|
|
3
|
-
pid: number;
|
|
4
|
-
parentPid: number;
|
|
5
|
-
threads: number;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
1
|
type Signal = { // TODO export type?
|
|
9
2
|
id: string;
|
|
10
3
|
code: number;
|
|
11
4
|
}
|
|
12
5
|
|
|
13
6
|
interface Noobs {
|
|
14
|
-
getUptime(): number;
|
|
15
|
-
listProcesses(): Promise<ProcessInfo[]>;
|
|
16
|
-
|
|
17
7
|
ObsInit(
|
|
18
8
|
pluginPath: string,
|
|
19
9
|
logPath: string,
|
|
@@ -28,10 +18,9 @@ interface Noobs {
|
|
|
28
18
|
ObsStopRecording(): void;
|
|
29
19
|
ObsGetLastRecording(): string;
|
|
30
20
|
|
|
31
|
-
|
|
21
|
+
ObsInitPreview(hwnd: Buffer): void;
|
|
22
|
+
ObsShowPreview(x: number, y: number, width: number, height: number): void;
|
|
32
23
|
ObsHidePreview(): void;
|
|
33
|
-
ObsResizePreview(width: number, height: number): void;
|
|
34
|
-
ObsMovePreview(x: number, y: number): void;
|
|
35
24
|
}
|
|
36
25
|
|
|
37
26
|
declare const noobs: Noobs;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "noobs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.29",
|
|
4
4
|
"description": "A native Node.js addon with libobs bindings for Warcraft Recorder.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"dist/**/*"
|
|
21
21
|
],
|
|
22
22
|
"author": "Warcraft Recorder LTD",
|
|
23
|
-
"license": "
|
|
23
|
+
"license": "LGPL-2.0",
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/node": "^24.1.0",
|
|
26
26
|
"node-gyp": "^11.2.0"
|
package/src/main.cpp
CHANGED
|
@@ -12,41 +12,6 @@
|
|
|
12
12
|
|
|
13
13
|
ObsInterface* obs = nullptr;
|
|
14
14
|
|
|
15
|
-
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
|
16
|
-
if (uMsg == WM_DESTROY) {
|
|
17
|
-
PostQuitMessage(0);
|
|
18
|
-
return 0;
|
|
19
|
-
}
|
|
20
|
-
return DefWindowProc(hwnd, uMsg, wParam, lParam);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
void WindowThread(std::promise<HWND> hwndPromise) {
|
|
25
|
-
const wchar_t CLASS_NAME[] = L"MyWindowClass";
|
|
26
|
-
HINSTANCE hInstance = GetModuleHandle(nullptr);
|
|
27
|
-
|
|
28
|
-
WNDCLASSW wc = {};
|
|
29
|
-
wc.lpfnWndProc = WindowProc;
|
|
30
|
-
wc.hInstance = hInstance;
|
|
31
|
-
wc.lpszClassName = CLASS_NAME;
|
|
32
|
-
RegisterClassW(&wc);
|
|
33
|
-
|
|
34
|
-
HWND hwnd = CreateWindowExW(
|
|
35
|
-
0, CLASS_NAME, L"My Window",
|
|
36
|
-
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 800, 600,
|
|
37
|
-
nullptr, nullptr, hInstance, nullptr
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
hwndPromise.set_value(hwnd); // Send hwnd back to main thread
|
|
41
|
-
ShowWindow(hwnd, SW_SHOW);
|
|
42
|
-
|
|
43
|
-
MSG msg = {};
|
|
44
|
-
while (GetMessage(&msg, nullptr, 0, 0)) {
|
|
45
|
-
TranslateMessage(&msg);
|
|
46
|
-
DispatchMessage(&msg);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
15
|
Napi::Value ObsInit(const Napi::CallbackInfo& info) {
|
|
51
16
|
bool valid = info.Length() == 5 &&
|
|
52
17
|
info[0].IsString() && // Plugin path
|
|
@@ -73,7 +38,6 @@ Napi::Value ObsInit(const Napi::CallbackInfo& info) {
|
|
|
73
38
|
return info.Env().Undefined();
|
|
74
39
|
}
|
|
75
40
|
|
|
76
|
-
|
|
77
41
|
Napi::Value ObsShutdown(const Napi::CallbackInfo& info) {
|
|
78
42
|
delete obs;
|
|
79
43
|
obs = nullptr;
|
|
@@ -83,8 +47,10 @@ Napi::Value ObsShutdown(const Napi::CallbackInfo& info) {
|
|
|
83
47
|
Napi::Value ObsStartBuffer(const Napi::CallbackInfo& info) {
|
|
84
48
|
blog(LOG_INFO, "ObsStartBuffer called");
|
|
85
49
|
|
|
86
|
-
if (!obs)
|
|
50
|
+
if (!obs) {
|
|
51
|
+
blog(LOG_ERROR, "ObsStartBuffer called but obs is not initialized");
|
|
87
52
|
throw std::runtime_error("Obs not initialized");
|
|
53
|
+
}
|
|
88
54
|
|
|
89
55
|
obs->startBuffering();
|
|
90
56
|
return info.Env().Undefined();
|
|
@@ -92,6 +58,7 @@ Napi::Value ObsStartBuffer(const Napi::CallbackInfo& info) {
|
|
|
92
58
|
|
|
93
59
|
Napi::Value ObsStartRecording(const Napi::CallbackInfo& info) {
|
|
94
60
|
if (!obs) {
|
|
61
|
+
blog(LOG_ERROR, "ObsStartRecording called but obs is not initialized");
|
|
95
62
|
throw std::runtime_error("Obs not initialized");
|
|
96
63
|
}
|
|
97
64
|
|
|
@@ -106,29 +73,37 @@ Napi::Value ObsStartRecording(const Napi::CallbackInfo& info) {
|
|
|
106
73
|
}
|
|
107
74
|
|
|
108
75
|
Napi::Value ObsStopRecording(const Napi::CallbackInfo& info) {
|
|
109
|
-
if (!obs)
|
|
76
|
+
if (!obs) {
|
|
77
|
+
blog(LOG_ERROR, "ObsStopRecording called but obs is not initialized");
|
|
110
78
|
throw std::runtime_error("Obs not initialized");
|
|
79
|
+
}
|
|
111
80
|
|
|
112
81
|
obs->stopRecording();
|
|
113
82
|
return info.Env().Undefined();
|
|
114
83
|
}
|
|
115
84
|
|
|
116
85
|
Napi::Value ObsGetLastRecording(const Napi::CallbackInfo& info) {
|
|
117
|
-
if (!obs)
|
|
86
|
+
if (!obs) {
|
|
87
|
+
blog(LOG_ERROR, "ObsGetLastRecording called but obs is not initialized");
|
|
118
88
|
throw std::runtime_error("Obs not initialized");
|
|
89
|
+
}
|
|
119
90
|
|
|
120
91
|
std::string lastRecording = obs->getLastRecording();
|
|
121
92
|
return Napi::String::New(info.Env(), lastRecording);
|
|
122
93
|
}
|
|
123
94
|
|
|
124
|
-
Napi::Value
|
|
125
|
-
blog(LOG_INFO, "
|
|
95
|
+
Napi::Value ObsInitPreview(const Napi::CallbackInfo& info) {
|
|
96
|
+
blog(LOG_INFO, "ObsInitPreview called");
|
|
126
97
|
|
|
127
|
-
if (!obs)
|
|
98
|
+
if (!obs) {
|
|
99
|
+
blog(LOG_ERROR, "ObsInitPreview called but obs is not initialized");
|
|
128
100
|
throw std::runtime_error("Obs not initialized");
|
|
101
|
+
}
|
|
129
102
|
|
|
130
|
-
|
|
131
|
-
|
|
103
|
+
bool valid = info.Length() == 1 && info[0].IsBuffer();
|
|
104
|
+
|
|
105
|
+
if (!valid) {
|
|
106
|
+
Napi::TypeError::New(info.Env(), "Invalid arguments passed to ObsInitPreview").ThrowAsJavaScriptException();
|
|
132
107
|
return info.Env().Undefined();
|
|
133
108
|
}
|
|
134
109
|
|
|
@@ -137,184 +112,63 @@ Napi::Value ObsShowPreview(const Napi::CallbackInfo& info) {
|
|
|
137
112
|
if (buffer.Length() < sizeof(HWND)) {
|
|
138
113
|
Napi::TypeError::New(info.Env(), "Buffer too small for HWND").ThrowAsJavaScriptException();
|
|
139
114
|
return info.Env().Undefined();
|
|
140
|
-
} else {
|
|
141
|
-
blog(LOG_INFO, "ObsShowPreview buffer length: %zu", buffer.Length());
|
|
142
|
-
blog(LOG_INFO, "ObsShowPreview hwnd length: %zu", sizeof(HWND));
|
|
143
115
|
}
|
|
144
116
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
uint8_t bytes[8];
|
|
148
|
-
uint64_t value;
|
|
149
|
-
HWND hwnd;
|
|
150
|
-
} hwndUnion;
|
|
151
|
-
|
|
152
|
-
memcpy(hwndUnion.bytes, buffer.Data(), sizeof(hwndUnion.bytes));
|
|
153
|
-
|
|
154
|
-
obs->showPreview(hwndUnion.hwnd);
|
|
117
|
+
HWND hwnd = *reinterpret_cast<HWND*>(buffer.Data());
|
|
118
|
+
obs->initPreview(hwnd);
|
|
155
119
|
return info.Env().Undefined();
|
|
156
120
|
}
|
|
157
121
|
|
|
158
|
-
Napi::Value
|
|
159
|
-
|
|
160
|
-
throw std::runtime_error("Obs not initialized");
|
|
122
|
+
Napi::Value ObsShowPreview(const Napi::CallbackInfo& info) {
|
|
123
|
+
blog(LOG_INFO, "ObsShowPreview called");
|
|
161
124
|
|
|
162
|
-
if (
|
|
163
|
-
|
|
164
|
-
|
|
125
|
+
if (!obs) {
|
|
126
|
+
blog(LOG_ERROR, "ObsShowPreview called but obs is not initialized");
|
|
127
|
+
throw std::runtime_error("Obs not initialized");
|
|
165
128
|
}
|
|
166
129
|
|
|
167
|
-
|
|
168
|
-
|
|
130
|
+
bool valid = info.Length() == 4 &&
|
|
131
|
+
info[0].IsNumber() && // X
|
|
132
|
+
info[1].IsNumber() && // Y
|
|
133
|
+
info[2].IsNumber() && // Width
|
|
134
|
+
info[3].IsNumber(); // Height
|
|
169
135
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
Napi::Value ObsMovePreview(const Napi::CallbackInfo& info) {
|
|
175
|
-
if (!obs)
|
|
176
|
-
throw std::runtime_error("Obs not initialized");
|
|
177
|
-
|
|
178
|
-
if (info.Length() < 2) {
|
|
179
|
-
Napi::TypeError::New(info.Env(), "Expected x and y").ThrowAsJavaScriptException();
|
|
136
|
+
if (!valid) {
|
|
137
|
+
Napi::TypeError::New(info.Env(), "Invalid arguments passed to ObsShowPreview").ThrowAsJavaScriptException();
|
|
180
138
|
return info.Env().Undefined();
|
|
181
139
|
}
|
|
182
140
|
|
|
183
141
|
int x = info[0].As<Napi::Number>().Int32Value();
|
|
184
142
|
int y = info[1].As<Napi::Number>().Int32Value();
|
|
143
|
+
int width = info[2].As<Napi::Number>().Int32Value();
|
|
144
|
+
int height = info[3].As<Napi::Number>().Int32Value();
|
|
185
145
|
|
|
186
|
-
obs->
|
|
146
|
+
obs->showPreview(x, y, width, height);
|
|
187
147
|
return info.Env().Undefined();
|
|
188
148
|
}
|
|
189
149
|
|
|
190
150
|
Napi::Value ObsHidePreview(const Napi::CallbackInfo& info) {
|
|
191
|
-
if (!obs)
|
|
151
|
+
if (!obs) {
|
|
152
|
+
blog(LOG_ERROR, "ObsHidePreview called but obs is not initialized");
|
|
192
153
|
throw std::runtime_error("Obs not initialized");
|
|
154
|
+
}
|
|
193
155
|
|
|
194
156
|
obs->hidePreview();
|
|
195
157
|
return info.Env().Undefined();
|
|
196
158
|
}
|
|
197
159
|
|
|
198
|
-
|
|
199
|
-
Napi::Number GetUptime(const Napi::CallbackInfo& info) {
|
|
200
|
-
DWORD ticks = GetTickCount();
|
|
201
|
-
return Napi::Number::New(info.Env(), static_cast<double>(ticks));
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
class ProcessListWorker : public Napi::AsyncWorker {
|
|
205
|
-
public:
|
|
206
|
-
ProcessListWorker(Napi::Function& callback)
|
|
207
|
-
: Napi::AsyncWorker(callback) {}
|
|
208
|
-
|
|
209
|
-
~ProcessListWorker() {}
|
|
210
|
-
|
|
211
|
-
// This runs on a background thread - won't block JS
|
|
212
|
-
void Execute() override {
|
|
213
|
-
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
214
|
-
if (snapshot == INVALID_HANDLE_VALUE) {
|
|
215
|
-
SetError("Failed to create process snapshot");
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
PROCESSENTRY32W pe32;
|
|
220
|
-
pe32.dwSize = sizeof(PROCESSENTRY32W);
|
|
221
|
-
|
|
222
|
-
if (!Process32FirstW(snapshot, &pe32)) {
|
|
223
|
-
CloseHandle(snapshot);
|
|
224
|
-
SetError("Failed to get first process");
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
do {
|
|
229
|
-
ProcessInfo info;
|
|
230
|
-
|
|
231
|
-
// Convert wide string to UTF-8
|
|
232
|
-
int utf8Length = WideCharToMultiByte(CP_UTF8, 0, pe32.szExeFile, -1, nullptr, 0, nullptr, nullptr);
|
|
233
|
-
if (utf8Length > 0) {
|
|
234
|
-
std::vector<char> utf8Buffer(utf8Length);
|
|
235
|
-
WideCharToMultiByte(CP_UTF8, 0, pe32.szExeFile, -1, utf8Buffer.data(), utf8Length, nullptr, nullptr);
|
|
236
|
-
|
|
237
|
-
info.name = std::string(utf8Buffer.data());
|
|
238
|
-
info.pid = pe32.th32ProcessID;
|
|
239
|
-
info.parentPid = pe32.th32ParentProcessID;
|
|
240
|
-
info.threads = pe32.cntThreads;
|
|
241
|
-
|
|
242
|
-
processes.push_back(info);
|
|
243
|
-
}
|
|
244
|
-
} while (Process32NextW(snapshot, &pe32));
|
|
245
|
-
|
|
246
|
-
CloseHandle(snapshot);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// This runs back on the main thread
|
|
250
|
-
void OnOK() override {
|
|
251
|
-
Napi::HandleScope scope(Env());
|
|
252
|
-
Napi::Array result = Napi::Array::New(Env());
|
|
253
|
-
|
|
254
|
-
for (size_t i = 0; i < processes.size(); i++) {
|
|
255
|
-
Napi::Object process = Napi::Object::New(Env());
|
|
256
|
-
process.Set("name", Napi::String::New(Env(), processes[i].name));
|
|
257
|
-
process.Set("pid", Napi::Number::New(Env(), processes[i].pid));
|
|
258
|
-
process.Set("parentPid", Napi::Number::New(Env(), processes[i].parentPid));
|
|
259
|
-
process.Set("threads", Napi::Number::New(Env(), processes[i].threads));
|
|
260
|
-
result.Set(i, process);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
Callback().Call({Env().Null(), result});
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
void OnError(const Napi::Error& e) override {
|
|
267
|
-
Napi::HandleScope scope(Env());
|
|
268
|
-
Callback().Call({e.Value(), Env().Undefined()});
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
private:
|
|
272
|
-
struct ProcessInfo {
|
|
273
|
-
std::string name;
|
|
274
|
-
DWORD pid;
|
|
275
|
-
DWORD parentPid;
|
|
276
|
-
DWORD threads;
|
|
277
|
-
};
|
|
278
|
-
|
|
279
|
-
std::vector<ProcessInfo> processes;
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
Napi::Value ListProcesses(const Napi::CallbackInfo& info) {
|
|
283
|
-
Napi::Env env = info.Env();
|
|
284
|
-
auto deferred = Napi::Promise::Deferred::New(env);
|
|
285
|
-
|
|
286
|
-
auto callback = Napi::Function::New(env, [deferred](const Napi::CallbackInfo& info) {
|
|
287
|
-
if (!info[0].IsNull()) {
|
|
288
|
-
// Error case
|
|
289
|
-
deferred.Reject(info[0]);
|
|
290
|
-
} else {
|
|
291
|
-
// Success case
|
|
292
|
-
deferred.Resolve(info[1]);
|
|
293
|
-
}
|
|
294
|
-
return info.Env().Undefined();
|
|
295
|
-
});
|
|
296
|
-
|
|
297
|
-
ProcessListWorker* worker = new ProcessListWorker(callback);
|
|
298
|
-
worker->Queue();
|
|
299
|
-
|
|
300
|
-
return deferred.Promise();
|
|
301
|
-
}
|
|
302
|
-
|
|
303
160
|
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
304
161
|
exports.Set("ObsInit", Napi::Function::New(env, ObsInit));
|
|
305
162
|
exports.Set("ObsShutdown", Napi::Function::New(env, ObsShutdown));
|
|
163
|
+
|
|
306
164
|
exports.Set("ObsStartBuffer", Napi::Function::New(env, ObsStartBuffer));
|
|
307
165
|
exports.Set("ObsStartRecording", Napi::Function::New(env, ObsStartRecording));
|
|
308
166
|
exports.Set("ObsStopRecording", Napi::Function::New(env, ObsStopRecording));
|
|
309
167
|
exports.Set("ObsGetLastRecording", Napi::Function::New(env, ObsGetLastRecording));
|
|
310
168
|
|
|
169
|
+
exports.Set("ObsInitPreview", Napi::Function::New(env, ObsInitPreview));
|
|
311
170
|
exports.Set("ObsShowPreview", Napi::Function::New(env, ObsShowPreview));
|
|
312
171
|
exports.Set("ObsHidePreview", Napi::Function::New(env, ObsHidePreview));
|
|
313
|
-
exports.Set("ObsResizePreview", Napi::Function::New(env, ObsResizePreview));
|
|
314
|
-
exports.Set("ObsMovePreview", Napi::Function::New(env, ObsMovePreview));
|
|
315
|
-
|
|
316
|
-
exports.Set("getUptime", Napi::Function::New(env, GetUptime));
|
|
317
|
-
exports.Set("listProcesses", Napi::Function::New(env, ListProcesses));
|
|
318
172
|
return exports;
|
|
319
173
|
}
|
|
320
174
|
|
package/src/obs_interface.cpp
CHANGED
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
#include <vector>
|
|
8
8
|
#include <thread>
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
10
|
std::vector<std::string> ObsInterface::get_available_video_encoders()
|
|
13
11
|
{
|
|
14
12
|
std::vector<std::string> encoders;
|
|
@@ -200,23 +198,23 @@ obs_output_t* ObsInterface::create_output(const std::string& recordingPath) {
|
|
|
200
198
|
obs_data_release(settings);
|
|
201
199
|
|
|
202
200
|
blog(LOG_INFO, "Create venc");
|
|
203
|
-
video_encoder = obs_video_encoder_create("
|
|
201
|
+
video_encoder = obs_video_encoder_create("obs_x264", "simple_h264_stream", NULL, NULL);
|
|
204
202
|
|
|
205
203
|
if (!video_encoder) {
|
|
206
204
|
blog(LOG_ERROR, "Failed to create video encoder!");
|
|
207
205
|
throw std::runtime_error("Failed to create video encoder!");
|
|
208
206
|
}
|
|
209
207
|
|
|
210
|
-
blog(LOG_INFO, "Set video encoder settings");
|
|
211
|
-
obs_data_t* amf_settings = obs_data_create();
|
|
212
|
-
obs_data_set_string(amf_settings, "preset", "speed"); // Faster preset
|
|
213
|
-
//obs_data_set_int(amf_settings, "bitrate", 2500);
|
|
214
|
-
obs_data_set_string(amf_settings, "rate_control", "CQP");
|
|
215
|
-
obs_data_set_int(amf_settings, "cqp", 30);
|
|
216
|
-
obs_data_set_string(amf_settings, "profile", "main");
|
|
217
|
-
obs_data_set_int(amf_settings, "keyint_sec", 1); // Set keyframe interval to 1 second
|
|
218
|
-
obs_encoder_update(video_encoder, amf_settings);
|
|
219
|
-
obs_data_release(amf_settings);
|
|
208
|
+
// blog(LOG_INFO, "Set video encoder settings");
|
|
209
|
+
// obs_data_t* amf_settings = obs_data_create();
|
|
210
|
+
// obs_data_set_string(amf_settings, "preset", "speed"); // Faster preset
|
|
211
|
+
// //obs_data_set_int(amf_settings, "bitrate", 2500);
|
|
212
|
+
// obs_data_set_string(amf_settings, "rate_control", "CQP");
|
|
213
|
+
// obs_data_set_int(amf_settings, "cqp", 30);
|
|
214
|
+
// obs_data_set_string(amf_settings, "profile", "main");
|
|
215
|
+
// obs_data_set_int(amf_settings, "keyint_sec", 1); // Set keyframe interval to 1 second
|
|
216
|
+
// obs_encoder_update(video_encoder, amf_settings);
|
|
217
|
+
// obs_data_release(amf_settings);
|
|
220
218
|
|
|
221
219
|
blog(LOG_INFO, "Create aenc");
|
|
222
220
|
audio_encoder = obs_audio_encoder_create("ffmpeg_aac", "simple_aac", NULL, 0, NULL);
|
|
@@ -326,134 +324,94 @@ void ObsInterface::create_signal_handlers(obs_output_t *output) {
|
|
|
326
324
|
}
|
|
327
325
|
|
|
328
326
|
void draw_callback(void* data, uint32_t cx, uint32_t cy) {
|
|
329
|
-
// Render the OBS preview scene here
|
|
330
327
|
obs_render_main_texture();
|
|
331
328
|
}
|
|
332
329
|
|
|
333
|
-
void ObsInterface::
|
|
330
|
+
void ObsInterface::initPreview(HWND parent) {
|
|
334
331
|
blog(LOG_INFO, "ObsInterface::showPreview");
|
|
335
332
|
|
|
336
|
-
if (
|
|
337
|
-
blog(LOG_INFO, "
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
blog(LOG_ERROR, "Failed to create preview child window");
|
|
357
|
-
return;
|
|
333
|
+
if (!preview_hwnd) {
|
|
334
|
+
blog(LOG_INFO, "Creating preview child window");
|
|
335
|
+
|
|
336
|
+
preview_hwnd = CreateWindowExA(
|
|
337
|
+
0, // No extended styles
|
|
338
|
+
"STATIC", // Simple static control class (ANSI string)
|
|
339
|
+
"OBS Preview", // Window name (ANSI string)
|
|
340
|
+
WS_CHILD | WS_BORDER, // Child + border, NOT visible initially
|
|
341
|
+
0, 0, // Initial position (x, y)
|
|
342
|
+
0, 0, // Initial size (width, height)
|
|
343
|
+
parent, // Parent window (your Electron app)
|
|
344
|
+
NULL, // No menu
|
|
345
|
+
GetModuleHandle(NULL),
|
|
346
|
+
NULL
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
if (!preview_hwnd) {
|
|
350
|
+
blog(LOG_ERROR, "Failed to create preview child window");
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
358
353
|
}
|
|
359
354
|
|
|
360
|
-
|
|
361
|
-
|
|
355
|
+
if (!display) {
|
|
356
|
+
blog(LOG_INFO, "Create OBS display in child window");
|
|
362
357
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
358
|
+
gs_init_data gs_data = {};
|
|
359
|
+
gs_data.adapter = 0;
|
|
360
|
+
gs_data.cx = 1920; // TODO get from video context?
|
|
361
|
+
gs_data.cy = 1080; // TODO get from video context?
|
|
362
|
+
gs_data.format = GS_BGRA;
|
|
363
|
+
gs_data.zsformat = GS_ZS_NONE;
|
|
364
|
+
gs_data.num_backbuffers = 1;
|
|
365
|
+
gs_data.window.hwnd = preview_hwnd;
|
|
366
|
+
|
|
367
|
+
display = obs_display_create(&gs_data, 0x0);
|
|
368
|
+
|
|
369
|
+
if (!display) {
|
|
370
|
+
blog(LOG_ERROR, "Failed to create OBS display");
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
372
373
|
|
|
373
|
-
display = obs_display_create(&gs_data, 0x0);
|
|
374
|
-
if (display) {
|
|
375
374
|
obs_display_add_draw_callback(display, draw_callback, NULL);
|
|
376
|
-
blog(LOG_INFO, "OBS preview embedded successfully");
|
|
377
|
-
} else {
|
|
378
|
-
blog(LOG_ERROR, "Failed to create OBS display");
|
|
379
|
-
DestroyWindow(previewWindow);
|
|
380
|
-
previewHwnd = nullptr;
|
|
381
375
|
}
|
|
376
|
+
|
|
377
|
+
obs_display_set_enabled(display, false);
|
|
382
378
|
}
|
|
383
379
|
|
|
384
|
-
void ObsInterface::
|
|
385
|
-
blog(LOG_INFO, "ObsInterface::
|
|
380
|
+
void ObsInterface::showPreview(int x, int y, int width, int height) {
|
|
381
|
+
blog(LOG_INFO, "ObsInterface::showPreview");
|
|
386
382
|
|
|
387
|
-
if (!
|
|
388
|
-
blog(
|
|
383
|
+
if (!preview_hwnd || !display) {
|
|
384
|
+
blog(LOG_ERROR, "Preview window not initialized");
|
|
389
385
|
return;
|
|
390
386
|
}
|
|
391
387
|
|
|
392
|
-
// Resize the child window
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
NULL,
|
|
396
|
-
|
|
397
|
-
width, height,
|
|
398
|
-
|
|
388
|
+
// Resize and move the existing child window.
|
|
389
|
+
bool success = SetWindowPos(
|
|
390
|
+
preview_hwnd, // Handle to the child window
|
|
391
|
+
NULL, // No Z-order change
|
|
392
|
+
x, y, // New position (x, y)
|
|
393
|
+
width, height, // New size (width, height)
|
|
394
|
+
SWP_NOZORDER | SWP_NOACTIVATE // Don't change position, Z-order, or activation
|
|
399
395
|
);
|
|
400
396
|
|
|
401
|
-
if (!
|
|
397
|
+
if (!success) {
|
|
402
398
|
blog(LOG_ERROR, "Failed to resize preview window to (%d x %d)", width, height);
|
|
403
399
|
return;
|
|
404
400
|
}
|
|
405
401
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
// Resize the OBS display to match the new window size
|
|
409
|
-
if (display) {
|
|
410
|
-
obs_display_resize(display, width, height);
|
|
411
|
-
blog(LOG_INFO, "OBS display resized to (%d x %d)", width, height);
|
|
412
|
-
} else {
|
|
413
|
-
blog(LOG_WARNING, "No OBS display to resize");
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
void ObsInterface::movePreview(int x, int y) {
|
|
418
|
-
blog(LOG_INFO, "ObsInterface::movePreview to position (%d, %d)", x, y);
|
|
419
|
-
|
|
420
|
-
if (!previewHwnd) {
|
|
421
|
-
blog(LOG_WARNING, "No preview window to move");
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// Move the child window to the new position within the parent
|
|
426
|
-
BOOL success = SetWindowPos(
|
|
427
|
-
previewHwnd, // Handle to the child window
|
|
428
|
-
NULL, // No Z-order change
|
|
429
|
-
x, y, // New position (x, y)
|
|
430
|
-
0, 0, // Keep current size (ignored due to SWP_NOSIZE)
|
|
431
|
-
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE // Don't change size, Z-order, or activation
|
|
432
|
-
);
|
|
433
|
-
|
|
434
|
-
if (success) {
|
|
435
|
-
blog(LOG_INFO, "Preview window moved successfully to (%d, %d)", x, y);
|
|
436
|
-
} else {
|
|
437
|
-
blog(LOG_ERROR, "Failed to move preview window to (%d, %d)", x, y);
|
|
438
|
-
}
|
|
402
|
+
ShowWindow(preview_hwnd, SW_SHOW);
|
|
403
|
+
obs_display_set_enabled(display, true);
|
|
439
404
|
}
|
|
440
405
|
|
|
441
406
|
void ObsInterface::hidePreview() {
|
|
442
407
|
blog(LOG_INFO, "ObsInterface::hidePreview");
|
|
443
408
|
|
|
444
|
-
if (
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
display = nullptr;
|
|
448
|
-
blog(LOG_INFO, "OBS display destroyed");
|
|
409
|
+
if (preview_hwnd) {
|
|
410
|
+
ShowWindow(preview_hwnd, SW_HIDE);
|
|
411
|
+
blog(LOG_INFO, "Preview child window hidden");
|
|
449
412
|
}
|
|
450
413
|
|
|
451
|
-
|
|
452
|
-
if (previewHwnd) {
|
|
453
|
-
DestroyWindow(previewHwnd);
|
|
454
|
-
previewHwnd = nullptr;
|
|
455
|
-
blog(LOG_INFO, "Preview child window destroyed");
|
|
456
|
-
}
|
|
414
|
+
obs_display_set_enabled(display, false);
|
|
457
415
|
}
|
|
458
416
|
|
|
459
417
|
ObsInterface::ObsInterface(
|
package/src/obs_interface.h
CHANGED
|
@@ -24,16 +24,13 @@ class ObsInterface {
|
|
|
24
24
|
void stopRecording();
|
|
25
25
|
std::string getLastRecording();
|
|
26
26
|
|
|
27
|
-
void
|
|
27
|
+
void initPreview(HWND parent); // Must call this before showPreview to setup resources.
|
|
28
|
+
void showPreview(int x, int y, int width, int height); // Also used for moving and resizing.
|
|
28
29
|
void hidePreview();
|
|
29
|
-
void resizePreview(int width, int height);
|
|
30
|
-
void movePreview(int x, int y);
|
|
31
30
|
|
|
32
31
|
std::vector<std::string> get_available_video_encoders();
|
|
33
32
|
|
|
34
33
|
// TODO
|
|
35
|
-
// Show preview
|
|
36
|
-
// Hide preview
|
|
37
34
|
// Configure video
|
|
38
35
|
// Configure audio
|
|
39
36
|
// List audio source
|
|
@@ -48,7 +45,7 @@ class ObsInterface {
|
|
|
48
45
|
obs_encoder_t *video_encoder = nullptr;
|
|
49
46
|
obs_encoder_t *audio_encoder = nullptr;
|
|
50
47
|
obs_display_t *display = nullptr;
|
|
51
|
-
HWND
|
|
48
|
+
HWND preview_hwnd = nullptr; // window handle for scene preview
|
|
52
49
|
Napi::ThreadSafeFunction jscb; // javascript callback
|
|
53
50
|
|
|
54
51
|
void init_obs(const std::string& pluginPath, const std::string& dataPath);
|
package/src/utils.cpp
CHANGED
|
@@ -15,7 +15,7 @@ void log_handler(int lvl, const char *msg, va_list args, void *p) {
|
|
|
15
15
|
auto time_t = std::chrono::system_clock::to_time_t(now);
|
|
16
16
|
|
|
17
17
|
std::stringstream filename_stream;
|
|
18
|
-
filename_stream << "
|
|
18
|
+
filename_stream << "OBS-" << std::put_time(std::localtime(&time_t), "%Y-%m-%d") << ".log";
|
|
19
19
|
|
|
20
20
|
// Use the provided directory path and append the filename
|
|
21
21
|
std::string log_dir = static_cast<const char*>(p);
|