noobs 0.0.89 → 0.0.115
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/dist/data/obs-plugins/win-capture/get-graphics-offsets32.exe +0 -0
- package/dist/data/obs-plugins/win-capture/get-graphics-offsets64.exe +0 -0
- package/dist/data/obs-plugins/win-capture/graphics-hook32.dll +0 -0
- package/dist/data/obs-plugins/win-capture/graphics-hook64.dll +0 -0
- package/dist/data/obs-plugins/win-capture/inject-helper32.exe +0 -0
- package/dist/data/obs-plugins/win-capture/inject-helper64.exe +0 -0
- package/dist/noobs.node +0 -0
- package/index.d.ts +24 -15
- package/package.json +1 -1
- package/src/main.cpp +223 -10
- package/src/obs_interface.cpp +371 -153
- package/src/obs_interface.h +72 -26
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/dist/noobs.node
CHANGED
|
Binary file
|
package/index.d.ts
CHANGED
|
@@ -136,8 +136,10 @@ export type ObsProperty =
|
|
|
136
136
|
| ObsGenericProperty;
|
|
137
137
|
|
|
138
138
|
export type Signal = {
|
|
139
|
+
type: string; // Either "output" or "volmeter".
|
|
139
140
|
id: string; // Signal identifier, e.g. "stop"
|
|
140
141
|
code: number; // 0 for success, other values for errors
|
|
142
|
+
value?: number; // Currently only used for volmeters.
|
|
141
143
|
};
|
|
142
144
|
|
|
143
145
|
export type SceneItemPosition = {
|
|
@@ -162,43 +164,50 @@ interface Noobs {
|
|
|
162
164
|
|
|
163
165
|
Shutdown(): void;
|
|
164
166
|
|
|
165
|
-
// Recording functions
|
|
167
|
+
// Recording functions.
|
|
166
168
|
SetBuffering(buffering: boolean): void; // In buffering mode, the recording is stored in memory and can be converted to a file later.
|
|
167
169
|
StartBuffer(): void;
|
|
168
170
|
StartRecording(offset: number): void;
|
|
169
171
|
StopRecording(): void;
|
|
172
|
+
ForceStopRecording(): void;
|
|
170
173
|
GetLastRecording(): string;
|
|
171
174
|
SetRecordingDir(recordingPath: string): void;
|
|
175
|
+
ResetVideoContext(fps: number, width: number, height: number): void;
|
|
172
176
|
|
|
173
|
-
//
|
|
177
|
+
// Encoder functions.
|
|
178
|
+
ListVideoEncoders(): string[]; // Returns a list of available video encoders.
|
|
179
|
+
SetVideoEncoder(id: string, settings: ObsData): void; // Create the video encoder to use.
|
|
180
|
+
|
|
181
|
+
// Source management functions.
|
|
174
182
|
CreateSource(name: string, type: string): void;
|
|
175
183
|
DeleteSource(name: string): void;
|
|
176
184
|
GetSourceSettings(name: string): ObsData;
|
|
177
185
|
SetSourceSettings(name: string, settings: ObsData): void;
|
|
178
186
|
GetSourceProperties(name: string): ObsProperty[];
|
|
179
187
|
|
|
180
|
-
//
|
|
188
|
+
// Audio source management functions.
|
|
189
|
+
SetMuteAudioInputs(mute: boolean): void; // Mute or unmute all audio inputs.
|
|
190
|
+
SetInputVolume(volume: number): void; // Set the volume for all audio inputs sources.
|
|
191
|
+
SetOutputVolume(volume: number): void; // Set the volume for all audio outputs sources.
|
|
192
|
+
SetProcessVolume(volume: number): void; // Set the volume for all audio process sources.
|
|
193
|
+
|
|
194
|
+
SetVolmeterEnabled(enabled: boolean): void; // Enable or disable the volume meter.
|
|
195
|
+
|
|
196
|
+
// Scene management functions.
|
|
181
197
|
AddSourceToScene(sourceName: string): void;
|
|
182
198
|
RemoveSourceFromScene(sourceName: string): void;
|
|
183
199
|
GetSourcePos(name: string): SceneItemPosition & SourceDimensions;
|
|
184
200
|
SetSourcePos(name: string, pos: SceneItemPosition): void;
|
|
185
201
|
// TODO: Cropping?
|
|
186
202
|
|
|
187
|
-
// Preview functions
|
|
203
|
+
// Preview functions.
|
|
188
204
|
InitPreview(hwnd: Buffer): void;
|
|
189
|
-
|
|
205
|
+
ConfigurePreview(x: number, y: number, width: number, height: number): void;
|
|
206
|
+
ShowPreview(): void;
|
|
190
207
|
HidePreview(): void;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
* Draws a red border around source preview
|
|
194
|
-
* @param enabled
|
|
195
|
-
*/
|
|
208
|
+
DisablePreview(): void;
|
|
209
|
+
GetPreviewInfo(): { canvasWidth: number; canvasHeight: number; previewWidth: number; previewHeight: number };
|
|
196
210
|
SetDrawSourceOutline(enabled: boolean): void;
|
|
197
|
-
/**
|
|
198
|
-
* Gets the current state of the preview outline, a red border around the source preview
|
|
199
|
-
*
|
|
200
|
-
* Default: *false*
|
|
201
|
-
*/
|
|
202
211
|
GetDrawSourceOutlineEnabled(): boolean;
|
|
203
212
|
}
|
|
204
213
|
|
package/package.json
CHANGED
package/src/main.cpp
CHANGED
|
@@ -54,6 +54,75 @@ Napi::Value ObsSetRecordingDir(const Napi::CallbackInfo& info) {
|
|
|
54
54
|
return info.Env().Undefined();
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
Napi::Value ObsResetVideoContext(const Napi::CallbackInfo& info) {
|
|
58
|
+
if (!obs) {
|
|
59
|
+
blog(LOG_ERROR, "ObsResetVideoContext called but obs is not initialized");
|
|
60
|
+
throw std::runtime_error("Obs not initialized");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
bool valid = info.Length() == 3 && info[0].IsNumber() && info[1].IsNumber() && info[2].IsNumber();
|
|
64
|
+
|
|
65
|
+
if (!valid) {
|
|
66
|
+
Napi::TypeError::New(info.Env(), "Invalid arguments passed to ObsResetVideo").ThrowAsJavaScriptException();
|
|
67
|
+
return info.Env().Undefined();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
int fps = info[0].As<Napi::Number>().Int32Value();
|
|
71
|
+
int width = info[1].As<Napi::Number>().Int32Value();
|
|
72
|
+
int height = info[2].As<Napi::Number>().Int32Value();
|
|
73
|
+
|
|
74
|
+
obs->setVideoContext(fps, width, height);
|
|
75
|
+
return info.Env().Undefined();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
Napi::Value ObsListVideoEncoders(const Napi::CallbackInfo& info) {
|
|
79
|
+
if (!obs) {
|
|
80
|
+
blog(LOG_ERROR, "ObsListVideoEncoders called but obs is not initialized");
|
|
81
|
+
throw std::runtime_error("Obs not initialized");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
bool valid = info.Length() == 0;
|
|
85
|
+
|
|
86
|
+
if (!valid) {
|
|
87
|
+
Napi::TypeError::New(info.Env(), "Invalid arguments passed to ObsListVideoEncoders").ThrowAsJavaScriptException();
|
|
88
|
+
return info.Env().Undefined();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
auto encoders = obs->listAvailableVideoEncoders();
|
|
92
|
+
Napi::Array result = Napi::Array::New(info.Env(), encoders.size());
|
|
93
|
+
|
|
94
|
+
for (size_t i = 0; i < encoders.size(); ++i) {
|
|
95
|
+
result[i] = Napi::String::New(info.Env(), encoders[i]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
Napi::Value ObsSetVideoEncoder(const Napi::CallbackInfo& info) {
|
|
102
|
+
if (!obs) {
|
|
103
|
+
blog(LOG_ERROR, "ObsSetVideoEncoder called but obs is not initialized");
|
|
104
|
+
throw std::runtime_error("Obs not initialized");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
bool valid = info.Length() == 2 &&
|
|
108
|
+
info[0].IsString() && // Encoder ID
|
|
109
|
+
info[1].IsObject(); // Settings object
|
|
110
|
+
|
|
111
|
+
if (!valid) {
|
|
112
|
+
Napi::TypeError::New(info.Env(), "Invalid arguments passed to ObsSetVideoEncoder").ThrowAsJavaScriptException();
|
|
113
|
+
return info.Env().Undefined();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
std::string id = info[0].As<Napi::String>().Utf8Value();
|
|
117
|
+
Napi::Object obj = info[1].As<Napi::Object>();
|
|
118
|
+
|
|
119
|
+
obs_data_t* settings = napi_to_data(obj);
|
|
120
|
+
obs->setVideoEncoder(id, settings);
|
|
121
|
+
obs_data_release(settings);
|
|
122
|
+
|
|
123
|
+
return info.Env().Undefined();
|
|
124
|
+
}
|
|
125
|
+
|
|
57
126
|
Napi::Value ObsSetBuffering(const Napi::CallbackInfo& info) {
|
|
58
127
|
blog(LOG_INFO, "ObsSetBuffering called");
|
|
59
128
|
|
|
@@ -118,6 +187,16 @@ Napi::Value ObsStopRecording(const Napi::CallbackInfo& info) {
|
|
|
118
187
|
return info.Env().Undefined();
|
|
119
188
|
}
|
|
120
189
|
|
|
190
|
+
Napi::Value ObsForceStopRecording(const Napi::CallbackInfo& info) {
|
|
191
|
+
if (!obs) {
|
|
192
|
+
blog(LOG_ERROR, "ObsForceStopRecording called but obs is not initialized");
|
|
193
|
+
throw std::runtime_error("Obs not initialized");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
obs->forceStopRecording();
|
|
197
|
+
return info.Env().Undefined();
|
|
198
|
+
}
|
|
199
|
+
|
|
121
200
|
Napi::Value ObsGetLastRecording(const Napi::CallbackInfo& info) {
|
|
122
201
|
if (!obs) {
|
|
123
202
|
blog(LOG_ERROR, "ObsGetLastRecording called but obs is not initialized");
|
|
@@ -155,11 +234,11 @@ Napi::Value ObsInitPreview(const Napi::CallbackInfo& info) {
|
|
|
155
234
|
return info.Env().Undefined();
|
|
156
235
|
}
|
|
157
236
|
|
|
158
|
-
Napi::Value
|
|
159
|
-
blog(LOG_INFO, "
|
|
237
|
+
Napi::Value ObsConfigurePreview(const Napi::CallbackInfo& info) {
|
|
238
|
+
blog(LOG_INFO, "ObsConfigurePreview called");
|
|
160
239
|
|
|
161
240
|
if (!obs) {
|
|
162
|
-
blog(LOG_ERROR, "
|
|
241
|
+
blog(LOG_ERROR, "ObsConfigurePreview called but obs is not initialized");
|
|
163
242
|
throw std::runtime_error("Obs not initialized");
|
|
164
243
|
}
|
|
165
244
|
|
|
@@ -170,7 +249,7 @@ Napi::Value ObsShowPreview(const Napi::CallbackInfo& info) {
|
|
|
170
249
|
info[3].IsNumber(); // Height
|
|
171
250
|
|
|
172
251
|
if (!valid) {
|
|
173
|
-
Napi::TypeError::New(info.Env(), "Invalid arguments passed to
|
|
252
|
+
Napi::TypeError::New(info.Env(), "Invalid arguments passed to ObsConfigurePreview").ThrowAsJavaScriptException();
|
|
174
253
|
return info.Env().Undefined();
|
|
175
254
|
}
|
|
176
255
|
|
|
@@ -179,7 +258,19 @@ Napi::Value ObsShowPreview(const Napi::CallbackInfo& info) {
|
|
|
179
258
|
int width = info[2].As<Napi::Number>().Int32Value();
|
|
180
259
|
int height = info[3].As<Napi::Number>().Int32Value();
|
|
181
260
|
|
|
182
|
-
obs->
|
|
261
|
+
obs->configurePreview(x, y, width, height);
|
|
262
|
+
return info.Env().Undefined();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
Napi::Value ObsShowPreview(const Napi::CallbackInfo& info) {
|
|
266
|
+
blog(LOG_INFO, "ObsShowPreview called");
|
|
267
|
+
|
|
268
|
+
if (!obs) {
|
|
269
|
+
blog(LOG_ERROR, "ObsShowPreview called but obs is not initialized");
|
|
270
|
+
throw std::runtime_error("Obs not initialized");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
obs->showPreview();
|
|
183
274
|
return info.Env().Undefined();
|
|
184
275
|
}
|
|
185
276
|
|
|
@@ -193,14 +284,31 @@ Napi::Value ObsHidePreview(const Napi::CallbackInfo& info) {
|
|
|
193
284
|
return info.Env().Undefined();
|
|
194
285
|
}
|
|
195
286
|
|
|
196
|
-
Napi::Value
|
|
287
|
+
Napi::Value ObsDisablePreview(const Napi::CallbackInfo& info) {
|
|
288
|
+
if (!obs) {
|
|
289
|
+
blog(LOG_ERROR, "ObsDisablePreview called but obs is not initialized");
|
|
290
|
+
throw std::runtime_error("Obs not initialized");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
obs->disablePreview();
|
|
294
|
+
return info.Env().Undefined();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
Napi::Value ObsGetPreviewInfo(const Napi::CallbackInfo& info) {
|
|
197
298
|
if (!obs) {
|
|
198
|
-
blog(LOG_ERROR, "
|
|
299
|
+
blog(LOG_ERROR, "ObsGetPreviewInfo called but obs is not initialized");
|
|
199
300
|
throw std::runtime_error("Obs not initialized");
|
|
200
301
|
}
|
|
201
302
|
|
|
202
|
-
|
|
203
|
-
|
|
303
|
+
PreviewInfo previewInfo = obs->getPreviewInfo();
|
|
304
|
+
|
|
305
|
+
Napi::Object result = Napi::Object::New(info.Env());
|
|
306
|
+
result.Set("canvasWidth", Napi::Number::New(info.Env(), previewInfo.canvasWidth));
|
|
307
|
+
result.Set("canvasHeight", Napi::Number::New(info.Env(), previewInfo.canvasHeight));
|
|
308
|
+
result.Set("previewWidth", Napi::Number::New(info.Env(), previewInfo.displayWidth));
|
|
309
|
+
result.Set("previewHeight", Napi::Number::New(info.Env(), previewInfo.displayHeight));
|
|
310
|
+
|
|
311
|
+
return result;
|
|
204
312
|
}
|
|
205
313
|
|
|
206
314
|
Napi::Value ObsCreateSource(const Napi::CallbackInfo& info) {
|
|
@@ -309,6 +417,98 @@ Napi::Value ObsGetSourceProperties(const Napi::CallbackInfo& info) {
|
|
|
309
417
|
return result;
|
|
310
418
|
}
|
|
311
419
|
|
|
420
|
+
|
|
421
|
+
Napi::Value ObsSetMuteAudioInputs(const Napi::CallbackInfo& info) {
|
|
422
|
+
if (!obs) {
|
|
423
|
+
blog(LOG_ERROR, "ObsSetMuteAudioInputs called but obs is not initialized");
|
|
424
|
+
throw std::runtime_error("Obs not initialized");
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
bool valid = info.Length() == 1 && info[0].IsBoolean();
|
|
428
|
+
|
|
429
|
+
if (!valid) {
|
|
430
|
+
Napi::TypeError::New(info.Env(), "Invalid arguments passed to ObsSetMuteAudioInputs").ThrowAsJavaScriptException();
|
|
431
|
+
return info.Env().Undefined();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
bool mute = info[0].As<Napi::Boolean>().Value();
|
|
435
|
+
obs->setMuteAudioInputs(mute);
|
|
436
|
+
return info.Env().Undefined();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
Napi::Value ObsSetOutputVolume(const Napi::CallbackInfo& info) {
|
|
440
|
+
if (!obs) {
|
|
441
|
+
blog(LOG_ERROR, "ObsSetOutputVolume called but obs is not initialized");
|
|
442
|
+
throw std::runtime_error("Obs not initialized");
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
bool valid = info.Length() == 1 && info[0].IsNumber();
|
|
446
|
+
float volume = info[0].As<Napi::Number>().FloatValue();
|
|
447
|
+
|
|
448
|
+
if (!valid || (volume < 0.0f || volume > 1.0f)) {
|
|
449
|
+
Napi::TypeError::New(info.Env(), "Invalid arguments passed to ObsSetOutputVolume").ThrowAsJavaScriptException();
|
|
450
|
+
return info.Env().Undefined();
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
obs->setOutputVolume(volume);
|
|
454
|
+
return info.Env().Undefined();
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
Napi::Value ObsSetInputVolume(const Napi::CallbackInfo& info) {
|
|
458
|
+
if (!obs) {
|
|
459
|
+
blog(LOG_ERROR, "ObsSetInputVolume called but obs is not initialized");
|
|
460
|
+
throw std::runtime_error("Obs not initialized");
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
bool valid = info.Length() == 1 && info[0].IsNumber();
|
|
464
|
+
float volume = info[0].As<Napi::Number>().FloatValue();
|
|
465
|
+
|
|
466
|
+
if (!valid || (volume < 0.0f || volume > 1.0f)) {
|
|
467
|
+
Napi::TypeError::New(info.Env(), "Invalid arguments passed to ObsSetInputVolume").ThrowAsJavaScriptException();
|
|
468
|
+
return info.Env().Undefined();
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
obs->setInputVolume(volume);
|
|
472
|
+
return info.Env().Undefined();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
Napi::Value ObsSetProcessVolume(const Napi::CallbackInfo& info) {
|
|
476
|
+
if (!obs) {
|
|
477
|
+
blog(LOG_ERROR, "ObsSetProcessVolume called but obs is not initialized");
|
|
478
|
+
throw std::runtime_error("Obs not initialized");
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
bool valid = info.Length() == 1 && info[0].IsNumber();
|
|
482
|
+
float volume = info[0].As<Napi::Number>().FloatValue();
|
|
483
|
+
|
|
484
|
+
if (!valid || (volume < 0.0f || volume > 1.0f)) {
|
|
485
|
+
Napi::TypeError::New(info.Env(), "Invalid arguments passed to ObsSetProcessVolume").ThrowAsJavaScriptException();
|
|
486
|
+
return info.Env().Undefined();
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
obs->setProcessVolume(volume);
|
|
490
|
+
return info.Env().Undefined();
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
Napi::Value ObsSetVolmeterEnabled(const Napi::CallbackInfo& info) {
|
|
494
|
+
if (!obs) {
|
|
495
|
+
blog(LOG_ERROR, "ObsSetVolmeterEnabled called but obs is not initialized");
|
|
496
|
+
throw std::runtime_error("Obs not initialized");
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
bool valid = info.Length() == 1 && info[0].IsBoolean();
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
if (!valid) {
|
|
503
|
+
Napi::TypeError::New(info.Env(), "Invalid arguments passed to ObsSetVolmeterEnabled").ThrowAsJavaScriptException();
|
|
504
|
+
return info.Env().Undefined();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
bool enabled = info[0].As<Napi::Boolean>().Value();
|
|
508
|
+
obs->setVolmeterEnabled(enabled);
|
|
509
|
+
return info.Env().Undefined();
|
|
510
|
+
}
|
|
511
|
+
|
|
312
512
|
Napi::Value ObsCreateScene(const Napi::CallbackInfo& info) {
|
|
313
513
|
if (!obs) {
|
|
314
514
|
blog(LOG_ERROR, "ObsCreateScene called but obs is not initialized");
|
|
@@ -447,11 +647,15 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
447
647
|
exports.Set("Init", Napi::Function::New(env, ObsInit));
|
|
448
648
|
exports.Set("Shutdown", Napi::Function::New(env, ObsShutdown));
|
|
449
649
|
exports.Set("SetRecordingDir", Napi::Function::New(env, ObsSetRecordingDir));
|
|
650
|
+
exports.Set("ResetVideoContext", Napi::Function::New(env, ObsResetVideoContext));
|
|
651
|
+
exports.Set("ListVideoEncoders", Napi::Function::New(env, ObsListVideoEncoders));
|
|
652
|
+
exports.Set("SetVideoEncoder", Napi::Function::New(env, ObsSetVideoEncoder));
|
|
450
653
|
|
|
451
654
|
exports.Set("SetBuffering", Napi::Function::New(env, ObsSetBuffering));
|
|
452
655
|
exports.Set("StartBuffer", Napi::Function::New(env, ObsStartBuffer));
|
|
453
656
|
exports.Set("StartRecording", Napi::Function::New(env, ObsStartRecording));
|
|
454
657
|
exports.Set("StopRecording", Napi::Function::New(env, ObsStopRecording));
|
|
658
|
+
exports.Set("ForceStopRecording", Napi::Function::New(env, ObsForceStopRecording));
|
|
455
659
|
exports.Set("GetLastRecording", Napi::Function::New(env, ObsGetLastRecording));
|
|
456
660
|
|
|
457
661
|
exports.Set("CreateSource", Napi::Function::New(env, ObsCreateSource));
|
|
@@ -459,6 +663,13 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
459
663
|
exports.Set("GetSourceSettings", Napi::Function::New(env, ObsGetSourceSettings));
|
|
460
664
|
exports.Set("SetSourceSettings", Napi::Function::New(env, ObsSetSourceSettings));
|
|
461
665
|
exports.Set("GetSourceProperties", Napi::Function::New(env, ObsGetSourceProperties));
|
|
666
|
+
exports.Set("SetMuteAudioInputs", Napi::Function::New(env, ObsSetMuteAudioInputs));
|
|
667
|
+
|
|
668
|
+
exports.Set("SetOutputVolume", Napi::Function::New(env, ObsSetOutputVolume));
|
|
669
|
+
exports.Set("SetInputVolume", Napi::Function::New(env, ObsSetInputVolume));
|
|
670
|
+
exports.Set("SetProcessVolume", Napi::Function::New(env, ObsSetProcessVolume));
|
|
671
|
+
|
|
672
|
+
exports.Set("SetVolmeterEnabled", Napi::Function::New(env, ObsSetVolmeterEnabled));
|
|
462
673
|
|
|
463
674
|
exports.Set("AddSourceToScene", Napi::Function::New(env, ObsAddSourceToScene));
|
|
464
675
|
exports.Set("RemoveSourceFromScene", Napi::Function::New(env, ObsRemoveSourceFromScene));
|
|
@@ -466,9 +677,11 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
466
677
|
exports.Set("SetSourcePos", Napi::Function::New(env, ObsSetSourcePos));
|
|
467
678
|
|
|
468
679
|
exports.Set("InitPreview", Napi::Function::New(env, ObsInitPreview));
|
|
680
|
+
exports.Set("ConfigurePreview", Napi::Function::New(env, ObsConfigurePreview));
|
|
469
681
|
exports.Set("ShowPreview", Napi::Function::New(env, ObsShowPreview));
|
|
470
682
|
exports.Set("HidePreview", Napi::Function::New(env, ObsHidePreview));
|
|
471
|
-
exports.Set("
|
|
683
|
+
exports.Set("DisablePreview", Napi::Function::New(env, ObsDisablePreview));
|
|
684
|
+
exports.Set("GetPreviewInfo", Napi::Function::New(env, ObsGetPreviewInfo));
|
|
472
685
|
exports.Set("GetDrawSourceOutlineEnabled", Napi::Function::New(env, ObsGetDrawSourceOutlineEnabled));
|
|
473
686
|
exports.Set("SetDrawSourceOutline", Napi::Function::New(env, ObsSetDrawSourceOutline));
|
|
474
687
|
|
package/src/obs_interface.cpp
CHANGED
|
@@ -8,46 +8,34 @@
|
|
|
8
8
|
#include <graphics/vec4.h>
|
|
9
9
|
#include <util/platform.h>
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
while (obs_enum_encoder_types(idx++, &encoder_type)) {
|
|
18
|
-
bool video = obs_get_encoder_type(encoder_type) == OBS_ENCODER_VIDEO;
|
|
11
|
+
void call_jscb(Napi::Env env, Napi::Function cb, SignalData* sd) {
|
|
12
|
+
Napi::Object obj = Napi::Object::New(env);
|
|
13
|
+
obj.Set("type", Napi::String::New(env, sd->type));
|
|
14
|
+
obj.Set("id", Napi::String::New(env, sd->id));
|
|
15
|
+
obj.Set("code", Napi::Number::New(env, sd->code));
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
if (sd->value.has_value()) {
|
|
18
|
+
obj.Set("value", Napi::Number::New(env, sd->value.value()));
|
|
22
19
|
}
|
|
23
20
|
|
|
24
|
-
|
|
21
|
+
cb.Call({ obj });
|
|
22
|
+
delete sd;
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
void ObsInterface::list_encoders(obs_encoder_type type)
|
|
28
26
|
{
|
|
29
|
-
blog(LOG_INFO, "
|
|
30
|
-
blog(LOG_INFO, "List encoders of type: %d", type);
|
|
31
|
-
|
|
27
|
+
blog(LOG_INFO, "Encoders:");
|
|
32
28
|
size_t idx = 0;
|
|
33
29
|
const char *encoder_type;
|
|
34
30
|
|
|
35
31
|
while (obs_enum_encoder_types(idx++, &encoder_type)) {
|
|
36
|
-
if (obs_get_encoder_type(encoder_type) != type) {
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// if (obs_get_encoder_caps(encoder_type) & hide_flags) {
|
|
41
|
-
// continue;
|
|
42
|
-
// }
|
|
43
|
-
|
|
44
32
|
blog(LOG_INFO, "\t- %s (%s)", encoder_type, obs_encoder_get_display_name(encoder_type));
|
|
45
33
|
}
|
|
46
34
|
};
|
|
47
35
|
|
|
48
36
|
void ObsInterface::list_source_types()
|
|
49
37
|
{
|
|
50
|
-
blog(LOG_INFO, "
|
|
38
|
+
blog(LOG_INFO, "Sources:");
|
|
51
39
|
size_t idx = 0;
|
|
52
40
|
const char *src = nullptr;
|
|
53
41
|
|
|
@@ -56,20 +44,9 @@ void ObsInterface::list_source_types()
|
|
|
56
44
|
}
|
|
57
45
|
}
|
|
58
46
|
|
|
59
|
-
void ObsInterface::list_input_types()
|
|
60
|
-
{
|
|
61
|
-
blog(LOG_INFO, "List input types");
|
|
62
|
-
size_t idx = 0;
|
|
63
|
-
const char *src = nullptr;
|
|
64
|
-
|
|
65
|
-
while (obs_enum_input_types(idx++, &src)) {
|
|
66
|
-
blog(LOG_INFO, "\t- %s", src);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
47
|
void ObsInterface::list_output_types()
|
|
71
48
|
{
|
|
72
|
-
blog(LOG_INFO, "
|
|
49
|
+
blog(LOG_INFO, "Outputs:");
|
|
73
50
|
size_t idx = 0;
|
|
74
51
|
const char *src = nullptr;
|
|
75
52
|
|
|
@@ -98,16 +75,50 @@ void ObsInterface::load_module(const char* module, const char* data) {
|
|
|
98
75
|
}
|
|
99
76
|
}
|
|
100
77
|
|
|
101
|
-
void ObsInterface::
|
|
102
|
-
blog(LOG_INFO, "
|
|
78
|
+
void ObsInterface::setVideoContext(int fps, int width, int height) {
|
|
79
|
+
blog(LOG_INFO, "Reset video context");
|
|
103
80
|
|
|
81
|
+
blog(LOG_INFO, "FPS: %d", fps);
|
|
82
|
+
blog(LOG_INFO, "Width: %d", width);
|
|
83
|
+
blog(LOG_INFO, "Height: %d", height);
|
|
84
|
+
|
|
85
|
+
if (fps <= 10) {
|
|
86
|
+
blog(LOG_WARNING, "Invalid FPS provided for reset, using default 10");
|
|
87
|
+
fps = 60;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (width <= 32 || height <= 32) {
|
|
91
|
+
blog(LOG_WARNING, "Invalid width or height provided for reset, using default 1920x1080");
|
|
92
|
+
width = 1920;
|
|
93
|
+
height = 1080;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
int ret = reset_video(fps, width, height);
|
|
97
|
+
|
|
98
|
+
if (ret == OBS_VIDEO_CURRENTLY_ACTIVE) {
|
|
99
|
+
blog(LOG_WARNING, "Can't reset video as currently active");
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (ret != OBS_VIDEO_SUCCESS) {
|
|
104
|
+
blog(LOG_ERROR, "Failed to reset video context: %d", ret);
|
|
105
|
+
throw std::runtime_error("Failed to reset video context");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Recreate the encoders as they are tied to the video context.
|
|
109
|
+
create_video_encoders();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
int ObsInterface::reset_video(int fps, int width, int height) {
|
|
114
|
+
blog(LOG_INFO, "Reset video");
|
|
104
115
|
obs_video_info ovi = {};
|
|
105
116
|
|
|
106
|
-
ovi.base_width =
|
|
107
|
-
ovi.base_height =
|
|
108
|
-
ovi.output_width =
|
|
109
|
-
ovi.output_height =
|
|
110
|
-
ovi.fps_num =
|
|
117
|
+
ovi.base_width = width;
|
|
118
|
+
ovi.base_height = height;
|
|
119
|
+
ovi.output_width = width;
|
|
120
|
+
ovi.output_height = height;
|
|
121
|
+
ovi.fps_num = fps;
|
|
111
122
|
ovi.fps_den = 1;
|
|
112
123
|
|
|
113
124
|
ovi.output_format = VIDEO_FORMAT_NV12;
|
|
@@ -118,29 +129,14 @@ void ObsInterface::reset_video() {
|
|
|
118
129
|
ovi.gpu_conversion = true;
|
|
119
130
|
ovi.graphics_module = "libobs-d3d11.dll";
|
|
120
131
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
obs_enter_graphics();
|
|
124
|
-
int dt = gs_get_device_type();
|
|
125
|
-
blog(LOG_INFO, "Device type = %d", dt); // should be 1 for D3D11
|
|
126
|
-
obs_leave_graphics();
|
|
127
|
-
|
|
128
|
-
if (success != OBS_VIDEO_SUCCESS) {
|
|
129
|
-
blog(LOG_ERROR, "Failed to reset video!");
|
|
130
|
-
throw std::runtime_error("Failed to reset video!");
|
|
131
|
-
}
|
|
132
|
+
return obs_reset_video(&ovi);
|
|
132
133
|
}
|
|
133
134
|
|
|
134
|
-
|
|
135
|
+
bool ObsInterface::reset_audio() {
|
|
135
136
|
struct obs_audio_info oai = {0};
|
|
136
137
|
oai.samples_per_sec = 48000;
|
|
137
138
|
oai.speakers = SPEAKERS_STEREO;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if (!reset) {
|
|
141
|
-
blog(LOG_ERROR, "Failed to reset audio!");
|
|
142
|
-
throw std::runtime_error("Failed to reset audio!");
|
|
143
|
-
}
|
|
139
|
+
return obs_reset_audio(&oai);
|
|
144
140
|
}
|
|
145
141
|
|
|
146
142
|
void ObsInterface::init_obs(const std::string& distPath) {
|
|
@@ -166,10 +162,12 @@ void ObsInterface::init_obs(const std::string& distPath) {
|
|
|
166
162
|
|
|
167
163
|
std::string effectsPath = basePath + "data/effects/";
|
|
168
164
|
std::string pluginPath = basePath + "obs-plugins/";
|
|
165
|
+
std::string pluginDataPath = basePath + "data/obs-plugins/";
|
|
169
166
|
|
|
170
167
|
blog(LOG_INFO, "Base path: %s", basePath.c_str());
|
|
171
168
|
blog(LOG_INFO, "Effects path: %s", effectsPath.c_str());
|
|
172
169
|
blog(LOG_INFO, "Plugin path: %s", pluginPath.c_str());
|
|
170
|
+
blog(LOG_INFO, "Data path: %s", pluginDataPath.c_str());
|
|
173
171
|
|
|
174
172
|
// Add the effects path. We need this before resetting video and audio
|
|
175
173
|
// to ensure the effects are available. The function is deprecated in
|
|
@@ -177,8 +175,18 @@ void ObsInterface::init_obs(const std::string& distPath) {
|
|
|
177
175
|
obs_add_data_path(effectsPath.c_str());
|
|
178
176
|
|
|
179
177
|
// This must come before loading modules to initialize D3D11.
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
// Choose some sensible defaults that can be reconfigured.
|
|
179
|
+
int rc = reset_video(60, 1920, 1080);
|
|
180
|
+
|
|
181
|
+
if (rc != OBS_VIDEO_SUCCESS) {
|
|
182
|
+
blog(LOG_ERROR, "Failed to reset video!");
|
|
183
|
+
throw std::runtime_error("Failed to reset video!");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!reset_audio()) {
|
|
187
|
+
blog(LOG_ERROR, "Failed to reset audio!");
|
|
188
|
+
throw std::runtime_error("Failed to reset audio!");
|
|
189
|
+
}
|
|
182
190
|
|
|
183
191
|
std::vector<std::string> modules = {
|
|
184
192
|
"obs-x264",
|
|
@@ -190,7 +198,7 @@ void ObsInterface::init_obs(const std::string& distPath) {
|
|
|
190
198
|
|
|
191
199
|
for (const auto& module : modules) {
|
|
192
200
|
std::string modulePath = pluginPath + module + ".dll";
|
|
193
|
-
std::string moduleDataPath =
|
|
201
|
+
std::string moduleDataPath = pluginDataPath + module;
|
|
194
202
|
load_module(modulePath.c_str(), moduleDataPath.c_str());
|
|
195
203
|
}
|
|
196
204
|
|
|
@@ -198,7 +206,6 @@ void ObsInterface::init_obs(const std::string& distPath) {
|
|
|
198
206
|
|
|
199
207
|
list_encoders();
|
|
200
208
|
list_source_types();
|
|
201
|
-
list_input_types();
|
|
202
209
|
list_output_types();
|
|
203
210
|
|
|
204
211
|
blog(LOG_INFO, "Exit init_obs");
|
|
@@ -235,10 +242,10 @@ void ObsInterface::create_output() {
|
|
|
235
242
|
|
|
236
243
|
blog(LOG_INFO, "Set ffmpeg_muxer settings");
|
|
237
244
|
obs_data_t *ffmpeg_settings = obs_data_create();
|
|
238
|
-
// Need to specify the exact path for ffmpeg_muxer.
|
|
245
|
+
// Need to specify the exact path for ffmpeg_muxer. We will write this again at start recording.
|
|
239
246
|
std::string filename = recording_path + "\\" + get_current_date_time() + ".mp4";
|
|
240
247
|
obs_data_set_string(ffmpeg_settings, "path", filename.c_str());
|
|
241
|
-
|
|
248
|
+
unbuffered_output_filename = filename;
|
|
242
249
|
|
|
243
250
|
// Apply and release the settings.
|
|
244
251
|
obs_output_update(file_output, ffmpeg_settings);
|
|
@@ -279,38 +286,48 @@ void ObsInterface::setRecordingDir(const std::string& recordingPath) {
|
|
|
279
286
|
}
|
|
280
287
|
|
|
281
288
|
void ObsInterface::create_video_encoders() {
|
|
282
|
-
blog(LOG_INFO, "
|
|
289
|
+
blog(LOG_INFO, "Set video encoder: %s", video_encoder_id.c_str());
|
|
283
290
|
|
|
284
|
-
|
|
291
|
+
if (file_video_encoder) {
|
|
292
|
+
blog(LOG_DEBUG, "Releasing file video encoder");
|
|
293
|
+
obs_encoder_release(file_video_encoder);
|
|
294
|
+
file_video_encoder = nullptr;
|
|
295
|
+
}
|
|
285
296
|
|
|
297
|
+
file_video_encoder = obs_video_encoder_create(
|
|
298
|
+
video_encoder_id.c_str(),
|
|
299
|
+
"noobs_file_encoder",
|
|
300
|
+
video_encoder_settings,
|
|
301
|
+
NULL
|
|
302
|
+
);
|
|
286
303
|
|
|
287
304
|
if (!file_video_encoder) {
|
|
288
305
|
blog(LOG_ERROR, "Failed to create video encoder!");
|
|
289
306
|
throw std::runtime_error("Failed to create video encoder!");
|
|
290
307
|
}
|
|
291
308
|
|
|
292
|
-
|
|
309
|
+
if (buffer_video_encoder) {
|
|
310
|
+
blog(LOG_DEBUG, "Releasing buffer video encoder");
|
|
311
|
+
obs_encoder_release(buffer_video_encoder);
|
|
312
|
+
buffer_video_encoder = nullptr;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
buffer_video_encoder = obs_video_encoder_create(
|
|
316
|
+
video_encoder_id.c_str(),
|
|
317
|
+
"noobs_buffer_encoder",
|
|
318
|
+
video_encoder_settings,
|
|
319
|
+
NULL
|
|
320
|
+
);
|
|
293
321
|
|
|
294
322
|
if (!buffer_video_encoder) {
|
|
295
323
|
blog(LOG_ERROR, "Failed to create buffer video encoder!");
|
|
296
324
|
throw std::runtime_error("Failed to create buffer video encoder!");
|
|
297
325
|
}
|
|
298
326
|
|
|
299
|
-
blog(LOG_INFO, "Set file video encoder settings");
|
|
300
|
-
obs_data_t* venc_settings = obs_data_create();
|
|
301
|
-
// obs_data_set_string(venc_settings, "preset", "speed"); // Faster preset
|
|
302
|
-
obs_data_set_string(venc_settings, "rate_control", "CRF");
|
|
303
|
-
obs_data_set_int(venc_settings, "crf", 22);
|
|
304
|
-
obs_data_set_string(venc_settings, "profile", "main");
|
|
305
|
-
obs_data_set_int(venc_settings, "keyint_sec", 1); // Set keyframe interval to 1 second
|
|
306
|
-
|
|
307
|
-
obs_encoder_update(file_video_encoder, venc_settings);
|
|
308
|
-
obs_encoder_update(buffer_video_encoder, venc_settings);
|
|
309
|
-
obs_data_release(venc_settings);
|
|
310
|
-
|
|
311
327
|
obs_output_set_video_encoder(file_output, file_video_encoder);
|
|
312
|
-
obs_encoder_set_video(file_video_encoder, obs_get_video());
|
|
313
328
|
obs_output_set_video_encoder(buffer_output, buffer_video_encoder);
|
|
329
|
+
|
|
330
|
+
obs_encoder_set_video(file_video_encoder, obs_get_video());
|
|
314
331
|
obs_encoder_set_video(buffer_video_encoder, obs_get_video());
|
|
315
332
|
}
|
|
316
333
|
|
|
@@ -375,12 +392,34 @@ void ObsInterface::create_scene() {
|
|
|
375
392
|
obs_set_output_source(0, scene_source); // 0 = video track
|
|
376
393
|
}
|
|
377
394
|
|
|
395
|
+
void ObsInterface::volmeter_callback(void *data,
|
|
396
|
+
const float magnitude[MAX_AUDIO_CHANNELS],
|
|
397
|
+
const float peak[MAX_AUDIO_CHANNELS],
|
|
398
|
+
const float inputPeak[MAX_AUDIO_CHANNELS])
|
|
399
|
+
{
|
|
400
|
+
// blog(LOG_DEBUG, "Volmeter callback triggered: %f %f %f",
|
|
401
|
+
// obs_db_to_mul(magnitude[0]),
|
|
402
|
+
// obs_db_to_mul(peak[0]),
|
|
403
|
+
// obs_db_to_mul(inputPeak[0])
|
|
404
|
+
// );
|
|
405
|
+
|
|
406
|
+
SignalContext* ctx = static_cast<SignalContext*>(data);
|
|
407
|
+
ObsInterface* self = ctx->self;
|
|
408
|
+
|
|
409
|
+
if (!self->volmeter_enabled) {
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
SignalData* sd = new SignalData{ "volmeter", ctx->id.c_str(), 0, obs_db_to_mul(peak[0]) };
|
|
414
|
+
self->jscb.NonBlockingCall(sd, call_jscb);
|
|
415
|
+
}
|
|
416
|
+
|
|
378
417
|
void ObsInterface::createSource(std::string name, std::string type) {
|
|
379
418
|
blog(LOG_INFO, "Create source: %s of type %s", name.c_str(), type.c_str());
|
|
380
419
|
|
|
381
420
|
obs_source_t *source = obs_source_create(
|
|
382
|
-
type.c_str(),
|
|
383
|
-
name.c_str(),
|
|
421
|
+
type.c_str(), // Type of source, e.g. "wasapi_input_capture"
|
|
422
|
+
name.c_str(), // Name of the source, e.g. "My Audio Input"
|
|
384
423
|
NULL, // No settings.
|
|
385
424
|
NULL // No hotkey data.
|
|
386
425
|
);
|
|
@@ -390,6 +429,30 @@ void ObsInterface::createSource(std::string name, std::string type) {
|
|
|
390
429
|
throw std::runtime_error("Failed to create source!");
|
|
391
430
|
}
|
|
392
431
|
|
|
432
|
+
if (type == AUDIO_OUTPUT) {
|
|
433
|
+
blog(LOG_INFO, "Setting output volume for source: %s to %d", name.c_str(), output_volume);
|
|
434
|
+
obs_source_set_volume(source, output_volume);
|
|
435
|
+
} else if (type == AUDIO_INPUT) {
|
|
436
|
+
blog(LOG_INFO, "Setting input volume for source: %s to %d", name.c_str(), input_volume);
|
|
437
|
+
obs_source_set_volume(source, input_volume);
|
|
438
|
+
} else if (type == AUDIO_PROCESS) {
|
|
439
|
+
blog(LOG_INFO, "Setting process volume for source: %s to %d", name.c_str(), process_volume);
|
|
440
|
+
obs_source_set_volume(source, process_volume);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (type == AUDIO_OUTPUT || type == AUDIO_INPUT || type == AUDIO_PROCESS) {
|
|
444
|
+
blog(LOG_INFO, "Creating volmeter for source: %s", name.c_str());
|
|
445
|
+
|
|
446
|
+
obs_volmeter_t *volmeter = obs_volmeter_create(OBS_FADER_CUBIC);
|
|
447
|
+
obs_volmeter_attach_source(volmeter, source);
|
|
448
|
+
|
|
449
|
+
SignalContext* ctx = new SignalContext{ this, name }; // TODO don't leak this.
|
|
450
|
+
obs_volmeter_add_callback(volmeter, volmeter_callback, ctx);
|
|
451
|
+
|
|
452
|
+
// Store the volmeter in the volmeters map.
|
|
453
|
+
volmeters[name] = volmeter;
|
|
454
|
+
}
|
|
455
|
+
|
|
393
456
|
// Store the source in the sources map.
|
|
394
457
|
sources[name] = source;
|
|
395
458
|
}
|
|
@@ -397,6 +460,20 @@ void ObsInterface::createSource(std::string name, std::string type) {
|
|
|
397
460
|
void ObsInterface::deleteSource(std::string name) {
|
|
398
461
|
blog(LOG_INFO, "Delete source: %s", name.c_str());
|
|
399
462
|
|
|
463
|
+
// First release a volmeter if there is one present.
|
|
464
|
+
// Only audio sources have volmeters ofcourse.
|
|
465
|
+
auto vol_it = volmeters.find(name);
|
|
466
|
+
|
|
467
|
+
if (vol_it != volmeters.end()) {
|
|
468
|
+
obs_volmeter_t* volmeter = vol_it->second;
|
|
469
|
+
obs_volmeter_remove_callback(volmeter, volmeter_callback, this);
|
|
470
|
+
obs_volmeter_detach_source(volmeter);
|
|
471
|
+
obs_volmeter_destroy(volmeter);
|
|
472
|
+
blog(LOG_INFO, "Volmeter deleted for source: %s", name.c_str());
|
|
473
|
+
volmeters.erase(name);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Now deal with the source itself.
|
|
400
477
|
auto it = sources.find(name);
|
|
401
478
|
|
|
402
479
|
if (it == sources.end()) {
|
|
@@ -405,6 +482,7 @@ void ObsInterface::deleteSource(std::string name) {
|
|
|
405
482
|
}
|
|
406
483
|
|
|
407
484
|
obs_source_t* source = it->second;
|
|
485
|
+
obs_source_remove(source); // ???
|
|
408
486
|
obs_source_release(source);
|
|
409
487
|
sources.erase(name);
|
|
410
488
|
blog(LOG_INFO, "Source deleted: %s", name.c_str());
|
|
@@ -466,65 +544,30 @@ obs_properties_t* ObsInterface::getSourceProperties(std::string name) {
|
|
|
466
544
|
return props;
|
|
467
545
|
}
|
|
468
546
|
|
|
469
|
-
void
|
|
470
|
-
Napi::Object obj = Napi::Object::New(env);
|
|
471
|
-
obj.Set("id", Napi::String::New(env, sd->id));
|
|
472
|
-
obj.Set("code", Napi::Number::New(env, sd->code));
|
|
473
|
-
cb.Call({ obj });
|
|
474
|
-
delete sd;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
void ObsInterface::output_signal_handler_starting(void *data, calldata_t *cd) {
|
|
478
|
-
long long code = calldata_int(cd, "code");
|
|
479
|
-
ObsInterface* self = static_cast<ObsInterface*>(data);
|
|
480
|
-
SignalData* sd = new SignalData{ "starting", code };
|
|
481
|
-
self->jscb.NonBlockingCall(sd, call_jscb);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
void ObsInterface::output_signal_handler_start(void *data, calldata_t *cd) {
|
|
547
|
+
void ObsInterface::output_signal_handler(void *data, calldata_t *cd) {
|
|
485
548
|
long long code = calldata_int(cd, "code");
|
|
486
|
-
ObsInterface* self = static_cast<ObsInterface*>(data);
|
|
487
|
-
SignalData* sd = new SignalData{ "start", code };
|
|
488
|
-
self->jscb.NonBlockingCall(sd, call_jscb);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
void ObsInterface::output_signal_handler_stop(void *data, calldata_t *cd) {
|
|
492
|
-
long long code = calldata_int(cd, "code");
|
|
493
|
-
ObsInterface* self = static_cast<ObsInterface*>(data);
|
|
494
|
-
SignalData* sd = new SignalData{ "stop", code };
|
|
495
|
-
self->jscb.NonBlockingCall(sd, call_jscb);
|
|
496
|
-
}
|
|
497
549
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
ObsInterface* self = static_cast<ObsInterface*>(data);
|
|
501
|
-
SignalData* sd = new SignalData{ "stopping", code };
|
|
502
|
-
self->jscb.NonBlockingCall(sd, call_jscb);
|
|
503
|
-
}
|
|
550
|
+
SignalContext* ctx = static_cast<SignalContext*>(data);
|
|
551
|
+
ObsInterface* self = ctx->self;
|
|
504
552
|
|
|
505
|
-
|
|
506
|
-
long long code = calldata_int(cd, "code");
|
|
507
|
-
ObsInterface* self = static_cast<ObsInterface*>(data);
|
|
508
|
-
SignalData* sd = new SignalData{ "saved", code };
|
|
553
|
+
SignalData* sd = new SignalData{ "output", ctx->id.c_str(), code };
|
|
509
554
|
self->jscb.NonBlockingCall(sd, call_jscb);
|
|
510
555
|
}
|
|
511
556
|
|
|
512
557
|
void ObsInterface::connect_signal_handlers(obs_output_t *output) {
|
|
513
558
|
signal_handler_t *sh = obs_output_get_signal_handler(output);
|
|
514
|
-
signal_handler_connect(sh, "
|
|
515
|
-
signal_handler_connect(sh, "
|
|
516
|
-
signal_handler_connect(sh, "stopping",
|
|
517
|
-
signal_handler_connect(sh, "stop",
|
|
518
|
-
signal_handler_connect(sh, "saved", output_signal_handler_saved, this);
|
|
559
|
+
signal_handler_connect(sh, "start", output_signal_handler, start_ctx);
|
|
560
|
+
signal_handler_connect(sh, "starting", output_signal_handler, starting_ctx);
|
|
561
|
+
signal_handler_connect(sh, "stopping", output_signal_handler, stopping_ctx);
|
|
562
|
+
signal_handler_connect(sh, "stop", output_signal_handler, stop_ctx);
|
|
519
563
|
}
|
|
520
564
|
|
|
521
565
|
void ObsInterface::disconnect_signal_handlers(obs_output_t *output) {
|
|
522
566
|
signal_handler_t *sh = obs_output_get_signal_handler(output);
|
|
523
|
-
signal_handler_disconnect(sh, "starting",
|
|
524
|
-
signal_handler_disconnect(sh, "start",
|
|
525
|
-
signal_handler_disconnect(sh, "stopping",
|
|
526
|
-
signal_handler_disconnect(sh, "stop",
|
|
527
|
-
signal_handler_disconnect(sh, "saved", output_signal_handler_saved, this);
|
|
567
|
+
signal_handler_disconnect(sh, "starting", output_signal_handler, starting_ctx);
|
|
568
|
+
signal_handler_disconnect(sh, "start", output_signal_handler, start_ctx);
|
|
569
|
+
signal_handler_disconnect(sh, "stopping", output_signal_handler, stopping_ctx);
|
|
570
|
+
signal_handler_disconnect(sh, "stop", output_signal_handler, stop_ctx);
|
|
528
571
|
}
|
|
529
572
|
|
|
530
573
|
bool draw_source_outline(obs_scene_t *scene, obs_sceneitem_t *item, void *p) {
|
|
@@ -582,6 +625,12 @@ bool draw_source_outline(obs_scene_t *scene, obs_sceneitem_t *item, void *p) {
|
|
|
582
625
|
gs_draw_sprite(nullptr, 0, 4.0f, height);
|
|
583
626
|
gs_matrix_pop();
|
|
584
627
|
|
|
628
|
+
// Dragging point box (25x25 pixels in bottom-right corner)
|
|
629
|
+
gs_matrix_push();
|
|
630
|
+
gs_matrix_translate3f(pos.x + width - 25.0f, pos.y + height - 25.0f, 0.0f);
|
|
631
|
+
gs_draw_sprite(nullptr, 0, 25.0f, 25.0f);
|
|
632
|
+
gs_matrix_pop();
|
|
633
|
+
|
|
585
634
|
gs_matrix_pop();
|
|
586
635
|
|
|
587
636
|
gs_technique_end_pass(tech);
|
|
@@ -683,7 +732,7 @@ void ObsInterface::initPreview(HWND parent) {
|
|
|
683
732
|
obs_display_set_enabled(display, false);
|
|
684
733
|
}
|
|
685
734
|
|
|
686
|
-
void ObsInterface::
|
|
735
|
+
void ObsInterface::configurePreview(int x, int y, int width, int height) {
|
|
687
736
|
blog(LOG_INFO, "ObsInterface::showPreview");
|
|
688
737
|
|
|
689
738
|
if (!preview_hwnd || !display) {
|
|
@@ -691,7 +740,7 @@ void ObsInterface::showPreview(int x, int y, int width, int height) {
|
|
|
691
740
|
return;
|
|
692
741
|
}
|
|
693
742
|
|
|
694
|
-
blog(LOG_INFO, "
|
|
743
|
+
blog(LOG_INFO, "Moving preview child window to (%d, %d) with size (%d x %d)", x, y, width, height);
|
|
695
744
|
|
|
696
745
|
// Resize and move the existing child window.
|
|
697
746
|
bool success = SetWindowPos(
|
|
@@ -710,8 +759,18 @@ void ObsInterface::showPreview(int x, int y, int width, int height) {
|
|
|
710
759
|
uint32_t w, h;
|
|
711
760
|
obs_display_size(display, &w, &h); // Get the display size to match the video context.
|
|
712
761
|
blog(LOG_INFO, "Current Display size set to (%d x %d)", w, h);
|
|
713
|
-
|
|
714
762
|
obs_display_resize(display, width, height);
|
|
763
|
+
obs_display_set_enabled(display, true);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
void ObsInterface::showPreview() {
|
|
767
|
+
blog(LOG_INFO, "ObsInterface::showPreview");
|
|
768
|
+
|
|
769
|
+
if (!preview_hwnd || !display) {
|
|
770
|
+
blog(LOG_ERROR, "Preview window not initialized");
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
|
|
715
774
|
ShowWindow(preview_hwnd, SW_SHOW);
|
|
716
775
|
obs_display_set_enabled(display, true);
|
|
717
776
|
}
|
|
@@ -723,14 +782,24 @@ void ObsInterface::hidePreview() {
|
|
|
723
782
|
ShowWindow(preview_hwnd, SW_HIDE);
|
|
724
783
|
blog(LOG_INFO, "Preview child window hidden");
|
|
725
784
|
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
void ObsInterface::disablePreview() {
|
|
788
|
+
blog(LOG_INFO, "ObsInterface::disablePreview");
|
|
726
789
|
|
|
790
|
+
if (!display) {
|
|
791
|
+
blog(LOG_ERROR, "Preview window not initialized");
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
hidePreview();
|
|
727
796
|
obs_display_set_enabled(display, false);
|
|
728
797
|
}
|
|
729
798
|
|
|
730
|
-
|
|
799
|
+
PreviewInfo ObsInterface::getPreviewInfo() {
|
|
731
800
|
if (!display) {
|
|
732
|
-
blog(LOG_WARNING, "Display not initialized");
|
|
733
|
-
return
|
|
801
|
+
blog(LOG_WARNING, "Display not initialized when calling getPreviewInfo");
|
|
802
|
+
return { 1920, 1080, 1920, 1080 }; // Default values
|
|
734
803
|
}
|
|
735
804
|
|
|
736
805
|
obs_video_info ovi;
|
|
@@ -739,19 +808,14 @@ float ObsInterface::getPreviewScaleFactor() {
|
|
|
739
808
|
uint32_t width, height;
|
|
740
809
|
obs_display_size(display, &width, &height);
|
|
741
810
|
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
if (scaleX < scaleY) {
|
|
749
|
-
previewScale = scaleX;
|
|
750
|
-
} else {
|
|
751
|
-
previewScale = scaleY;
|
|
752
|
-
}
|
|
811
|
+
PreviewInfo info = {
|
|
812
|
+
ovi.base_width,
|
|
813
|
+
ovi.base_height,
|
|
814
|
+
width,
|
|
815
|
+
height,
|
|
816
|
+
};
|
|
753
817
|
|
|
754
|
-
return
|
|
818
|
+
return info;
|
|
755
819
|
}
|
|
756
820
|
|
|
757
821
|
void ObsInterface::setDrawSourceOutline(bool enabled) {
|
|
@@ -779,10 +843,17 @@ ObsInterface::ObsInterface(
|
|
|
779
843
|
jscb = cb;
|
|
780
844
|
recording_path = recordingPath;
|
|
781
845
|
|
|
846
|
+
starting_ctx = new SignalContext{ this, "starting" };
|
|
847
|
+
start_ctx = new SignalContext{ this, "start" };
|
|
848
|
+
stopping_ctx = new SignalContext{ this, "stopping" };
|
|
849
|
+
stop_ctx = new SignalContext{ this, "stop" };
|
|
850
|
+
|
|
782
851
|
// Create the resources we rely on.
|
|
783
852
|
create_output();
|
|
784
853
|
create_scene();
|
|
785
854
|
|
|
855
|
+
video_encoder_id = "obs_x264";
|
|
856
|
+
video_encoder_settings = obs_data_create();
|
|
786
857
|
create_video_encoders();
|
|
787
858
|
create_audio_encoders();
|
|
788
859
|
}
|
|
@@ -790,16 +861,26 @@ ObsInterface::ObsInterface(
|
|
|
790
861
|
ObsInterface::~ObsInterface() {
|
|
791
862
|
blog(LOG_DEBUG, "Destroying ObsInterface");
|
|
792
863
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
864
|
+
for (auto& kv : volmeters) {
|
|
865
|
+
obs_volmeter_t* volmeter = kv.second;
|
|
866
|
+
obs_volmeter_remove_callback(volmeter, volmeter_callback, this);
|
|
867
|
+
obs_volmeter_detach_source(volmeter);
|
|
868
|
+
obs_volmeter_destroy(volmeter);
|
|
869
|
+
blog(LOG_INFO, "Volmeter deleted for source: %s", kv.first.c_str());
|
|
870
|
+
volmeters.erase(kv.first);
|
|
796
871
|
}
|
|
797
872
|
|
|
873
|
+
delete starting_ctx;
|
|
874
|
+
delete start_ctx;
|
|
875
|
+
delete stopping_ctx;
|
|
876
|
+
delete stop_ctx;
|
|
877
|
+
|
|
798
878
|
for (auto& kv : sources) {
|
|
799
879
|
std::string name = kv.first;
|
|
800
880
|
obs_source_t* source = kv.second;
|
|
801
881
|
blog(LOG_DEBUG, "Releasing source: %s", name.c_str());
|
|
802
882
|
obs_source_release(source);
|
|
883
|
+
sources.erase(name);
|
|
803
884
|
}
|
|
804
885
|
|
|
805
886
|
if (scene) {
|
|
@@ -839,6 +920,11 @@ ObsInterface::~ObsInterface() {
|
|
|
839
920
|
|
|
840
921
|
blog(LOG_DEBUG, "Now shutting down OBS");
|
|
841
922
|
obs_shutdown();
|
|
923
|
+
|
|
924
|
+
if (jscb) {
|
|
925
|
+
blog(LOG_DEBUG, "Releasing JavaScript callback");
|
|
926
|
+
jscb.Release();
|
|
927
|
+
}
|
|
842
928
|
}
|
|
843
929
|
|
|
844
930
|
bool ObsInterface::setBuffering(bool value) {
|
|
@@ -909,6 +995,14 @@ void ObsInterface::startRecording(int offset) {
|
|
|
909
995
|
throw std::runtime_error("Failed to call convert procedure handler");
|
|
910
996
|
}
|
|
911
997
|
} else {
|
|
998
|
+
|
|
999
|
+
obs_data_t *ffmpeg_settings = obs_data_create();
|
|
1000
|
+
std::string filename = recording_path + "\\" + get_current_date_time() + ".mp4";
|
|
1001
|
+
obs_data_set_string(ffmpeg_settings, "path", filename.c_str());
|
|
1002
|
+
obs_output_update(output, ffmpeg_settings);
|
|
1003
|
+
obs_data_release(ffmpeg_settings);
|
|
1004
|
+
unbuffered_output_filename = filename;
|
|
1005
|
+
|
|
912
1006
|
blog(LOG_INFO, "Starting ffmpeg_muxer output");
|
|
913
1007
|
|
|
914
1008
|
bool is_active = obs_output_active(output);
|
|
@@ -945,6 +1039,20 @@ void ObsInterface::stopRecording() {
|
|
|
945
1039
|
blog(LOG_INFO, "ObsInterface::stopRecording exited");
|
|
946
1040
|
}
|
|
947
1041
|
|
|
1042
|
+
void ObsInterface::forceStopRecording() {
|
|
1043
|
+
blog(LOG_INFO, "ObsInterface::forceStopRecording enter");
|
|
1044
|
+
obs_output_t* output = buffering ? buffer_output : file_output;
|
|
1045
|
+
bool is_active = obs_output_active(output);
|
|
1046
|
+
|
|
1047
|
+
if (!is_active) {
|
|
1048
|
+
blog(LOG_WARNING, "Output is not active");
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
obs_output_force_stop(output);
|
|
1053
|
+
blog(LOG_INFO, "ObsInterface::forceStopRecording exited");
|
|
1054
|
+
}
|
|
1055
|
+
|
|
948
1056
|
std::string ObsInterface::getLastRecording() {
|
|
949
1057
|
blog(LOG_INFO, "calling get last replay proc handler");
|
|
950
1058
|
calldata cd;
|
|
@@ -957,7 +1065,7 @@ std::string ObsInterface::getLastRecording() {
|
|
|
957
1065
|
|
|
958
1066
|
if (!buffering) {
|
|
959
1067
|
blog(LOG_INFO, "Getting last recording path from ffmpeg_muxer");
|
|
960
|
-
return
|
|
1068
|
+
return unbuffered_output_filename;
|
|
961
1069
|
}
|
|
962
1070
|
|
|
963
1071
|
bool success = proc_handler_call(ph, "get_last_replay", &cd);
|
|
@@ -1057,4 +1165,114 @@ void ObsInterface::setSourcePos(std::string name, vec2* pos, vec2* scale) {
|
|
|
1057
1165
|
|
|
1058
1166
|
obs_sceneitem_set_pos(item, pos);
|
|
1059
1167
|
obs_sceneitem_set_scale(item, scale);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
std::vector<std::string> ObsInterface::listAvailableVideoEncoders()
|
|
1171
|
+
{
|
|
1172
|
+
std::vector<std::string> encoders;
|
|
1173
|
+
size_t idx = 0;
|
|
1174
|
+
const char *encoder_type;
|
|
1175
|
+
|
|
1176
|
+
while (obs_enum_encoder_types(idx++, &encoder_type)) {
|
|
1177
|
+
bool video = obs_get_encoder_type(encoder_type) == OBS_ENCODER_VIDEO;
|
|
1178
|
+
|
|
1179
|
+
if (video)
|
|
1180
|
+
encoders.emplace_back(encoder_type);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
return encoders;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
void ObsInterface::setVideoEncoder(std::string id, obs_data_t* settings) {
|
|
1187
|
+
// TODO don't allow this if output is active.
|
|
1188
|
+
video_encoder_id = id;
|
|
1189
|
+
video_encoder_settings = settings;
|
|
1190
|
+
create_video_encoders();
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
void ObsInterface::setMuteAudioInputs(bool mute) {
|
|
1194
|
+
// Loop over all sources, and set the mute state if they are of type "wasapi_input_capture".
|
|
1195
|
+
for (const auto& kv : sources) {
|
|
1196
|
+
const std::string& name = kv.first;
|
|
1197
|
+
obs_source_t* source = kv.second;
|
|
1198
|
+
|
|
1199
|
+
if (!source) {
|
|
1200
|
+
blog(LOG_WARNING, "Source %s not found", name.c_str());
|
|
1201
|
+
continue;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const char* type = obs_source_get_id(source);
|
|
1205
|
+
|
|
1206
|
+
if (strcmp(type, AUDIO_INPUT) == 0) {
|
|
1207
|
+
obs_source_set_muted(source, mute);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
void ObsInterface::setOutputVolume(float volume) {
|
|
1213
|
+
blog(LOG_INFO, "Setting output volume to %f", volume);
|
|
1214
|
+
output_volume = volume;
|
|
1215
|
+
|
|
1216
|
+
for (const auto& kv : sources) {
|
|
1217
|
+
const std::string& name = kv.first;
|
|
1218
|
+
obs_source_t* source = kv.second;
|
|
1219
|
+
|
|
1220
|
+
if (!source) {
|
|
1221
|
+
blog(LOG_WARNING, "Source %s not found", name.c_str());
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
const char* type = obs_source_get_id(source);
|
|
1226
|
+
|
|
1227
|
+
if (strcmp(type, AUDIO_OUTPUT) == 0) {
|
|
1228
|
+
obs_source_set_volume(source, output_volume);
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
void ObsInterface::setInputVolume(float volume) {
|
|
1234
|
+
blog(LOG_INFO, "Setting input volume to %f", volume);
|
|
1235
|
+
input_volume = volume;
|
|
1236
|
+
|
|
1237
|
+
for (const auto& kv : sources) {
|
|
1238
|
+
const std::string& name = kv.first;
|
|
1239
|
+
obs_source_t* source = kv.second;
|
|
1240
|
+
|
|
1241
|
+
if (!source) {
|
|
1242
|
+
blog(LOG_WARNING, "Source %s not found", name.c_str());
|
|
1243
|
+
continue;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
const char* type = obs_source_get_id(source);
|
|
1247
|
+
|
|
1248
|
+
if (strcmp(type, AUDIO_INPUT) == 0) {
|
|
1249
|
+
obs_source_set_volume(source, input_volume);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
void ObsInterface::setProcessVolume(float volume) {
|
|
1255
|
+
blog(LOG_INFO, "Setting process volume to %f", volume);
|
|
1256
|
+
process_volume = volume;
|
|
1257
|
+
|
|
1258
|
+
for (const auto& kv : sources) {
|
|
1259
|
+
const std::string& name = kv.first;
|
|
1260
|
+
obs_source_t* source = kv.second;
|
|
1261
|
+
|
|
1262
|
+
if (!source) {
|
|
1263
|
+
blog(LOG_WARNING, "Source %s not found", name.c_str());
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
const char* type = obs_source_get_id(source);
|
|
1268
|
+
|
|
1269
|
+
if (strcmp(type, AUDIO_PROCESS) == 0) {
|
|
1270
|
+
obs_source_set_volume(source, process_volume);
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
void ObsInterface::setVolmeterEnabled(bool enabled) {
|
|
1276
|
+
blog(LOG_INFO, "Setting volmeter enabled: %d", enabled);
|
|
1277
|
+
volmeter_enabled = enabled;
|
|
1060
1278
|
}
|
package/src/obs_interface.h
CHANGED
|
@@ -4,10 +4,30 @@
|
|
|
4
4
|
#include <napi.h>
|
|
5
5
|
#include <windows.h>
|
|
6
6
|
#include <map>
|
|
7
|
+
#include <string>
|
|
8
|
+
#include <optional>
|
|
9
|
+
|
|
10
|
+
#define AUDIO_INPUT "wasapi_input_capture"
|
|
11
|
+
#define AUDIO_OUTPUT "wasapi_output_capture"
|
|
12
|
+
#define AUDIO_PROCESS "wasapi_process_output_capture"
|
|
13
|
+
|
|
14
|
+
class ObsInterface;
|
|
7
15
|
|
|
8
16
|
struct SignalData {
|
|
17
|
+
std::string type;
|
|
9
18
|
std::string id;
|
|
10
19
|
long long code;
|
|
20
|
+
std::optional<float> value;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
struct SignalContext {
|
|
24
|
+
ObsInterface* self;
|
|
25
|
+
std::string id;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
struct PreviewInfo {
|
|
29
|
+
uint32_t canvasWidth, canvasHeight;
|
|
30
|
+
uint32_t displayWidth, displayHeight;
|
|
11
31
|
};
|
|
12
32
|
|
|
13
33
|
class ObsInterface {
|
|
@@ -22,17 +42,25 @@ class ObsInterface {
|
|
|
22
42
|
~ObsInterface();
|
|
23
43
|
|
|
24
44
|
bool setBuffering(bool buffering); // In buffering mode, the recording is stored in memory and can be converted to a file later.
|
|
25
|
-
void startBuffering();
|
|
26
|
-
void startRecording(int offset);
|
|
27
|
-
void stopRecording();
|
|
28
|
-
|
|
45
|
+
void startBuffering(); // Start buffering to memory.
|
|
46
|
+
void startRecording(int offset); // Convert the active buffered recording to a real one.
|
|
47
|
+
void stopRecording(); // Stop the recording.
|
|
48
|
+
void forceStopRecording(); // Force stop the recording, this will not save the current recording.
|
|
49
|
+
std::string getLastRecording(); // Get the last recorded file path.
|
|
29
50
|
void setRecordingDir(const std::string& recordingPath); // Output must not be active when calling this.
|
|
51
|
+
void setVideoContext(int fps, int width, int height); // Reset video settings.
|
|
30
52
|
|
|
31
|
-
void createSource(std::string name, std::string type);
|
|
32
|
-
void deleteSource(std::string name);
|
|
33
|
-
obs_data_t* getSourceSettings(std::string name);
|
|
53
|
+
void createSource(std::string name, std::string type); // Create a new source
|
|
54
|
+
void deleteSource(std::string name); // Release a source.
|
|
55
|
+
obs_data_t* getSourceSettings(std::string name); // Get the current settings.
|
|
34
56
|
void setSourceSettings(std::string name, obs_data_t* settings); // Set settings.
|
|
35
|
-
obs_properties_t* getSourceProperties(std::string name);
|
|
57
|
+
obs_properties_t* getSourceProperties(std::string name); // Get the settings schema.
|
|
58
|
+
void setMuteAudioInputs(bool mute); // Mute or unmute all audio inputs.
|
|
59
|
+
void setOutputVolume(float volume);
|
|
60
|
+
void setInputVolume(float volume);
|
|
61
|
+
void setProcessVolume(float volume);
|
|
62
|
+
|
|
63
|
+
void setVolmeterEnabled(bool enabled);
|
|
36
64
|
|
|
37
65
|
void addSourceToScene(std::string name); // Add source to scene.
|
|
38
66
|
void removeSourceFromScene(std::string name); // Remove source from scene.
|
|
@@ -40,49 +68,56 @@ class ObsInterface {
|
|
|
40
68
|
void setSourcePos(std::string name, vec2* pos, vec2* scale); // Size does not get set here because it's set by the source itself.
|
|
41
69
|
|
|
42
70
|
void initPreview(HWND parent); // Must call this before showPreview to setup resources.
|
|
43
|
-
void
|
|
44
|
-
void
|
|
45
|
-
|
|
71
|
+
void configurePreview(int x, int y, int width, int height); // Move and resize the preview display.
|
|
72
|
+
void showPreview(); // Show the preview display.
|
|
73
|
+
void hidePreview(); // Hide the preview display, but leave it running.
|
|
74
|
+
void disablePreview(); // Disable the preview display, to save resources.
|
|
75
|
+
PreviewInfo getPreviewInfo(); // Get the dimensions of the display, and the base canvas.
|
|
46
76
|
void setDrawSourceOutline(bool enabled); // Red box around source
|
|
47
77
|
bool getDrawSourceOutlineEnabled();
|
|
48
78
|
|
|
49
|
-
std::vector<std::string>
|
|
50
|
-
|
|
51
|
-
// TODO
|
|
52
|
-
// Configure video
|
|
53
|
-
// Configure audio
|
|
54
|
-
// List audio source
|
|
55
|
-
// Reconfigure audio sources
|
|
56
|
-
// Reconfigure video sources
|
|
79
|
+
std::vector<std::string> listAvailableVideoEncoders(); // Return a list of available video encoders.
|
|
80
|
+
void setVideoEncoder(std::string id, obs_data_t* settings); // Set the video encoder to use.
|
|
57
81
|
|
|
58
82
|
private:
|
|
59
83
|
obs_output_t *file_output = nullptr;
|
|
60
84
|
obs_output_t *buffer_output = nullptr;
|
|
85
|
+
|
|
61
86
|
obs_scene_t *scene = nullptr;
|
|
87
|
+
|
|
62
88
|
obs_encoder_t *file_video_encoder = nullptr;
|
|
63
89
|
obs_encoder_t *file_audio_encoder = nullptr;
|
|
90
|
+
|
|
64
91
|
obs_encoder_t *buffer_video_encoder = nullptr;
|
|
65
92
|
obs_encoder_t *buffer_audio_encoder = nullptr;
|
|
93
|
+
|
|
94
|
+
float output_volume = 1.0f; // Volume for the output.
|
|
95
|
+
float input_volume = 1.0f; // Volume for the input.
|
|
96
|
+
float process_volume = 1.0f; // Volume for the process.
|
|
97
|
+
|
|
66
98
|
obs_display_t *display = nullptr;
|
|
67
99
|
HWND preview_hwnd = nullptr; // window handle for scene preview
|
|
68
100
|
Napi::ThreadSafeFunction jscb; // javascript callback
|
|
69
101
|
std::string recording_path = "";
|
|
102
|
+
std::string unbuffered_output_filename = "";
|
|
103
|
+
|
|
70
104
|
bool buffering = false; // Whether we are buffering the recording in memory.
|
|
71
105
|
bool drawSourceOutline = false; // Draw red outline around source
|
|
72
106
|
std::map<std::string, obs_source_t*> sources; // Map of source names to obs_source_t pointers.
|
|
107
|
+
std::map<std::string, obs_volmeter_t*> volmeters; // Map of source names to obs_volmeter_t pointers.
|
|
73
108
|
|
|
74
109
|
void init_obs(const std::string& distPath);
|
|
75
|
-
|
|
76
|
-
|
|
110
|
+
int reset_video(int fps, int width, int height);
|
|
111
|
+
bool reset_audio();
|
|
77
112
|
void load_module(const char* module, const char* data); // Load a module, data is optional.
|
|
78
113
|
void connect_signal_handlers(obs_output_t *output);
|
|
79
114
|
void disconnect_signal_handlers(obs_output_t *output);
|
|
80
115
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
static void
|
|
116
|
+
SignalContext* starting_ctx;
|
|
117
|
+
SignalContext* start_ctx;
|
|
118
|
+
SignalContext* stopping_ctx;
|
|
119
|
+
SignalContext* stop_ctx;
|
|
120
|
+
static void output_signal_handler(void *data, calldata_t *cd);
|
|
86
121
|
|
|
87
122
|
void list_encoders(obs_encoder_type type = OBS_ENCODER_VIDEO);
|
|
88
123
|
void list_source_types();
|
|
@@ -92,6 +127,17 @@ class ObsInterface {
|
|
|
92
127
|
void create_scene();
|
|
93
128
|
void create_output();
|
|
94
129
|
|
|
130
|
+
std::string video_encoder_id; // The video encoder ID to use.
|
|
131
|
+
obs_data_t* video_encoder_settings; // Settings for the video encoder.
|
|
95
132
|
void create_video_encoders();
|
|
96
133
|
void create_audio_encoders();
|
|
134
|
+
|
|
135
|
+
bool volmeter_enabled = false; // Whether the volmeter callback is enabled.
|
|
136
|
+
|
|
137
|
+
static void volmeter_callback(
|
|
138
|
+
void *data,
|
|
139
|
+
const float magnitude[MAX_AUDIO_CHANNELS],
|
|
140
|
+
const float peak[MAX_AUDIO_CHANNELS],
|
|
141
|
+
const float inputPeak[MAX_AUDIO_CHANNELS]
|
|
142
|
+
);
|
|
97
143
|
};
|