Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions js/module.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,8 @@ export interface IVideo {
destroy(): void;
readonly skippedFrames: number;
readonly encodedFrames: number;
readonly canvasId: number;
refresh(): void;
}
export interface IVideoFactory {
create(): IVideo;
Expand Down
13 changes: 13 additions & 0 deletions js/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1465,6 +1465,19 @@ export interface IVideo {
* Number of total encoded frames
*/
readonly encodedFrames: number;

/**
* Server-side canvas id. Pass to APIs that reference video contexts by id.
*/
readonly canvasId: number;

/**
* Drops the cached video-info snapshot so the next `video` read hits the
* server. Call after any flow that mutates server-side state without going
* through the JS setter — e.g. after autoconfig's apply phase pushed a new
* resolution / FPS via NodeObs.StartSaveSettings.
*/
refresh(): void;
}

export interface IVideoFactory {
Expand Down
56 changes: 51 additions & 5 deletions obs-studio-client/source/nodeobs_autoconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

#include "nodeobs_autoconfig.hpp"
#include "shared.hpp"
#include "streaming.hpp"
#include <cstring>

bool autoConfig::isWorkerRunning = false;
bool autoConfig::worker_stop = true;
Expand Down Expand Up @@ -59,6 +61,10 @@ void autoConfig::worker()
data->event = response[1].value_str;
data->description = response[2].value_str;
data->percentage = response[3].value_union.fp64;
// Optional 5th payload field (added for the POC UI). Older
// servers won't include it — guard the read.
if (response.size() >= 5)
data->payload = response[4].value_str;
ac_queue_task_workers.push_back(new std::thread(&autoConfig::queueTask, data));
}
}
Expand Down Expand Up @@ -102,20 +108,39 @@ void autoConfig::stop_worker()

Napi::Value autoConfig::InitializeAutoConfig(const Napi::CallbackInfo &info)
{
Napi::Function async_callback = info[0].As<Napi::Function>();
Napi::Object serverInfo = info[1].ToObject();
std::string continent = serverInfo.Get("continent").ToString().Utf8Value();
std::string service = serverInfo.Get("service_name").ToString().Utf8Value();
if (info.Length() < 2 || !info[0].IsArray() || !info[1].IsFunction()) {
Napi::TypeError::New(info.Env(), "InitializeAutoConfig expects (streamings: IStreaming[], callback)")
.ThrowAsJavaScriptException();
return info.Env().Undefined();
}

Napi::Array array = info[0].As<Napi::Array>();
std::vector<uint64_t> uids(array.Length());
for (uint32_t i = 0; i < array.Length(); i++) {
if (!osn::TryUnwrapStreamingUid(array.Get(i), uids[i])) {
Napi::TypeError::New(info.Env(), "InitializeAutoConfig: streamings[i] is not an IStreaming instance")
.ThrowAsJavaScriptException();
return info.Env().Undefined();
}
}
std::vector<char> uidsBin(uids.size() * sizeof(uint64_t));
if (!uids.empty())
memcpy(uidsBin.data(), uids.data(), uidsBin.size());

Napi::Function async_callback = info[1].As<Napi::Function>();

auto conn = GetConnection(info);
if (!conn)
return info.Env().Undefined();

std::vector<ipc::value> response = conn->call_synchronous_helper("AutoConfig", "InitializeAutoConfig", {continent, service});
std::vector<ipc::value> response = conn->call_synchronous_helper("AutoConfig", "InitializeAutoConfig", {ipc::value(uidsBin)});

if (!ValidateResponse(info, response))
return info.Env().Undefined();

if (isWorkerRunning)
stop_worker();

js_thread = Napi::ThreadSafeFunction::New(info.Env(), async_callback, "AutoConfig", 0, 1, [](Napi::Env) {});

start_worker();
Expand Down Expand Up @@ -177,6 +202,9 @@ void autoConfig::queueTask(AutoConfigInfo *data)
if (event_data->event.compare("error") != 0) {
result.Set(Napi::String::New(env, "percentage"), Napi::Number::New(env, event_data->percentage));
}
if (!event_data->payload.empty()) {
result.Set(Napi::String::New(env, "payload"), Napi::String::New(env, event_data->payload));
}
result.Set(Napi::String::New(env, "continent"), Napi::String::New(env, ""));

jsCallback.Call({result});
Expand Down Expand Up @@ -264,6 +292,23 @@ Napi::Value autoConfig::StartSaveSettings(const Napi::CallbackInfo &info)
return info.Env().Undefined();
}

Napi::Value autoConfig::GetAutoConfigSummary(const Napi::CallbackInfo &info)
{
auto conn = GetConnection(info);
if (!conn)
return info.Env().Undefined();

std::vector<ipc::value> response = conn->call_synchronous_helper("AutoConfig", "GetAutoConfigSummary", {});

if (!ValidateResponse(info, response))
return info.Env().Undefined();

if (response.size() < 2)
return info.Env().Undefined();

return Napi::String::New(info.Env(), response[1].value_str);
}

Napi::Value autoConfig::TerminateAutoConfig(const Napi::CallbackInfo &info)
{
auto conn = GetConnection(info);
Expand Down Expand Up @@ -292,4 +337,5 @@ void autoConfig::Init(Napi::Env env, Napi::Object exports)
exports.Set(Napi::String::New(env, "StartSaveStreamSettings"), Napi::Function::New(env, autoConfig::StartSaveStreamSettings));
exports.Set(Napi::String::New(env, "StartSaveSettings"), Napi::Function::New(env, autoConfig::StartSaveSettings));
exports.Set(Napi::String::New(env, "TerminateAutoConfig"), Napi::Function::New(env, autoConfig::TerminateAutoConfig));
exports.Set(Napi::String::New(env, "GetAutoConfigSummary"), Napi::Function::New(env, autoConfig::GetAutoConfigSummary));
}
5 changes: 5 additions & 0 deletions obs-studio-client/source/nodeobs_autoconfig.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ struct AutoConfigInfo {
std::string event;
std::string description;
double percentage = 0;
// Optional JSON payload for new event types (bandwidth_result,
// selection_decision, video_decision, encoder_detection). Empty for legacy
// events. Surfaced to JS as a "payload" property when non-empty.
std::string payload;
};

extern const char *ac_sem_name;
Expand Down Expand Up @@ -61,4 +65,5 @@ Napi::Value StartSetDefaultSettings(const Napi::CallbackInfo &info);
Napi::Value StartSaveStreamSettings(const Napi::CallbackInfo &info);
Napi::Value StartSaveSettings(const Napi::CallbackInfo &info);
Napi::Value TerminateAutoConfig(const Napi::CallbackInfo &info);
Napi::Value GetAutoConfigSummary(const Napi::CallbackInfo &info);
}
41 changes: 41 additions & 0 deletions obs-studio-client/source/streaming.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
#include "reconnect.hpp"
#include "network.hpp"
#include "video.hpp"
#include "simple-streaming.hpp"
#include "advanced-streaming.hpp"
#include "enhanced-broadcasting-simple-streaming.hpp"
#include "enhanced-broadcasting-advanced-streaming.hpp"

void osn::Streaming::ReleaseObjects()
{
Expand Down Expand Up @@ -442,3 +446,40 @@ Napi::Value osn::Streaming::GetDataOutput(const Napi::CallbackInfo &info)

return Napi::Number::New(info.Env(), response[1].value_union.fp64);
}

bool osn::TryUnwrapStreamingUid(const Napi::Value &value, uint64_t &out_uid)
{
if (!value.IsObject())
return false;
Napi::Object obj = value.As<Napi::Object>();

if (obj.InstanceOf(osn::SimpleStreaming::constructor.Value())) {
auto *s = Napi::ObjectWrap<osn::SimpleStreaming>::Unwrap(obj);
if (s) {
out_uid = s->uid;
return true;
}
}
if (obj.InstanceOf(osn::AdvancedStreaming::constructor.Value())) {
auto *s = Napi::ObjectWrap<osn::AdvancedStreaming>::Unwrap(obj);
if (s) {
out_uid = s->uid;
return true;
}
}
if (obj.InstanceOf(osn::EnhancedBroadcastingSimpleStreaming::constructor.Value())) {
auto *s = Napi::ObjectWrap<osn::EnhancedBroadcastingSimpleStreaming>::Unwrap(obj);
if (s) {
out_uid = s->uid;
return true;
}
}
if (obj.InstanceOf(osn::EnhancedBroadcastingAdvancedStreaming::constructor.Value())) {
auto *s = Napi::ObjectWrap<osn::EnhancedBroadcastingAdvancedStreaming>::Unwrap(obj);
if (s) {
out_uid = s->uid;
return true;
}
}
return false;
}
2 changes: 2 additions & 0 deletions obs-studio-client/source/streaming.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,6 @@ class Streaming : public WorkerSignals {
void Start(const Napi::CallbackInfo &info);
void Stop(const Napi::CallbackInfo &info);
};

bool TryUnwrapStreamingUid(const Napi::Value &value, uint64_t &out_uid);
}
12 changes: 12 additions & 0 deletions obs-studio-client/source/video.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Napi::Object osn::Video::Init(Napi::Env env, Napi::Object exports)

InstanceAccessor("skippedFrames", &osn::Video::GetSkippedFrames, nullptr),
InstanceAccessor("encodedFrames", &osn::Video::GetEncodedFrames, nullptr),
InstanceAccessor("canvasId", &osn::Video::GetCanvasId, nullptr),
InstanceMethod("refresh", &osn::Video::Refresh),
});
exports.Set("Video", func);
osn::Video::constructor = Napi::Persistent(func);
Expand Down Expand Up @@ -109,6 +111,16 @@ void osn::Video::Destroy(const Napi::CallbackInfo &info)
return;
}

Napi::Value osn::Video::GetCanvasId(const Napi::CallbackInfo &info)
{
return Napi::Number::New(info.Env(), (double)this->canvasId);
}

void osn::Video::Refresh(const Napi::CallbackInfo &)
{
isLastVideoValid = false;
}

inline void CreateVideo(const Napi::CallbackInfo &info, const std::vector<ipc::value> &response, Napi::Object &video, uint32_t index)
{
video.Set("fpsNum", response.at(index++).value_union.ui32);
Expand Down
10 changes: 10 additions & 0 deletions obs-studio-client/source/video.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,15 @@ class Video : public Napi::ObjectWrap<osn::Video> {

Napi::Value GetLegacySettings(const Napi::CallbackInfo &info);
void SetLegacySettings(const Napi::CallbackInfo &info, const Napi::Value &value);

// Read-only accessor exposing the server-side canvas id (the same value the
// server's osn::Video::Manager keys this object by). Required so the frontend
// can refer to a canvas in APIs like autoconfig that take ids.
Napi::Value GetCanvasId(const Napi::CallbackInfo &info);

// Drops the cached video-info snapshot so the next `video` read hits the server.
// Needed when the server mutated the canvas without going through this object's
// JS setter — e.g. after autoconfig's apply phase pushes a new resolution / FPS.
void Refresh(const Napi::CallbackInfo &info);
};
}
1 change: 1 addition & 0 deletions obs-studio-server/source/nodeobs_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "util/lexer.h"
#include "util-crashmanager.h"
#include "util-metricsprovider.h"
#include "nodeobs_service.h"

#include "osn-streaming.hpp"
#include "osn-recording.hpp"
Expand Down
2 changes: 1 addition & 1 deletion obs-studio-server/source/nodeobs_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
#include <vector>
#include <queue>
#include "nodeobs_configManager.hpp"
#include "nodeobs_service.h"
//#include "nodeobs_service.h"
#include "util-osx.hpp"

extern std::string g_moduleDirectory;
Expand Down
Loading
Loading