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 CHANGED
@@ -1,8 +1,15 @@
1
1
  # No(de)obs
2
+ > Previously named "warcraft-recorder-obs-engine"
2
3
 
3
- Node bindings to libobs, built for Warcraft Recorder. An alternative but less mature `obs-studio-node` with a focus on recording gameplay from a replay buffer.
4
+ This library is still very young, you probably don't want to use it.
4
5
 
5
- Uses a [custom](https://github.com/aza547/warcraft-recorder-obs-studio) fork of libobs to enable "replay_buffer to recording" as well as some less interesting improvements to allow libobs to be easier used as a library and not through OBS studio itself.
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
- ObsShowPreview(hwnd: Buffer): void;
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.6",
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": "ISC",
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 ObsShowPreview(const Napi::CallbackInfo& info) {
125
- blog(LOG_INFO, "ObsShowPreview called");
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
- if (info.Length() < 1 || !info[0].IsBuffer()) {
131
- Napi::TypeError::New(info.Env(), "Expected HWND as Buffer").ThrowAsJavaScriptException();
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
- // Union to safely read the bytes
146
- union {
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 ObsResizePreview(const Napi::CallbackInfo& info) {
159
- if (!obs)
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 (info.Length() < 2) {
163
- Napi::TypeError::New(info.Env(), "Expected width and height").ThrowAsJavaScriptException();
164
- return info.Env().Undefined();
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
- int width = info[0].As<Napi::Number>().Int32Value();
168
- int height = info[1].As<Napi::Number>().Int32Value();
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
- obs->resizePreview(width, height);
171
- return info.Env().Undefined();
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->movePreview(x, y);
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
 
@@ -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("h264_texture_amf", "simple_h264_stream", NULL, NULL);
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::showPreview(HWND hwnd) {
330
+ void ObsInterface::initPreview(HWND parent) {
334
331
  blog(LOG_INFO, "ObsInterface::showPreview");
335
332
 
336
- if (display) {
337
- blog(LOG_INFO, "Display already exists, returning early");
338
- return; // Return early if display already exists
339
- }
340
-
341
- // Create an embedded child window for OBS preview
342
- HWND previewWindow = CreateWindowExA(
343
- 0, // No extended styles
344
- "STATIC", // Simple static control class (ANSI string)
345
- "OBS Preview", // Window name (ANSI string)
346
- WS_CHILD | WS_VISIBLE | WS_BORDER, // Child + visible + border
347
- 20, 20, // Position within parent (x, y)
348
- 1920, 1080, // Size (width, height)
349
- hwnd, // Parent window (your Electron app)
350
- NULL, // No menu
351
- GetModuleHandle(NULL),
352
- NULL
353
- );
354
-
355
- if (!previewWindow) {
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
- // Store for cleanup
361
- previewHwnd = previewWindow;
355
+ if (!display) {
356
+ blog(LOG_INFO, "Create OBS display in child window");
362
357
 
363
- blog(LOG_INFO, "Create OBS display in child window");
364
- gs_init_data gs_data = {};
365
- gs_data.adapter = 0;
366
- gs_data.cx = 1920; // Match child window size
367
- gs_data.cy = 1080;
368
- gs_data.format = GS_BGRA;
369
- gs_data.zsformat = GS_ZS_NONE;
370
- gs_data.num_backbuffers = 1;
371
- gs_data.window.hwnd = previewWindow; // Use child window, not parent
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::resizePreview(int width, int height) {
385
- blog(LOG_INFO, "ObsInterface::resizePreview to size (%d x %d)", width, height);
380
+ void ObsInterface::showPreview(int x, int y, int width, int height) {
381
+ blog(LOG_INFO, "ObsInterface::showPreview");
386
382
 
387
- if (!previewHwnd) {
388
- blog(LOG_WARNING, "No preview window to resize");
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
- BOOL windowSuccess = SetWindowPos(
394
- previewHwnd, // Handle to the child window
395
- NULL, // No Z-order change
396
- 0, 0, // Keep current position (ignored due to SWP_NOMOVE)
397
- width, height, // New size (width, height)
398
- SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE // Don't change position, Z-order, or activation
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 (!windowSuccess) {
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
- blog(LOG_INFO, "Preview window resized successfully to (%d x %d)", width, height);
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 (display) {
445
- obs_display_remove_draw_callback(display, draw_callback, NULL);
446
- obs_display_destroy(display);
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
- // Destroy the child window to fully clean up the preview
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(
@@ -24,16 +24,13 @@ class ObsInterface {
24
24
  void stopRecording();
25
25
  std::string getLastRecording();
26
26
 
27
- void showPreview(HWND hwnd);
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 previewHwnd = nullptr; // window handle for scene preview
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 << "ObsEngine-" << std::put_time(std::localtime(&time_t), "%Y-%m-%d") << ".log";
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);