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 +45 -1
- package/dist/noobs.node +0 -0
- package/index.d.ts +20 -14
- package/index.js +2 -2
- package/package.json +1 -1
- package/src/main.cpp +30 -5
- package/src/obs_interface.cpp +145 -101
- package/src/obs_interface.h +14 -9
- package/src/utils.cpp +8 -0
- package/src/utils.h +2 -1
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
|
-
|
|
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 =
|
|
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;
|
|
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;
|
|
139
|
-
y: number;
|
|
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;
|
|
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
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() ==
|
|
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()
|
|
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
|
|
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));
|
package/src/obs_interface.cpp
CHANGED
|
@@ -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(
|
|
195
|
-
blog(LOG_INFO, "Create
|
|
194
|
+
void ObsInterface::create_output() {
|
|
195
|
+
blog(LOG_INFO, "Create outputs");
|
|
196
196
|
|
|
197
|
-
|
|
198
|
-
|
|
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 (
|
|
203
|
-
blog(
|
|
204
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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 *
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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(
|
|
235
|
-
obs_data_release(
|
|
231
|
+
obs_output_update(file_output, ffmpeg_settings);
|
|
232
|
+
obs_data_release(ffmpeg_settings);
|
|
236
233
|
|
|
237
234
|
// Add the signal handler callback.
|
|
238
|
-
|
|
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::
|
|
268
|
+
void ObsInterface::create_video_encoders() {
|
|
269
269
|
blog(LOG_INFO, "Create video encoder");
|
|
270
270
|
|
|
271
|
-
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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::
|
|
304
|
+
void ObsInterface::create_audio_encoders() {
|
|
301
305
|
blog(LOG_INFO, "Create audio encoder");
|
|
302
306
|
|
|
303
|
-
if (!output) {
|
|
304
|
-
|
|
305
|
-
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
318
|
+
file_audio_encoder = obs_audio_encoder_create("ffmpeg_aac", "aac_file", NULL, 0, NULL);
|
|
315
319
|
|
|
316
|
-
if (!
|
|
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(
|
|
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
|
-
|
|
328
|
-
|
|
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
|
-
|
|
331
|
-
obs_encoder_set_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::
|
|
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(
|
|
683
|
+
create_output();
|
|
663
684
|
create_scene();
|
|
664
685
|
|
|
665
|
-
|
|
666
|
-
|
|
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 (
|
|
688
|
-
if (obs_output_active(
|
|
708
|
+
if (buffer_output) {
|
|
709
|
+
if (obs_output_active(buffer_output)) {
|
|
689
710
|
blog(LOG_DEBUG, "Force stopping output");
|
|
690
|
-
obs_output_force_stop(
|
|
711
|
+
obs_output_force_stop(buffer_output);
|
|
691
712
|
}
|
|
692
713
|
|
|
693
714
|
blog(LOG_DEBUG, "Releasing output");
|
|
694
|
-
obs_output_release(
|
|
715
|
+
obs_output_release(buffer_output);
|
|
695
716
|
}
|
|
696
717
|
|
|
697
|
-
if (
|
|
698
|
-
|
|
699
|
-
|
|
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 (
|
|
703
|
-
|
|
704
|
-
|
|
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
|
-
|
|
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
|
|
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, "
|
|
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 (
|
|
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");
|
package/src/obs_interface.h
CHANGED
|
@@ -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
|
|
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 *
|
|
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 *
|
|
59
|
-
obs_encoder_t *
|
|
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
|
|
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(
|
|
88
|
+
void create_output();
|
|
84
89
|
|
|
85
|
-
void
|
|
86
|
-
void
|
|
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();
|