noobs 0.0.60 → 0.0.64

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
@@ -19,12 +19,56 @@ npm install noobs
19
19
 
20
20
  ## Usage
21
21
 
22
+ ### Lifecycle
22
23
  ```javascript
23
24
  import noobs from 'noobs';
24
25
 
26
+ const pluginPath = ...;
27
+ const logPath = ...;
28
+ const dataPath = ...;
29
+ const recordingPath = ...;
30
+ const cb = (signal) => console.log(signal);
31
+
32
+ noobs.Init(pluginPath, logPath, dataPath, recordingPath, cb);
33
+ ...
34
+ noobs.Shutdown();
35
+ ```
36
+
37
+ ### Sources
38
+ ```javascript
39
+ noobs.CreateSource('Test Source', 'monitor_capture'); // Creates a source
40
+ noobs.AddSourceToScene('Test Source'); // Add the source to the scene
41
+ const s = noobs.GetSourceSettings('Test Source'); // View current settings
42
+ const p = noobs.GetSourceProperties('Test Source'); // View settings schema
43
+ noobs.SetSourceSettings('Test Source', { ...s, monitor: 1 });
44
+ noobs.RemoveSourceFromScene('Test Source'); // Remove a source from the scene
45
+ noobs.DeleteSource('Test Source') // Release a source
46
+ ```
47
+
48
+ ### Basic Recording Usage
49
+ ```javascript
50
+ noobs.StartRecording();
25
51
  ...
52
+ noobs.StopRecording();
53
+ ```
54
+
55
+ ### Buffer Recording
56
+ ```javascript
57
+ noobs.SetBuffering(true);
58
+
59
+ noobs.StartBuffering();
60
+ ...
61
+ noobs.StartRecording(5); // include last 5 seconds of the recording buffer
62
+ ...
63
+ noobs.StopRecording();
64
+ ```
26
65
 
27
- noobs.ObsInit(pluginPath, logPath, dataPath, recordingPath, cb);
66
+ ### Preview
67
+ ```javascript
68
+ const hwnd = this.mainWindow.getNativeWindowHandle();
69
+ noobs.InitPreview(hwnd);
70
+ noobs.ShowPreview(x, y, width, height); // Use this for moving/resizing
71
+ // noobs.HidePreview();
28
72
  ```
29
73
 
30
74
  See `test.js` for more.
package/dist/noobs.node CHANGED
Binary file
package/index.d.ts CHANGED
@@ -1,15 +1,21 @@
1
1
  // OBS Data Types
2
- export type ObsDataValue = string | number | boolean | ObsData | ObsData[] | null;
2
+ export type ObsDataValue =
3
+ | string
4
+ | number
5
+ | boolean
6
+ | ObsData
7
+ | ObsData[]
8
+ | null;
3
9
 
4
10
  export interface ObsData {
5
11
  [key: string]: ObsDataValue;
6
12
  }
7
13
 
8
14
  // OBS Property Types
9
- export type ObsPropertyType =
15
+ export type ObsPropertyType =
10
16
  | 'invalid'
11
17
  | 'bool'
12
- | 'int'
18
+ | 'int'
13
19
  | 'float'
14
20
  | 'text'
15
21
  | 'path'
@@ -114,7 +120,7 @@ export interface ObsGenericProperty extends ObsPropertyBase {
114
120
  type: 'invalid' | 'unknown';
115
121
  }
116
122
 
117
- export type ObsProperty =
123
+ export type ObsProperty =
118
124
  | ObsIntProperty
119
125
  | ObsFloatProperty
120
126
  | ObsTextProperty
@@ -130,35 +136,35 @@ export type ObsProperty =
130
136
  | ObsGenericProperty;
131
137
 
132
138
  export type Signal = {
133
- id: string; // Signal identifier, e.g. "stop"
139
+ id: string; // Signal identifier, e.g. "stop"
134
140
  code: number; // 0 for success, other values for errors
135
- }
141
+ };
136
142
 
137
143
  export type SceneItemPosition = {
138
- x: number; // X position in pixels
139
- y: number; // Y position in pixels
144
+ x: number; // X position in pixels
145
+ y: number; // Y position in pixels
140
146
  scaleX: number; // X scaling factor
141
147
  scaleY: number; // Y scaling factor
142
148
  };
143
149
 
144
150
  export type SourceDimensions = {
145
151
  height: number; // Height in pixels, before scaling
146
- width: number; // Width in pixels, before scaling
147
- }
152
+ width: number; // Width in pixels, before scaling
153
+ };
148
154
 
149
155
  interface Noobs {
150
156
  Init(
151
- pluginPath: string,
152
- logPath: string,
157
+ pluginPath: string,
158
+ logPath: string,
153
159
  dataPath: string,
154
160
  recordingPath: string,
155
161
  cb: (signal: Signal) => void,
156
- buffering: boolean,
157
162
  ): void;
158
163
 
159
164
  Shutdown(): void;
160
-
165
+
161
166
  // Recording functions
167
+ SetBuffering(buffering: boolean): void; // In buffering mode, the recording is stored in memory and can be converted to a file later.
162
168
  StartBuffer(): void;
163
169
  StartRecording(offset: number): void;
164
170
  StopRecording(): void;
package/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  const path = require('path');
2
2
 
3
- process.env.Path += ";";
4
- process.env.Path += path.resolve(__dirname, 'dist', 'bin');
3
+ process.env.Path += ';';
4
+ process.env.Path += path.resolve(__dirname, 'dist', 'bin').replace('app.asar', 'app.asar.unpacked');
5
5
 
6
6
  const packageName = 'noobs.node';
7
- const noobs = require(`./dist/${packageName}`)
7
+ const noobs = require(`./dist/${packageName}`);
8
8
  module.exports = noobs;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noobs",
3
- "version": "0.0.60",
3
+ "version": "0.0.64",
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",
package/src/main.cpp CHANGED
@@ -7,13 +7,12 @@
7
7
  ObsInterface* obs = nullptr;
8
8
 
9
9
  Napi::Value ObsInit(const Napi::CallbackInfo& info) {
10
- bool valid = info.Length() == 6 &&
10
+ bool valid = info.Length() == 5 &&
11
11
  info[0].IsString() && // Plugin path
12
12
  info[1].IsString() && // Log path
13
13
  info[2].IsString() && // Data path
14
14
  info[3].IsString() && // Recording path
15
- info[4].IsFunction() && // JavaScript callback
16
- info[5].IsBoolean(); // IsBuffer
15
+ info[4].IsFunction(); // JavaScript callback
17
16
 
18
17
  if (!valid) {
19
18
  Napi::Error::New(info.Env(), "Invalid arguments passed to ObsInit").ThrowAsJavaScriptException();
@@ -25,12 +24,11 @@ Napi::Value ObsInit(const Napi::CallbackInfo& info) {
25
24
  std::string dataPath = info[2].As<Napi::String>().Utf8Value();
26
25
  std::string recordingPath = info[3].As<Napi::String>().Utf8Value();
27
26
  Napi::Function fn = info[4].As<Napi::Function>();
28
- bool isBuffer = info[5].As<Napi::Boolean>().Value();
29
27
 
30
28
  Napi::ThreadSafeFunction jscb =
31
29
  Napi::ThreadSafeFunction::New(info.Env(), fn, "JavaScript callback", 0, 1);
32
30
 
33
- obs = new ObsInterface(pluginPath, logPath, dataPath, recordingPath, jscb, isBuffer);
31
+ obs = new ObsInterface(pluginPath, logPath, dataPath, recordingPath, jscb);
34
32
  return info.Env().Undefined();
35
33
  }
36
34
 
@@ -54,7 +52,33 @@ Napi::Value ObsSetRecordingDir(const Napi::CallbackInfo& info) {
54
52
  }
55
53
 
56
54
  std::string recordingPath = info[0].As<Napi::String>().Utf8Value();
57
- obs->updateRecordingDir(recordingPath);
55
+ obs->setRecordingDir(recordingPath);
56
+ return info.Env().Undefined();
57
+ }
58
+
59
+ Napi::Value ObsSetBuffering(const Napi::CallbackInfo& info) {
60
+ blog(LOG_INFO, "ObsSetBuffering called");
61
+
62
+ if (!obs) {
63
+ blog(LOG_ERROR, "ObsSetBuffering called but obs is not initialized");
64
+ throw std::runtime_error("Obs not initialized");
65
+ }
66
+
67
+ bool valid = info.Length() == 1 && info[0].IsBoolean();
68
+
69
+ if (!valid) {
70
+ Napi::TypeError::New(info.Env(), "Invalid arguments passed to ObsSetBuffering").ThrowAsJavaScriptException();
71
+ return info.Env().Undefined();
72
+ }
73
+
74
+ bool buffering = info[0].As<Napi::Boolean>().Value();
75
+ bool success = obs->setBuffering(buffering);
76
+
77
+ if (!success) {
78
+ Napi::Error::New(info.Env(), "Failed to set buffering mode, is recording active?").ThrowAsJavaScriptException();
79
+ return info.Env().Undefined();
80
+ }
81
+
58
82
  return info.Env().Undefined();
59
83
  }
60
84
 
@@ -399,6 +423,7 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
399
423
  exports.Set("Shutdown", Napi::Function::New(env, ObsShutdown));
400
424
  exports.Set("SetRecordingDir", Napi::Function::New(env, ObsSetRecordingDir));
401
425
 
426
+ exports.Set("SetBuffering", Napi::Function::New(env, ObsSetBuffering));
402
427
  exports.Set("StartBuffer", Napi::Function::New(env, ObsStartBuffer));
403
428
  exports.Set("StartRecording", Napi::Function::New(env, ObsStartRecording));
404
429
  exports.Set("StopRecording", Napi::Function::New(env, ObsStopRecording));
@@ -191,55 +191,56 @@ void ObsInterface::init_obs(const std::string& pluginPath, const std::string& da
191
191
  blog(LOG_INFO, "Exit init_obs");
192
192
  }
193
193
 
194
- void ObsInterface::create_output(const std::string& recordingPath, bool buffering) {
195
- blog(LOG_INFO, "Create output");
194
+ void ObsInterface::create_output() {
195
+ blog(LOG_INFO, "Create outputs");
196
196
 
197
- if (output) {
198
- blog(LOG_DEBUG, "Releasing output");
199
- obs_output_release(output);
200
- }
197
+ blog(LOG_INFO, "Creating replay buffer output");
198
+ buffer_output = obs_output_create("replay_buffer", "Buffer Output", NULL, NULL);
201
199
 
202
- if (buffering) {
203
- blog(LOG_INFO, "Creating replay buffer output");
204
- output = obs_output_create("replay_buffer", "recording_output", NULL, NULL);
205
- } else {
206
- blog(LOG_INFO, "Creating file output");
207
- output = obs_output_create("ffmpeg_muxer", "recording_output", NULL, NULL);
200
+ if (!buffer_output) {
201
+ blog(LOG_ERROR, "Failed to create buffer output!");
202
+ throw std::runtime_error("Failed to create buffer output!");
208
203
  }
209
204
 
210
- if (!output) {
211
- blog(LOG_ERROR, "Failed to create output!");
212
- throw std::runtime_error("Failed to create output!");
205
+ blog(LOG_INFO, "Creating file output");
206
+ file_output = obs_output_create("ffmpeg_muxer", "File Output", NULL, NULL);
207
+
208
+ if (!file_output) {
209
+ blog(LOG_ERROR, "Failed to create file output!");
210
+ throw std::runtime_error("Failed to create file output!");
213
211
  }
214
212
 
215
- obs_data_t *settings = obs_data_create();
213
+ obs_data_t *buffer_settings = obs_data_create();
214
+ blog(LOG_INFO, "Set replay_buffer settings");
215
+ obs_data_set_int(buffer_settings, "max_time_sec", 60);
216
+ obs_data_set_int(buffer_settings, "max_size_mb", 1024);
217
+ obs_data_set_string(buffer_settings, "directory", recording_path.c_str());
218
+ obs_data_set_string(buffer_settings, "format", "%CCYY-%MM-%DD %hh-%mm-%ss");
219
+ obs_data_set_string(buffer_settings, "extension", "mp4");
220
+ obs_output_update(buffer_output, buffer_settings);
221
+ obs_data_release(buffer_settings);
216
222
 
217
- if (buffering) {
218
- blog(LOG_INFO, "Set replay_buffer settings");
219
- obs_data_set_int(settings, "max_time_sec", 60);
220
- obs_data_set_int(settings, "max_size_mb", 1024);
221
- obs_data_set_string(settings, "directory", recordingPath.c_str());
222
- obs_data_set_string(settings, "format", "%CCYY-%MM-%DD %hh-%mm-%ss");
223
- obs_data_set_string(settings, "extension", "mp4");
224
- } else {
225
- blog(LOG_INFO, "Set ffmpeg_muxer settings");
226
- obs_data_set_string(settings, "extension", "mp4");
227
- // Apparently need to specify the exact path for ffmpeg_muxer.
228
- // TODO add something to auto generate this ?
229
- obs_data_set_string(settings, "path", (recordingPath + "/noobs.mp4").c_str());
230
- recording_path = recordingPath + "/noobs.mp4";
231
- }
223
+ blog(LOG_INFO, "Set ffmpeg_muxer settings");
224
+ obs_data_t *ffmpeg_settings = obs_data_create();
225
+ // Need to specify the exact path for ffmpeg_muxer.
226
+ std::string filename = recording_path + "\\" + get_current_date_time() + ".mp4";
227
+ obs_data_set_string(ffmpeg_settings, "path", filename.c_str());
228
+ recording_path = filename;
232
229
 
233
230
  // Apply and release the settings.
234
- obs_output_update(output, settings);
235
- obs_data_release(settings);
231
+ obs_output_update(file_output, ffmpeg_settings);
232
+ obs_data_release(ffmpeg_settings);
236
233
 
237
234
  // Add the signal handler callback.
238
- create_signal_handlers(output);
235
+ connect_signal_handlers(buffer_output);
236
+ connect_signal_handlers(file_output);
239
237
  }
240
238
 
241
- void ObsInterface::updateRecordingDir(const std::string& recordingPath) {
242
- blog(LOG_INFO, "Updating recording directory");
239
+ void ObsInterface::setRecordingDir(const std::string& recordingPath) {
240
+ blog(LOG_INFO, "Set recording directory");
241
+ // TODO make this work for file output also.
242
+
243
+ obs_output_t *output = buffering ? buffer_output : file_output;
243
244
 
244
245
  if (!output) {
245
246
  blog(LOG_ERROR, "No output to update recording directory");
@@ -267,67 +268,79 @@ void ObsInterface::updateRecordingDir(const std::string& recordingPath) {
267
268
  void ObsInterface::configure_video_encoder() {
268
269
  blog(LOG_INFO, "Create video encoder");
269
270
 
270
- if (!output) {
271
- blog(LOG_ERROR, "No output on configure_video_encoder");
272
- throw std::runtime_error("Failed to create video encoder!");
273
- }
271
+ file_video_encoder = obs_video_encoder_create("obs_x264", "simple_h264_stream", NULL, NULL);
274
272
 
275
- if (video_encoder) {
276
- blog(LOG_DEBUG, "Releasing video encoder");
277
- obs_encoder_release(video_encoder);
278
- video_encoder = nullptr;
279
- }
280
273
 
281
- video_encoder = obs_video_encoder_create("obs_x264", "simple_h264_stream", NULL, NULL);
282
-
283
- if (!video_encoder) {
274
+ if (!file_video_encoder) {
284
275
  blog(LOG_ERROR, "Failed to create video encoder!");
285
276
  throw std::runtime_error("Failed to create video encoder!");
286
277
  }
287
278
 
288
- blog(LOG_INFO, "Set video encoder settings");
279
+ buffer_video_encoder = obs_video_encoder_create("obs_x264", "simple_h264_stream", NULL, NULL);
280
+
281
+ if (!buffer_video_encoder) {
282
+ blog(LOG_ERROR, "Failed to create buffer video encoder!");
283
+ throw std::runtime_error("Failed to create buffer video encoder!");
284
+ }
285
+
286
+ blog(LOG_INFO, "Set file video encoder settings");
289
287
  obs_data_t* venc_settings = obs_data_create();
290
288
  obs_data_set_string(venc_settings, "preset", "speed"); // Faster preset
291
289
  obs_data_set_string(venc_settings, "rate_control", "CRF");
292
290
  obs_data_set_int(venc_settings, "crf", 30);
293
291
  obs_data_set_string(venc_settings, "profile", "main");
294
292
  obs_data_set_int(venc_settings, "keyint_sec", 1); // Set keyframe interval to 1 second
295
- obs_encoder_update(video_encoder, venc_settings);
293
+
294
+ obs_encoder_update(file_video_encoder, venc_settings);
295
+ obs_encoder_update(buffer_video_encoder, venc_settings);
296
296
  obs_data_release(venc_settings);
297
+
298
+ obs_output_set_video_encoder(file_output, file_video_encoder);
299
+ obs_encoder_set_video(file_video_encoder, obs_get_video());
300
+ obs_output_set_video_encoder(buffer_output, buffer_video_encoder);
301
+ obs_encoder_set_video(buffer_video_encoder, obs_get_video());
297
302
  }
298
303
 
299
304
  void ObsInterface::configure_audio_encoder() {
300
305
  blog(LOG_INFO, "Create audio encoder");
301
306
 
302
- if (!output) {
303
- blog(LOG_ERROR, "No output on configure_audio_encoder");
304
- throw std::runtime_error("Failed to create audio encoder!");
305
- }
307
+ // if (!output) {
308
+ // blog(LOG_ERROR, "No output on configure_audio_encoder");
309
+ // throw std::runtime_error("Failed to create audio encoder!");
310
+ // }
306
311
 
307
- if (audio_encoder) {
308
- blog(LOG_DEBUG, "Releasing audio encoder");
309
- obs_encoder_release(audio_encoder);
310
- audio_encoder = nullptr;
311
- }
312
+ // if (audio_encoder) {
313
+ // blog(LOG_DEBUG, "Releasing audio encoder");
314
+ // obs_encoder_release(audio_encoder);
315
+ // audio_encoder = nullptr;
316
+ // }
312
317
 
313
- audio_encoder = obs_audio_encoder_create("ffmpeg_aac", "simple_aac", NULL, 0, NULL);
318
+ file_audio_encoder = obs_audio_encoder_create("ffmpeg_aac", "simple_aac", NULL, 0, NULL);
314
319
 
315
- if (!audio_encoder) {
320
+ if (!file_audio_encoder) {
316
321
  blog(LOG_ERROR, "Failed to create audio encoder!");
317
322
  throw std::runtime_error("Failed to create audio encoder!");
318
323
  }
319
324
 
325
+ buffer_audio_encoder = obs_audio_encoder_create("ffmpeg_aac", "simple_aac", NULL, 0, NULL);
326
+
327
+ if (!buffer_audio_encoder) {
328
+ blog(LOG_ERROR, "Failed to create buffer audio encoder!");
329
+ throw std::runtime_error("Failed to create buffer audio encoder!");
330
+ }
331
+
320
332
  blog(LOG_INFO, "Set audio encoder settings");
321
333
  obs_data_t *aenc_settings = obs_data_create();
322
334
  obs_data_set_int(aenc_settings, "bitrate", 128);
323
- obs_encoder_update(audio_encoder, aenc_settings);
335
+ obs_encoder_update(file_audio_encoder, aenc_settings);
336
+ obs_encoder_update(buffer_audio_encoder, aenc_settings);
324
337
  obs_data_release(aenc_settings);
325
338
 
326
- obs_output_set_video_encoder(output, video_encoder);
327
- obs_output_set_audio_encoder(output, audio_encoder, 0);
339
+ obs_output_set_audio_encoder(file_output, file_audio_encoder, 0);
340
+ obs_encoder_set_audio(file_audio_encoder, obs_get_audio());
328
341
 
329
- obs_encoder_set_video(video_encoder, obs_get_video());
330
- obs_encoder_set_audio(audio_encoder, obs_get_audio());
342
+ obs_output_set_audio_encoder(buffer_output, buffer_audio_encoder, 0);
343
+ obs_encoder_set_audio(buffer_audio_encoder, obs_get_audio());
331
344
  }
332
345
 
333
346
  void ObsInterface::create_scene() {
@@ -471,7 +484,7 @@ void ObsInterface::output_signal_handler_saved(void *data, calldata_t *cd) {
471
484
  self->jscb.NonBlockingCall(sd, call_jscb);
472
485
  }
473
486
 
474
- void ObsInterface::create_signal_handlers(obs_output_t *output) {
487
+ void ObsInterface::connect_signal_handlers(obs_output_t *output) {
475
488
  signal_handler_t *sh = obs_output_get_signal_handler(output);
476
489
  signal_handler_connect(sh, "starting", output_signal_handler_starting, this);
477
490
  signal_handler_connect(sh, "start", output_signal_handler_start, this);
@@ -480,69 +493,70 @@ void ObsInterface::create_signal_handlers(obs_output_t *output) {
480
493
  signal_handler_connect(sh, "saved", output_signal_handler_saved, this);
481
494
  }
482
495
 
483
- bool draw_box(obs_scene_t *scene, obs_sceneitem_t *item, void *p) {
484
- // Get the item position and size
485
- vec2 pos; vec2 scale;
486
-
487
- obs_sceneitem_get_pos(item, &pos);
488
- obs_sceneitem_get_scale(item, &scale);
496
+ void ObsInterface::disconnect_signal_handlers(obs_output_t *output) {
497
+ signal_handler_t *sh = obs_output_get_signal_handler(output);
498
+ signal_handler_disconnect(sh, "starting", output_signal_handler_starting, this);
499
+ signal_handler_disconnect(sh, "start", output_signal_handler_start, this);
500
+ signal_handler_disconnect(sh, "stopping", output_signal_handler_stopping, this);
501
+ signal_handler_disconnect(sh, "stop", output_signal_handler_stop, this);
502
+ signal_handler_disconnect(sh, "saved", output_signal_handler_saved, this);
503
+ }
489
504
 
490
- obs_source_t *src = obs_sceneitem_get_source(item);
491
- uint32_t sizex = obs_source_get_width(src);
492
- uint32_t sizey = obs_source_get_height(src);
505
+ bool draw_box(obs_scene_t *scene, obs_sceneitem_t *item, void *p) {
506
+ // Get the item position and size
507
+ vec2 pos; vec2 scale;
508
+ obs_sceneitem_get_pos(item, &pos);
509
+ obs_sceneitem_get_scale(item, &scale);
493
510
 
494
- // Calculate actual size with scaling
495
- float width = sizex * scale.x;
496
- float height = sizey * scale.y;
511
+ // Calculate actual size with scaling
512
+ obs_source_t *src = obs_sceneitem_get_source(item);
513
+ float width = obs_source_get_width(src) * scale.x;
514
+ float height = obs_source_get_height(src) * scale.y;
497
515
 
498
- // Draw rectangle around the source using the position and size
499
- gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
500
- gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color");
501
- gs_technique_t *tech = gs_effect_get_technique(solid, "Solid");
516
+ // Draw rectangle around the source using the position and size
517
+ gs_effect_t *solid = obs_get_base_effect(OBS_EFFECT_SOLID);
518
+ gs_eparam_t *color = gs_effect_get_param_by_name(solid, "color");
519
+ gs_technique_t *tech = gs_effect_get_technique(solid, "Solid");
502
520
 
503
- vec4 col = {0.733f, 0.267f, 0.125f, 1.0f}; // #BB4420
504
- gs_effect_set_vec4(color, &col);
521
+ vec4 col = {0.733f, 0.267f, 0.125f, 1.0f}; // #BB4420
522
+ gs_effect_set_vec4(color, &col);
505
523
 
506
- gs_technique_begin(tech);
507
- gs_technique_begin_pass(tech, 0);
524
+ gs_technique_begin(tech);
525
+ gs_technique_begin_pass(tech, 0);
508
526
 
509
- gs_matrix_push();
510
- gs_matrix_identity();
527
+ gs_matrix_push();
528
+ gs_matrix_identity();
511
529
 
512
- // Top border
513
- gs_matrix_push();
514
- gs_matrix_translate3f(pos.x, pos.y, 0.0f);
515
- //gs_matrix_scale3f(width, 1.0f, 1.0f);
516
- gs_draw_sprite(nullptr, 0, width, 1.0f);
517
- gs_matrix_pop();
530
+ // Top border
531
+ gs_matrix_push();
532
+ gs_matrix_translate3f(pos.x, pos.y, 0.0f);
533
+ gs_draw_sprite(nullptr, 0, width, 2.0f);
534
+ gs_matrix_pop();
518
535
 
519
- // Bottom border
520
- gs_matrix_push();
521
- gs_matrix_translate3f(pos.x, pos.y + height - 1.0f, 0.0f);
522
- // gs_matrix_scale3f(width, 1.0f, 1.0f);
523
- gs_draw_sprite(nullptr, 0, width, 1.0f);
524
- gs_matrix_pop();
536
+ // Bottom border
537
+ gs_matrix_push();
538
+ gs_matrix_translate3f(pos.x, pos.y + height - 2.0f, 0.0f);
539
+ gs_draw_sprite(nullptr, 0, width, 2.0f);
540
+ gs_matrix_pop();
525
541
 
526
- // Left border
527
- gs_matrix_push();
528
- gs_matrix_translate3f(pos.x, pos.y, 0.0f);
529
- // gs_matrix_scale3f(1.0f, height, 1.0f);
530
- gs_draw_sprite(nullptr, 0, 1.0f, height);
531
- gs_matrix_pop();
542
+ // Left border
543
+ gs_matrix_push();
544
+ gs_matrix_translate3f(pos.x, pos.y, 0.0f);
545
+ gs_draw_sprite(nullptr, 0, 2.0f, height);
546
+ gs_matrix_pop();
532
547
 
533
- // Right border
534
- gs_matrix_push();
535
- gs_matrix_translate3f(pos.x + width - 1.0f, pos.y, 0.0f);
536
- // gs_matrix_scale3f(1.0f, height, 1.0f);
537
- gs_draw_sprite(nullptr, 0, 1.0f, height);
538
- gs_matrix_pop();
548
+ // Right border
549
+ gs_matrix_push();
550
+ gs_matrix_translate3f(pos.x + width - 2.0f, pos.y, 0.0f);
551
+ gs_draw_sprite(nullptr, 0, 2.0f, height);
552
+ gs_matrix_pop();
539
553
 
540
- gs_matrix_pop();
554
+ gs_matrix_pop();
541
555
 
542
- gs_technique_end_pass(tech);
543
- gs_technique_end(tech);
556
+ gs_technique_end_pass(tech);
557
+ gs_technique_end(tech);
544
558
 
545
- return true;
559
+ return true;
546
560
  }
547
561
 
548
562
  void draw_callback(void* data, uint32_t cx, uint32_t cy) {
@@ -652,8 +666,7 @@ ObsInterface::ObsInterface(
652
666
  const std::string& logPath,
653
667
  const std::string& dataPath,
654
668
  const std::string& recordingPath,
655
- Napi::ThreadSafeFunction cb,
656
- bool buffering
669
+ Napi::ThreadSafeFunction cb
657
670
  ) {
658
671
  // Setup logs first so we have logs for the initialization.
659
672
  base_set_log_handler(log_handler, (void*)logPath.c_str());
@@ -664,9 +677,10 @@ ObsInterface::ObsInterface(
664
677
 
665
678
  // Setup callback function.
666
679
  jscb = cb;
680
+ recording_path = recordingPath;
667
681
 
668
682
  // Create the resources we rely on.
669
- create_output(recordingPath, buffering);
683
+ create_output();
670
684
  create_scene();
671
685
 
672
686
  configure_video_encoder();
@@ -691,39 +705,69 @@ ObsInterface::~ObsInterface() {
691
705
  obs_scene_release(scene);
692
706
  }
693
707
 
694
- if (output) {
695
- if (obs_output_active(output)) {
708
+ if (buffer_output) {
709
+ if (obs_output_active(buffer_output)) {
696
710
  blog(LOG_DEBUG, "Force stopping output");
697
- obs_output_force_stop(output);
711
+ obs_output_force_stop(buffer_output);
698
712
  }
699
713
 
700
714
  blog(LOG_DEBUG, "Releasing output");
701
- obs_output_release(output);
715
+ obs_output_release(buffer_output);
702
716
  }
703
717
 
704
- if (video_encoder) {
705
- blog(LOG_DEBUG, "Releasing video encoder");
706
- obs_encoder_release(video_encoder);
718
+ if (file_output) {
719
+ if (obs_output_active(file_output)) {
720
+ blog(LOG_DEBUG, "Force stopping output");
721
+ obs_output_force_stop(file_output);
722
+ }
723
+
724
+ blog(LOG_DEBUG, "Releasing output");
725
+ obs_output_release(file_output);
707
726
  }
708
727
 
709
- if (audio_encoder) {
710
- blog(LOG_DEBUG, "Releasing audio encoder");
711
- obs_encoder_release(audio_encoder);
712
- }
728
+ // if (video_encoder) {
729
+ // blog(LOG_DEBUG, "Releasing video encoder");
730
+ // obs_encoder_release(video_encoder);
731
+ // }
732
+
733
+ // if (audio_encoder) {
734
+ // blog(LOG_DEBUG, "Releasing audio encoder");
735
+ // obs_encoder_release(audio_encoder);
736
+ // }
713
737
 
714
738
  blog(LOG_DEBUG, "Now shutting down OBS");
715
739
  obs_shutdown();
716
740
  }
717
741
 
742
+ bool ObsInterface::setBuffering(bool value) {
743
+ obs_output_t* output = buffering ? buffer_output : file_output;
744
+
745
+ if (obs_output_active(output)) {
746
+ blog(LOG_ERROR, "Cannot change buffering state while output is active");
747
+ return false;
748
+ }
749
+
750
+ buffering = value;
751
+ return buffering;
752
+ }
753
+
718
754
  void ObsInterface::startBuffering() {
719
755
  blog(LOG_INFO, "ObsInterface::startBuffering called");
720
756
 
757
+ if (!buffering) {
758
+ blog(LOG_ERROR, "Buffering is not enabled!");
759
+ throw std::runtime_error("Buffering is not enabled!");
760
+ }
761
+
762
+ obs_output_t* output = buffer_output;
763
+
721
764
  if (!output) {
765
+ blog(LOG_ERROR, "Output is not initialized!");
722
766
  throw std::runtime_error("Output is not initialized!");
723
767
  }
724
768
 
725
769
  bool is_active = obs_output_active(output);
726
-
770
+
727
771
  if (is_active) {
728
772
  blog(LOG_WARNING, "Output is already active");
729
773
  return;
@@ -732,6 +776,7 @@ void ObsInterface::startBuffering() {
732
776
  bool success = obs_output_start(output);
733
777
 
734
778
  if (!success) {
779
+ blog(LOG_ERROR, "Failed to start buffering!");
735
780
  throw std::runtime_error("Failed to start buffering!");
736
781
  }
737
782
 
@@ -740,10 +785,9 @@ void ObsInterface::startBuffering() {
740
785
 
741
786
  void ObsInterface::startRecording(int offset) {
742
787
  blog(LOG_INFO, "ObsInterface::startRecording enter");
788
+ obs_output_t* output = buffering ? buffer_output : file_output;
743
789
 
744
- const char* type = obs_output_get_id(output);
745
-
746
- if (strcmp(type, "replay_buffer") == 0) {
790
+ if (buffering) {
747
791
  bool is_active = obs_output_active(output);
748
792
 
749
793
  if (!is_active) {
@@ -762,7 +806,7 @@ void ObsInterface::startRecording(int offset) {
762
806
  if (!success) {
763
807
  throw std::runtime_error("Failed to call convert procedure handler");
764
808
  }
765
- } else if (strcmp(type, "ffmpeg_muxer") == 0) {
809
+ } else {
766
810
  blog(LOG_INFO, "Starting ffmpeg_muxer output");
767
811
 
768
812
  bool is_active = obs_output_active(output);
@@ -780,9 +824,6 @@ void ObsInterface::startRecording(int offset) {
780
824
  blog(LOG_ERROR, "Failed to start recording: %s", err ? err : "Unknown error");
781
825
  throw std::runtime_error("Failed to start recording");
782
826
  }
783
- } else {
784
- blog(LOG_ERROR, "Unknown output type: %s", type);
785
- throw std::runtime_error("Unknown output type!");
786
827
  }
787
828
 
788
829
  blog(LOG_INFO, "ObsInterface::startRecording exit");
@@ -790,10 +831,11 @@ void ObsInterface::startRecording(int offset) {
790
831
 
791
832
  void ObsInterface::stopRecording() {
792
833
  blog(LOG_INFO, "ObsInterface::stopRecording enter");
834
+ obs_output_t* output = buffering ? buffer_output : file_output;
793
835
  bool is_active = obs_output_active(output);
794
836
 
795
837
  if (!is_active) {
796
- blog(LOG_WARNING, "Buffer is not active");
838
+ blog(LOG_WARNING, "Output is not active");
797
839
  return;
798
840
  }
799
841
 
@@ -805,22 +847,18 @@ std::string ObsInterface::getLastRecording() {
805
847
  blog(LOG_INFO, "calling get last replay proc handler");
806
848
  calldata cd;
807
849
  calldata_init(&cd);
850
+
851
+ obs_output_t* output = buffering ? buffer_output : file_output;
808
852
  proc_handler_t *ph = obs_output_get_proc_handler(output);
809
853
 
810
854
  const char* type = obs_output_get_id(output);
811
855
 
812
-
813
-
814
- if (strcmp(type, "ffmpeg_muxer") == 0) {
856
+ if (!buffering) {
857
+ blog(LOG_INFO, "Getting last recording path from ffmpeg_muxer");
815
858
  return recording_path;
816
859
  }
817
860
 
818
- bool success;
819
-
820
- if (strcmp(type, "replay_buffer") != 0) {
821
- blog(LOG_ERROR, "Unknown output type: %s", type);
822
- throw std::runtime_error("Unknown output type!");
823
- }
861
+ bool success = proc_handler_call(ph, "get_last_replay", &cd);
824
862
 
825
863
  if (!success) {
826
864
  blog(LOG_ERROR, "Failed to call procedure handler");
@@ -14,17 +14,17 @@ class ObsInterface {
14
14
  const std::string& logPath, // Where to write logs to
15
15
  const std::string& dataPath, // Where to look for effects
16
16
  const std::string& recordingPath, // Where to save recordings
17
- Napi::ThreadSafeFunction cb, // JavaScript callback
18
- bool buffering // Whether to enable buffering the recording in memory
17
+ Napi::ThreadSafeFunction cb // JavaScript callback
19
18
  );
20
19
 
21
20
  ~ObsInterface();
22
21
 
22
+ bool setBuffering(bool buffering); // In buffering mode, the recording is stored in memory and can be converted to a file later.
23
23
  void startBuffering(); // Start buffering to memory.
24
24
  void startRecording(int offset); // Convert the active buffered recording to a real one.
25
25
  void stopRecording(); // Stop the recording.
26
26
  std::string getLastRecording(); // Get the last recorded file path.
27
- void updateRecordingDir(const std::string& recordingPath); // Output must not be active when calling this.
27
+ void setRecordingDir(const std::string& recordingPath); // Output must not be active when calling this.
28
28
 
29
29
  void createSource(std::string name, std::string type); // Create a new source
30
30
  void deleteSource(std::string name); // Release a source.
@@ -51,22 +51,27 @@ class ObsInterface {
51
51
  // Reconfigure video sources
52
52
 
53
53
  private:
54
- obs_output_t *output = nullptr;
54
+ obs_output_t *file_output = nullptr;
55
+ obs_output_t *buffer_output = nullptr;
55
56
  obs_scene_t *scene = nullptr;
56
57
  obs_source_t *video_source = nullptr;
57
58
  obs_source_t *audio_source = nullptr;
58
- obs_encoder_t *video_encoder = nullptr;
59
- obs_encoder_t *audio_encoder = nullptr;
59
+ obs_encoder_t *file_video_encoder = nullptr;
60
+ obs_encoder_t *file_audio_encoder = nullptr;
61
+ obs_encoder_t *buffer_video_encoder = nullptr;
62
+ obs_encoder_t *buffer_audio_encoder = nullptr;
60
63
  obs_display_t *display = nullptr;
61
64
  HWND preview_hwnd = nullptr; // window handle for scene preview
62
65
  Napi::ThreadSafeFunction jscb; // javascript callback
63
66
  std::string recording_path = "";
67
+ bool buffering = false; // Whether we are buffering the recording in memory.
64
68
 
65
69
  void init_obs(const std::string& pluginPath, const std::string& dataPath);
66
70
  void reset_video();
67
71
  void reset_audio();
68
72
  void load_module(const char* module);
69
- void create_signal_handlers(obs_output_t *output);
73
+ void connect_signal_handlers(obs_output_t *output);
74
+ void disconnect_signal_handlers(obs_output_t *output);
70
75
 
71
76
  static void output_signal_handler_starting(void *data, calldata_t *cd);
72
77
  static void output_signal_handler_start(void *data, calldata_t *cd);
@@ -80,7 +85,7 @@ class ObsInterface {
80
85
  void list_output_types();
81
86
 
82
87
  void create_scene();
83
- void create_output(const std::string& recordingPath, bool buffering);
88
+ void create_output();
84
89
 
85
90
  void configure_video_encoder();
86
91
  void configure_audio_encoder();
package/src/utils.cpp CHANGED
@@ -394,3 +394,11 @@ Napi::Object property_to_napi(Napi::Env env, obs_property_t* property) {
394
394
 
395
395
  return obj;
396
396
  }
397
+
398
+ std::string get_current_date_time() {
399
+ auto now = std::chrono::system_clock::now();
400
+ auto time_t = std::chrono::system_clock::to_time_t(now);
401
+ std::stringstream ss;
402
+ ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H-%M-%S");
403
+ return ss.str();
404
+ }
package/src/utils.h CHANGED
@@ -9,4 +9,5 @@ Napi::Object data_to_napi(Napi::Env env,obs_data_t* data);
9
9
  obs_data_t* napi_to_data(Napi::Object obj);
10
10
 
11
11
  Napi::Object property_to_napi(Napi::Env env, obs_property_t* property);
12
- Napi::Array properties_to_napi(Napi::Env env, obs_properties_t* properties);
12
+ Napi::Array properties_to_napi(Napi::Env env, obs_properties_t* properties);
13
+ std::string get_current_date_time();