noobs 0.0.63 → 0.0.65

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 += ";";
3
+ process.env.Path += ';';
4
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.63",
3
+ "version": "0.0.65",
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
 
@@ -58,6 +56,32 @@ Napi::Value ObsSetRecordingDir(const Napi::CallbackInfo& info) {
58
56
  return info.Env().Undefined();
59
57
  }
60
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
+
82
+ return info.Env().Undefined();
83
+ }
84
+
61
85
  Napi::Value ObsStartBuffer(const Napi::CallbackInfo& info) {
62
86
  blog(LOG_INFO, "ObsStartBuffer called");
63
87
 
@@ -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,57 +191,57 @@ 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
239
  void ObsInterface::setRecordingDir(const std::string& recordingPath) {
242
240
  blog(LOG_INFO, "Set recording directory");
243
241
  // TODO make this work for file output also.
244
242
 
243
+ obs_output_t *output = buffering ? buffer_output : file_output;
244
+
245
245
  if (!output) {
246
246
  blog(LOG_ERROR, "No output to update recording directory");
247
247
  throw std::runtime_error("Output not initialized");
@@ -265,70 +265,82 @@ void ObsInterface::setRecordingDir(const std::string& recordingPath) {
265
265
  obs_data_release(settings);
266
266
  }
267
267
 
268
- void ObsInterface::configure_video_encoder() {
268
+ void ObsInterface::create_video_encoders() {
269
269
  blog(LOG_INFO, "Create video encoder");
270
270
 
271
- if (!output) {
272
- blog(LOG_ERROR, "No output on configure_video_encoder");
273
- throw std::runtime_error("Failed to create video encoder!");
274
- }
271
+ file_video_encoder = obs_video_encoder_create("obs_x264", "h264_stream_file", NULL, NULL);
275
272
 
276
- if (video_encoder) {
277
- blog(LOG_DEBUG, "Releasing video encoder");
278
- obs_encoder_release(video_encoder);
279
- video_encoder = nullptr;
280
- }
281
-
282
- video_encoder = obs_video_encoder_create("obs_x264", "simple_h264_stream", NULL, NULL);
283
273
 
284
- if (!video_encoder) {
274
+ if (!file_video_encoder) {
285
275
  blog(LOG_ERROR, "Failed to create video encoder!");
286
276
  throw std::runtime_error("Failed to create video encoder!");
287
277
  }
288
278
 
289
- blog(LOG_INFO, "Set video encoder settings");
279
+ buffer_video_encoder = obs_video_encoder_create("obs_x264", "h264_stream_buffer", 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");
290
287
  obs_data_t* venc_settings = obs_data_create();
291
288
  obs_data_set_string(venc_settings, "preset", "speed"); // Faster preset
292
289
  obs_data_set_string(venc_settings, "rate_control", "CRF");
293
290
  obs_data_set_int(venc_settings, "crf", 30);
294
291
  obs_data_set_string(venc_settings, "profile", "main");
295
292
  obs_data_set_int(venc_settings, "keyint_sec", 1); // Set keyframe interval to 1 second
296
- 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);
297
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());
298
302
  }
299
303
 
300
- void ObsInterface::configure_audio_encoder() {
304
+ void ObsInterface::create_audio_encoders() {
301
305
  blog(LOG_INFO, "Create audio encoder");
302
306
 
303
- if (!output) {
304
- blog(LOG_ERROR, "No output on configure_audio_encoder");
305
- throw std::runtime_error("Failed to create audio encoder!");
306
- }
307
+ // if (!output) {
308
+ // blog(LOG_ERROR, "No output on create_audio_encoders");
309
+ // throw std::runtime_error("Failed to create audio encoder!");
310
+ // }
307
311
 
308
- if (audio_encoder) {
309
- blog(LOG_DEBUG, "Releasing audio encoder");
310
- obs_encoder_release(audio_encoder);
311
- audio_encoder = nullptr;
312
- }
312
+ // if (audio_encoder) {
313
+ // blog(LOG_DEBUG, "Releasing audio encoder");
314
+ // obs_encoder_release(audio_encoder);
315
+ // audio_encoder = nullptr;
316
+ // }
313
317
 
314
- audio_encoder = obs_audio_encoder_create("ffmpeg_aac", "simple_aac", NULL, 0, NULL);
318
+ file_audio_encoder = obs_audio_encoder_create("ffmpeg_aac", "aac_file", NULL, 0, NULL);
315
319
 
316
- if (!audio_encoder) {
320
+ if (!file_audio_encoder) {
317
321
  blog(LOG_ERROR, "Failed to create audio encoder!");
318
322
  throw std::runtime_error("Failed to create audio encoder!");
319
323
  }
320
324
 
325
+ buffer_audio_encoder = obs_audio_encoder_create("ffmpeg_aac", "aac_buffer", 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
+
321
332
  blog(LOG_INFO, "Set audio encoder settings");
322
333
  obs_data_t *aenc_settings = obs_data_create();
323
334
  obs_data_set_int(aenc_settings, "bitrate", 128);
324
- 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);
325
337
  obs_data_release(aenc_settings);
326
338
 
327
- obs_output_set_video_encoder(output, video_encoder);
328
- 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());
329
341
 
330
- obs_encoder_set_video(video_encoder, obs_get_video());
331
- 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());
332
344
  }
333
345
 
334
346
  void ObsInterface::create_scene() {
@@ -472,7 +484,7 @@ void ObsInterface::output_signal_handler_saved(void *data, calldata_t *cd) {
472
484
  self->jscb.NonBlockingCall(sd, call_jscb);
473
485
  }
474
486
 
475
- void ObsInterface::create_signal_handlers(obs_output_t *output) {
487
+ void ObsInterface::connect_signal_handlers(obs_output_t *output) {
476
488
  signal_handler_t *sh = obs_output_get_signal_handler(output);
477
489
  signal_handler_connect(sh, "starting", output_signal_handler_starting, this);
478
490
  signal_handler_connect(sh, "start", output_signal_handler_start, this);
@@ -481,6 +493,15 @@ void ObsInterface::create_signal_handlers(obs_output_t *output) {
481
493
  signal_handler_connect(sh, "saved", output_signal_handler_saved, this);
482
494
  }
483
495
 
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
+ }
504
+
484
505
  bool draw_box(obs_scene_t *scene, obs_sceneitem_t *item, void *p) {
485
506
  // Get the item position and size
486
507
  vec2 pos; vec2 scale;
@@ -645,8 +666,7 @@ ObsInterface::ObsInterface(
645
666
  const std::string& logPath,
646
667
  const std::string& dataPath,
647
668
  const std::string& recordingPath,
648
- Napi::ThreadSafeFunction cb,
649
- bool buffering
669
+ Napi::ThreadSafeFunction cb
650
670
  ) {
651
671
  // Setup logs first so we have logs for the initialization.
652
672
  base_set_log_handler(log_handler, (void*)logPath.c_str());
@@ -657,13 +677,14 @@ ObsInterface::ObsInterface(
657
677
 
658
678
  // Setup callback function.
659
679
  jscb = cb;
680
+ recording_path = recordingPath;
660
681
 
661
682
  // Create the resources we rely on.
662
- create_output(recordingPath, buffering);
683
+ create_output();
663
684
  create_scene();
664
685
 
665
- configure_video_encoder();
666
- configure_audio_encoder();
686
+ create_video_encoders();
687
+ create_audio_encoders();
667
688
  }
668
689
 
669
690
  ObsInterface::~ObsInterface() {
@@ -684,39 +705,69 @@ ObsInterface::~ObsInterface() {
684
705
  obs_scene_release(scene);
685
706
  }
686
707
 
687
- if (output) {
688
- if (obs_output_active(output)) {
708
+ if (buffer_output) {
709
+ if (obs_output_active(buffer_output)) {
689
710
  blog(LOG_DEBUG, "Force stopping output");
690
- obs_output_force_stop(output);
711
+ obs_output_force_stop(buffer_output);
691
712
  }
692
713
 
693
714
  blog(LOG_DEBUG, "Releasing output");
694
- obs_output_release(output);
715
+ obs_output_release(buffer_output);
695
716
  }
696
717
 
697
- if (video_encoder) {
698
- blog(LOG_DEBUG, "Releasing video encoder");
699
- 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);
700
726
  }
701
727
 
702
- if (audio_encoder) {
703
- blog(LOG_DEBUG, "Releasing audio encoder");
704
- obs_encoder_release(audio_encoder);
705
- }
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
+ // }
706
737
 
707
738
  blog(LOG_DEBUG, "Now shutting down OBS");
708
739
  obs_shutdown();
709
740
  }
710
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
+
711
754
  void ObsInterface::startBuffering() {
712
755
  blog(LOG_INFO, "ObsInterface::startBuffering called");
713
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
+
714
764
  if (!output) {
765
+ blog(LOG_ERROR, "Output is not initialized!");
715
766
  throw std::runtime_error("Output is not initialized!");
716
767
  }
717
768
 
718
769
  bool is_active = obs_output_active(output);
719
-
770
+
720
771
  if (is_active) {
721
772
  blog(LOG_WARNING, "Output is already active");
722
773
  return;
@@ -725,6 +776,7 @@ void ObsInterface::startBuffering() {
725
776
  bool success = obs_output_start(output);
726
777
 
727
778
  if (!success) {
779
+ blog(LOG_ERROR, "Failed to start buffering!");
728
780
  throw std::runtime_error("Failed to start buffering!");
729
781
  }
730
782
 
@@ -733,10 +785,9 @@ void ObsInterface::startBuffering() {
733
785
 
734
786
  void ObsInterface::startRecording(int offset) {
735
787
  blog(LOG_INFO, "ObsInterface::startRecording enter");
788
+ obs_output_t* output = buffering ? buffer_output : file_output;
736
789
 
737
- const char* type = obs_output_get_id(output);
738
-
739
- if (strcmp(type, "replay_buffer") == 0) {
790
+ if (buffering) {
740
791
  bool is_active = obs_output_active(output);
741
792
 
742
793
  if (!is_active) {
@@ -755,7 +806,7 @@ void ObsInterface::startRecording(int offset) {
755
806
  if (!success) {
756
807
  throw std::runtime_error("Failed to call convert procedure handler");
757
808
  }
758
- } else if (strcmp(type, "ffmpeg_muxer") == 0) {
809
+ } else {
759
810
  blog(LOG_INFO, "Starting ffmpeg_muxer output");
760
811
 
761
812
  bool is_active = obs_output_active(output);
@@ -773,9 +824,6 @@ void ObsInterface::startRecording(int offset) {
773
824
  blog(LOG_ERROR, "Failed to start recording: %s", err ? err : "Unknown error");
774
825
  throw std::runtime_error("Failed to start recording");
775
826
  }
776
- } else {
777
- blog(LOG_ERROR, "Unknown output type: %s", type);
778
- throw std::runtime_error("Unknown output type!");
779
827
  }
780
828
 
781
829
  blog(LOG_INFO, "ObsInterface::startRecording exit");
@@ -783,10 +831,11 @@ void ObsInterface::startRecording(int offset) {
783
831
 
784
832
  void ObsInterface::stopRecording() {
785
833
  blog(LOG_INFO, "ObsInterface::stopRecording enter");
834
+ obs_output_t* output = buffering ? buffer_output : file_output;
786
835
  bool is_active = obs_output_active(output);
787
836
 
788
837
  if (!is_active) {
789
- blog(LOG_WARNING, "Buffer is not active");
838
+ blog(LOG_WARNING, "Output is not active");
790
839
  return;
791
840
  }
792
841
 
@@ -798,23 +847,18 @@ std::string ObsInterface::getLastRecording() {
798
847
  blog(LOG_INFO, "calling get last replay proc handler");
799
848
  calldata cd;
800
849
  calldata_init(&cd);
850
+
851
+ obs_output_t* output = buffering ? buffer_output : file_output;
801
852
  proc_handler_t *ph = obs_output_get_proc_handler(output);
802
853
 
803
854
  const char* type = obs_output_get_id(output);
804
855
 
805
- if (strcmp(type, "ffmpeg_muxer") == 0) {
856
+ if (!buffering) {
806
857
  blog(LOG_INFO, "Getting last recording path from ffmpeg_muxer");
807
858
  return recording_path;
808
859
  }
809
860
 
810
- bool success;
811
-
812
- if (strcmp(type, "replay_buffer") != 0) {
813
- blog(LOG_ERROR, "Unknown output type: %s", type);
814
- throw std::runtime_error("Unknown output type!");
815
- }
816
-
817
- success = proc_handler_call(ph, "get_last_replay", &cd);
861
+ bool success = proc_handler_call(ph, "get_last_replay", &cd);
818
862
 
819
863
  if (!success) {
820
864
  blog(LOG_ERROR, "Failed to call procedure handler");
@@ -14,12 +14,12 @@ 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.
@@ -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,8 +85,8 @@ 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
- void configure_video_encoder();
86
- void configure_audio_encoder();
90
+ void create_video_encoders();
91
+ void create_audio_encoders();
87
92
  };
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();