From fb05848426933bf345cddbf8dca4d6413c8b0646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lepin?= Date: Tue, 11 Jun 2019 13:38:29 +0200 Subject: [PATCH 1/7] requests: outputs API WIP --- src/WSRequestHandler.cpp | 7 +- src/WSRequestHandler.h | 19 +++-- src/WSRequestHandler_Outputs.cpp | 140 +++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 6 deletions(-) create mode 100644 src/WSRequestHandler_Outputs.cpp diff --git a/src/WSRequestHandler.cpp b/src/WSRequestHandler.cpp index 098b84ee..abbf07d3 100644 --- a/src/WSRequestHandler.cpp +++ b/src/WSRequestHandler.cpp @@ -125,7 +125,12 @@ QHash 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 WSRequestHandler::authNotRequired { diff --git a/src/WSRequestHandler.h b/src/WSRequestHandler.h index 4bae1a53..4464587f 100644 --- a/src/WSRequestHandler.h +++ b/src/WSRequestHandler.h @@ -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(const char* 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; @@ -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 messageMap; static QSet authNotRequired; @@ -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); }; diff --git a/src/WSRequestHandler_Outputs.cpp b/src/WSRequestHandler_Outputs.cpp new file mode 100644 index 00000000..a704a0f9 --- /dev/null +++ b/src/WSRequestHandler_Outputs.cpp @@ -0,0 +1,140 @@ +#include "WSRequestHandler.h" + +/** +* @typedef {Object} `Output` +* @property {String} `name` Output name +* @property {String} `type` Output type/kind +* @property {Object} `settings` Output name +* @property {boolean} `active` Output status (active or not) +* @property {double} `congestion` Output congestion +*/ +obs_data_t* getOutputInfo(obs_output_t* output) +{ + if (!output) { + return nullptr; + } + + OBSDataAutoRelease settings = obs_output_get_settings(output); + + 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_obj(data, "settings", settings); + obs_data_set_bool(data, "active", obs_output_active(output)); + obs_data_set_double(data, "congestion", obs_output_get_congestion(output)); + return data; +} + +HandlerResponse findOutputOrFail(WSRequestHandler* req, std::function 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} `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(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) { + // TODO check if already active + if (!obs_output_start(output)) { + return req->SendErrorResponse("output start failed"); + } + 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) { + // TODO check if output is already stopped + + bool forceStop = obs_data_get_bool(req->data, "force"); + if (forceStop) { + obs_output_force_stop(output); + } else { + obs_output_stop(output); + } + + return req->SendOKResponse(); + }); +} From ef6df948387d4926f24ceef9b3ed43bf04c468a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lepin?= Date: Tue, 11 Jun 2019 13:43:51 +0200 Subject: [PATCH 2/7] outputs: todos --- src/WSRequestHandler_Outputs.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/WSRequestHandler_Outputs.cpp b/src/WSRequestHandler_Outputs.cpp index a704a0f9..4ce414a0 100644 --- a/src/WSRequestHandler_Outputs.cpp +++ b/src/WSRequestHandler_Outputs.cpp @@ -19,9 +19,19 @@ obs_data_t* getOutputInfo(obs_output_t* output) 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)); + // TODO flags obs_data_set_obj(data, "settings", settings); obs_data_set_bool(data, "active", obs_output_active(output)); + // TODO reconnecting obs_data_set_double(data, "congestion", obs_output_get_congestion(output)); + // TODO width + // TODO height + // TODO delay + // TODO active delay + // TODO connect time ms + // TODO total frames + // TODO frames dropped + // TODO total bytes return data; } @@ -106,6 +116,7 @@ HandlerResponse WSRequestHandler::HandleStartOutput(WSRequestHandler* req) return findOutputOrFail(req, [req](obs_output_t* output) { // TODO check if already active if (!obs_output_start(output)) { + // TODO get last error message return req->SendErrorResponse("output start failed"); } return req->SendOKResponse(); From 480945073a4dce84717d3f3ae533039332d7b0ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lepin?= Date: Tue, 11 Jun 2019 13:45:14 +0200 Subject: [PATCH 3/7] outputs: start or stop only when possible --- src/WSRequestHandler_Outputs.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/WSRequestHandler_Outputs.cpp b/src/WSRequestHandler_Outputs.cpp index 4ce414a0..424d282c 100644 --- a/src/WSRequestHandler_Outputs.cpp +++ b/src/WSRequestHandler_Outputs.cpp @@ -114,7 +114,10 @@ HandlerResponse WSRequestHandler::HandleGetOutputInfo(WSRequestHandler* req) HandlerResponse WSRequestHandler::HandleStartOutput(WSRequestHandler* req) { return findOutputOrFail(req, [req](obs_output_t* output) { - // TODO check if already active + if (obs_output_active(output)) { + return req->SendErrorResponse("output already active"); + } + if (!obs_output_start(output)) { // TODO get last error message return req->SendErrorResponse("output start failed"); @@ -137,7 +140,9 @@ HandlerResponse WSRequestHandler::HandleStartOutput(WSRequestHandler* req) HandlerResponse WSRequestHandler::HandleStopOutput(WSRequestHandler* req) { return findOutputOrFail(req, [req](obs_output_t* output) { - // TODO check if output is already stopped + if (!obs_output_active(output)) { + return req->SendErrorResponse("output not active"); + } bool forceStop = obs_data_get_bool(req->data, "force"); if (forceStop) { From 6fed6f9f7d02525e9de4b0efe815c3c40aa69e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lepin?= Date: Tue, 11 Jun 2019 13:49:21 +0200 Subject: [PATCH 4/7] StartOutput: provide output error message when start fails --- src/WSRequestHandler.cpp | 4 ++-- src/WSRequestHandler.h | 2 +- src/WSRequestHandler_Outputs.cpp | 9 ++++++--- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/WSRequestHandler.cpp b/src/WSRequestHandler.cpp index abbf07d3..53d43fc1 100644 --- a/src/WSRequestHandler.cpp +++ b/src/WSRequestHandler.cpp @@ -201,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); } diff --git a/src/WSRequestHandler.h b/src/WSRequestHandler.h index 4464587f..b2eefa3d 100644 --- a/src/WSRequestHandler.h +++ b/src/WSRequestHandler.h @@ -44,7 +44,7 @@ class WSRequestHandler : public QObject { bool hasField(QString name); HandlerResponse SendOKResponse(obs_data_t* additionalFields = nullptr); - HandlerResponse SendErrorResponse(const char* errorMessage); + HandlerResponse SendErrorResponse(QString errorMessage); HandlerResponse SendErrorResponse(obs_data_t* additionalFields = nullptr); HandlerResponse SendResponse(const char* status, obs_data_t* additionalFields = nullptr); diff --git a/src/WSRequestHandler_Outputs.cpp b/src/WSRequestHandler_Outputs.cpp index 424d282c..92c0a03a 100644 --- a/src/WSRequestHandler_Outputs.cpp +++ b/src/WSRequestHandler_Outputs.cpp @@ -118,10 +118,13 @@ HandlerResponse WSRequestHandler::HandleStartOutput(WSRequestHandler* req) return req->SendErrorResponse("output already active"); } - if (!obs_output_start(output)) { - // TODO get last error message - return req->SendErrorResponse("output start failed"); + 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(); }); } From 2b746d135387f76e2d4a0fc67b45294e1c01ba35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lepin?= Date: Tue, 11 Jun 2019 14:07:44 +0200 Subject: [PATCH 5/7] cmake: add output requests --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index bcd31ca8..435a4e37 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 From 0b42e3d0f3e8bd62f2d26e1bbf1aefbf46ff5a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lepin?= Date: Tue, 11 Jun 2019 14:11:27 +0200 Subject: [PATCH 6/7] outputs: add more info fields --- src/WSRequestHandler_Outputs.cpp | 39 ++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/src/WSRequestHandler_Outputs.cpp b/src/WSRequestHandler_Outputs.cpp index 92c0a03a..f1c28bd3 100644 --- a/src/WSRequestHandler_Outputs.cpp +++ b/src/WSRequestHandler_Outputs.cpp @@ -4,9 +4,22 @@ * @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) { @@ -16,22 +29,34 @@ obs_data_t* getOutputInfo(obs_output_t* output) 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)); - // TODO flags + 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)); - // TODO reconnecting + obs_data_set_bool(data, "reconnecting", obs_output_reconnecting(output)); obs_data_set_double(data, "congestion", obs_output_get_congestion(output)); - // TODO width - // TODO height + 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)); // TODO delay // TODO active delay + // TODO connect time ms - // TODO total frames - // TODO frames dropped - // TODO total bytes return data; } From 0b5cb76b5dc76f14ad97e7ff446bee8a4f784751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lepin?= Date: Tue, 16 Jul 2019 18:22:07 +0200 Subject: [PATCH 7/7] outputs: remove TODOs --- src/WSRequestHandler_Outputs.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/WSRequestHandler_Outputs.cpp b/src/WSRequestHandler_Outputs.cpp index f1c28bd3..45469411 100644 --- a/src/WSRequestHandler_Outputs.cpp +++ b/src/WSRequestHandler_Outputs.cpp @@ -53,10 +53,7 @@ obs_data_t* getOutputInfo(obs_output_t* 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)); - // TODO delay - // TODO active delay - // TODO connect time ms return data; }