Skip to content
Merged
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
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ set(obs-websocket_SOURCES
src/WSRequestHandler_Streaming.cpp
src/WSRequestHandler_StudioMode.cpp
src/WSRequestHandler_Transitions.cpp
src/WSRequestHandler_Outputs.cpp
src/WSEvents.cpp
src/Config.cpp
src/Utils.cpp
Expand Down
11 changes: 8 additions & 3 deletions src/WSRequestHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,12 @@ QHash<QString, HandlerResponse(*)(WSRequestHandler*)> WSRequestHandler::messageM
{ "GetTextFreetype2Properties", WSRequestHandler::HandleGetTextFreetype2Properties },

{ "GetBrowserSourceProperties", WSRequestHandler::HandleGetBrowserSourceProperties },
{ "SetBrowserSourceProperties", WSRequestHandler::HandleSetBrowserSourceProperties }
{ "SetBrowserSourceProperties", WSRequestHandler::HandleSetBrowserSourceProperties },

{ "ListOutputs", WSRequestHandler::HandleListOutputs },
{ "GetOutputInfo", WSRequestHandler::HandleGetOutputInfo },
{ "StartOutput", WSRequestHandler::HandleStartOutput },
{ "StopOutput", WSRequestHandler::HandleStopOutput }
};

QSet<QString> WSRequestHandler::authNotRequired {
Expand Down Expand Up @@ -196,9 +201,9 @@ HandlerResponse WSRequestHandler::SendOKResponse(obs_data_t* additionalFields) {
return SendResponse("ok", additionalFields);
}

HandlerResponse WSRequestHandler::SendErrorResponse(const char* errorMessage) {
HandlerResponse WSRequestHandler::SendErrorResponse(QString errorMessage) {
OBSDataAutoRelease fields = obs_data_create();
obs_data_set_string(fields, "error", errorMessage);
obs_data_set_string(fields, "error", errorMessage.toUtf8().constData());

return SendResponse("error", fields);
}
Expand Down
19 changes: 14 additions & 5 deletions src/WSRequestHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,15 @@ class WSRequestHandler : public QObject {
std::string processIncomingMessage(std::string& textMessage);
bool hasField(QString name);

HandlerResponse SendOKResponse(obs_data_t* additionalFields = nullptr);
HandlerResponse SendErrorResponse(QString errorMessage);
HandlerResponse SendErrorResponse(obs_data_t* additionalFields = nullptr);
HandlerResponse SendResponse(const char* status, obs_data_t* additionalFields = nullptr);

obs_data_t* parameters() {
return this->data;
}

private:
const char* _messageId;
const char* _requestType;
Expand All @@ -51,11 +60,6 @@ class WSRequestHandler : public QObject {

HandlerResponse processRequest(std::string& textMessage);

HandlerResponse SendOKResponse(obs_data_t* additionalFields = nullptr);
HandlerResponse SendErrorResponse(const char* errorMessage);
HandlerResponse SendErrorResponse(obs_data_t* additionalFields = nullptr);
HandlerResponse SendResponse(const char* status, obs_data_t* additionalFields = nullptr);

static QHash<QString, HandlerResponse(*)(WSRequestHandler*)> messageMap;
static QSet<QString> authNotRequired;

Expand Down Expand Up @@ -160,4 +164,9 @@ class WSRequestHandler : public QObject {

static HandlerResponse HandleSetBrowserSourceProperties(WSRequestHandler* req);
static HandlerResponse HandleGetBrowserSourceProperties(WSRequestHandler* req);

static HandlerResponse HandleListOutputs(WSRequestHandler* req);
static HandlerResponse HandleGetOutputInfo(WSRequestHandler* req);
static HandlerResponse HandleStartOutput(WSRequestHandler* req);
static HandlerResponse HandleStopOutput(WSRequestHandler* req);
};
181 changes: 181 additions & 0 deletions src/WSRequestHandler_Outputs.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
#include "WSRequestHandler.h"

/**
* @typedef {Object} `Output`
* @property {String} `name` Output name
* @property {String} `type` Output type/kind
* @property {int} `width` Video output width
* @property {int} `height` Video output height
* @property {Object} `flags` Output flags
* @property {int} `flags.rawValue` Raw flags value
* @property {boolean} `flags.audio` Output uses audio
* @property {boolean} `flags.video` Output uses video
* @property {boolean} `flags.encoded` Output is encoded
* @property {boolean} `flags.multiTrack` Output uses several audio tracks
* @property {boolean} `flags.service` Output uses a service
* @property {Object} `settings` Output name
* @property {boolean} `active` Output status (active or not)
* @property {boolean} `reconnecting` Output reconnection status (reconnecting or not)
* @property {double} `congestion` Output congestion
* @property {int} `totalFrames` Number of frames sent
* @property {int} `droppedFrames` Number of frames dropped
* @property {int} `totalBytes` Total bytes sent
*/
obs_data_t* getOutputInfo(obs_output_t* output)
{
if (!output) {
return nullptr;
}

OBSDataAutoRelease settings = obs_output_get_settings(output);

uint32_t rawFlags = obs_output_get_flags(output);
OBSDataAutoRelease flags = obs_data_create();
obs_data_set_int(flags, "rawValue", rawFlags);
obs_data_set_bool(flags, "audio", rawFlags & OBS_OUTPUT_AUDIO);
obs_data_set_bool(flags, "video", rawFlags & OBS_OUTPUT_VIDEO);
obs_data_set_bool(flags, "encoded", rawFlags & OBS_OUTPUT_ENCODED);
obs_data_set_bool(flags, "multiTrack", rawFlags & OBS_OUTPUT_MULTI_TRACK);
obs_data_set_bool(flags, "service", rawFlags & OBS_OUTPUT_SERVICE);

obs_data_t* data = obs_data_create();

obs_data_set_string(data, "name", obs_output_get_name(output));
obs_data_set_string(data, "type", obs_output_get_id(output));
obs_data_set_int(data, "width", obs_output_get_width(output));
obs_data_set_int(data, "height", obs_output_get_height(output));
obs_data_set_obj(data, "flags", flags);
obs_data_set_obj(data, "settings", settings);

obs_data_set_bool(data, "active", obs_output_active(output));
obs_data_set_bool(data, "reconnecting", obs_output_reconnecting(output));
obs_data_set_double(data, "congestion", obs_output_get_congestion(output));
obs_data_set_int(data, "totalFrames", obs_output_get_total_frames(output));
obs_data_set_int(data, "droppedFrames", obs_output_get_frames_dropped(output));
obs_data_set_int(data, "totalBytes", obs_output_get_total_bytes(output));

return data;
}

HandlerResponse findOutputOrFail(WSRequestHandler* req, std::function<HandlerResponse (obs_output_t*)> callback)
{
if (!req->hasField("outputName")) {
return req->SendErrorResponse("missing request parameters");
}

const char* outputName = obs_data_get_string(req->parameters(), "outputName");
OBSOutputAutoRelease output = obs_get_output_by_name(outputName);
if (!output) {
return req->SendErrorResponse("specified output doesn't exist");
}

return callback(output);
}

/**
* List existing outputs
*
* @return {Array<Output>} `outputs` Outputs list
*
* @api requests
* @name ListOutputs
* @category outputs
* @since 4.7.0
*/
HandlerResponse WSRequestHandler::HandleListOutputs(WSRequestHandler* req)
{
OBSDataArrayAutoRelease outputs = obs_data_array_create();

obs_enum_outputs([](void* param, obs_output_t* output) {
obs_data_array_t* outputs = reinterpret_cast<obs_data_array_t*>(param);

OBSDataAutoRelease outputInfo = getOutputInfo(output);
obs_data_array_push_back(outputs, outputInfo);

return true;
}, outputs);

OBSDataAutoRelease fields = obs_data_create();
obs_data_set_array(fields, "outputs", outputs);
return req->SendOKResponse(fields);
}

/**
* Get information about a single output
*
* @param {String} `outputName` Output name
*
* @return {Output} `outputInfo` Output info
*
* @api requests
* @name GetOutputInfo
* @category outputs
* @since 4.7.0
*/
HandlerResponse WSRequestHandler::HandleGetOutputInfo(WSRequestHandler* req)
{
return findOutputOrFail(req, [req](obs_output_t* output) {
OBSDataAutoRelease outputInfo = getOutputInfo(output);

OBSDataAutoRelease fields = obs_data_create();
obs_data_set_obj(fields, "outputInfo", outputInfo);
return req->SendOKResponse(fields);
});
}

/**
* Start an output
*
* @param {String} `outputName` Output name
*
* @api requests
* @name StartOutput
* @category outputs
* @since 4.7.0
*/
HandlerResponse WSRequestHandler::HandleStartOutput(WSRequestHandler* req)
{
return findOutputOrFail(req, [req](obs_output_t* output) {
if (obs_output_active(output)) {
return req->SendErrorResponse("output already active");
}

bool success = obs_output_start(output);
if (!success) {
QString lastError = obs_output_get_last_error(output);
QString errorMessage = QString("output start failed: %1").arg(lastError);
return req->SendErrorResponse(errorMessage);
}

return req->SendOKResponse();
});
}

/**
* Stop an output
*
* @param {String} `outputName` Output name
* @param {boolean (optional)} `force` Force stop (default: false)
*
* @api requests
* @name StopOutput
* @category outputs
* @since 4.7.0
*/
HandlerResponse WSRequestHandler::HandleStopOutput(WSRequestHandler* req)
{
return findOutputOrFail(req, [req](obs_output_t* output) {
if (!obs_output_active(output)) {
return req->SendErrorResponse("output not active");
}

bool forceStop = obs_data_get_bool(req->data, "force");
if (forceStop) {
obs_output_force_stop(output);
} else {
obs_output_stop(output);
}

return req->SendOKResponse();
});
}