From 6af3af61d5b3453ec336e53b6817ce2a4dc73d95 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Sat, 2 Apr 2022 22:46:00 +0800 Subject: [PATCH] requesthandler: Add Outputs requests Co-authored-by: Ruggero Tomaselli Co-authored-by: tt2468 --- src/requesthandler/RequestHandler.cpp | 7 + src/requesthandler/RequestHandler.h | 7 + src/requesthandler/RequestHandler_Outputs.cpp | 211 ++++++++++++++++++ src/requesthandler/RequestHandler_Stream.cpp | 2 + src/requesthandler/RequestHandler_Ui.cpp | 9 +- src/requesthandler/rpc/Request.cpp | 18 ++ src/requesthandler/rpc/Request.h | 2 + src/utils/Obs.h | 1 + src/utils/Obs_ArrayHelper.cpp | 32 +++ 9 files changed, 286 insertions(+), 3 deletions(-) diff --git a/src/requesthandler/RequestHandler.cpp b/src/requesthandler/RequestHandler.cpp index 28858b29d..e2c4fe5a3 100644 --- a/src/requesthandler/RequestHandler.cpp +++ b/src/requesthandler/RequestHandler.cpp @@ -151,6 +151,13 @@ const std::unordered_map RequestHandler::_han {"StopReplayBuffer", &RequestHandler::StopReplayBuffer}, {"SaveReplayBuffer", &RequestHandler::SaveReplayBuffer}, {"GetLastReplayBufferReplay", &RequestHandler::GetLastReplayBufferReplay}, + {"GetOutputList", &RequestHandler::GetOutputList}, + {"GetOutputStatus", &RequestHandler::GetOutputStatus}, + {"ToggleOutput", &RequestHandler::ToggleOutput}, + {"StartOutput", &RequestHandler::StartOutput}, + {"StopOutput", &RequestHandler::StopOutput}, + {"GetOutputSettings", &RequestHandler::GetOutputSettings}, + {"SetOutputSettings", &RequestHandler::SetOutputSettings}, // Stream {"GetStreamStatus", &RequestHandler::GetStreamStatus}, diff --git a/src/requesthandler/RequestHandler.h b/src/requesthandler/RequestHandler.h index fcf574371..4d9613843 100644 --- a/src/requesthandler/RequestHandler.h +++ b/src/requesthandler/RequestHandler.h @@ -170,6 +170,13 @@ class RequestHandler { RequestResult StopReplayBuffer(const Request &); RequestResult SaveReplayBuffer(const Request &); RequestResult GetLastReplayBufferReplay(const Request &); + RequestResult GetOutputList(const Request &); + RequestResult GetOutputStatus(const Request &); + RequestResult ToggleOutput(const Request &); + RequestResult StartOutput(const Request &); + RequestResult StopOutput(const Request &); + RequestResult GetOutputSettings(const Request &); + RequestResult SetOutputSettings(const Request &); // Stream RequestResult GetStreamStatus(const Request &); diff --git a/src/requesthandler/RequestHandler_Outputs.cpp b/src/requesthandler/RequestHandler_Outputs.cpp index f1e599459..87ab91648 100644 --- a/src/requesthandler/RequestHandler_Outputs.cpp +++ b/src/requesthandler/RequestHandler_Outputs.cpp @@ -275,3 +275,214 @@ RequestResult RequestHandler::GetLastReplayBufferReplay(const Request &) responseData["savedReplayPath"] = Utils::Obs::StringHelper::GetLastReplayBufferFileName(); return RequestResult::Success(responseData); } + +/** + * Gets the list of available outputs. + * + * @requestType GetOutputList + * @complexity 4 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category outputs + */ +RequestResult RequestHandler::GetOutputList(const Request &) +{ + json responseData; + responseData["outputs"] = Utils::Obs::ArrayHelper::GetOutputList(); + return RequestResult::Success(responseData); +} + +/** + * Gets the status of an output. + * + * @requestField outputName | String | Output name + * + * @responseField outputActive | Boolean | Whether the output is active + * @responseField outputReconnecting | Boolean | Whether the output is reconnecting + * @responseField outputTimecode | String | Current formatted timecode string for the output + * @responseField outputDuration | Number | Current duration in milliseconds for the output + * @responseField outputCongestion | Number | Congestion of the output + * @responseField outputBytes | Number | Number of bytes sent by the output + * @responseField outputSkippedFrames | Number | Number of frames skipped by the output's process + * @responseField outputTotalFrames | Number | Total number of frames delivered by the output's process + * + * @requestType GetOutputStatus + * @complexity 4 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category outputs + */ +RequestResult RequestHandler::GetOutputStatus(const Request &request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + OBSOutputAutoRelease output = request.ValidateOutput("outputName", statusCode, comment); + if (!output) + return RequestResult::Error(statusCode, comment); + + uint64_t outputDuration = Utils::Obs::NumberHelper::GetOutputDuration(output); + + json responseData; + responseData["outputActive"] = obs_output_active(output); + responseData["outputReconnecting"] = obs_output_reconnecting(output); + responseData["outputTimecode"] = Utils::Obs::StringHelper::DurationToTimecode(outputDuration); + responseData["outputDuration"] = outputDuration; + responseData["outputCongestion"] = obs_output_get_congestion(output); + responseData["outputBytes"] = obs_output_get_total_bytes(output); + responseData["outputSkippedFrames"] = obs_output_get_frames_dropped(output); + responseData["outputTotalFrames"] = obs_output_get_total_frames(output); + + return RequestResult::Success(responseData); +} + +/** + * Toggles the status of an output. + * + * @requestField outputName | String | Output name + * + * @responseField outputActive | Boolean | Whether the output is active + * + * @requestType ToggleOutput + * @complexity 4 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category outputs + */ +RequestResult RequestHandler::ToggleOutput(const Request &request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + OBSOutputAutoRelease output = request.ValidateOutput("outputName", statusCode, comment); + if (!output) + return RequestResult::Error(statusCode, comment); + + bool outputActive = obs_output_active(output); + if (outputActive) + obs_output_stop(output); + else + obs_output_start(output); + + json responseData; + responseData["outputActive"] = !outputActive; + return RequestResult::Success(responseData); +} + +/** + * Starts an output. + * + * @requestField outputName | String | Output name + * + * @requestType StartOutput + * @complexity 4 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category outputs + */ +RequestResult RequestHandler::StartOutput(const Request &request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + OBSOutputAutoRelease output = request.ValidateOutput("outputName", statusCode, comment); + if (!output) + return RequestResult::Error(statusCode, comment); + + if (obs_output_active(output)) + return RequestResult::Error(RequestStatus::OutputRunning); + + obs_output_start(output); + + return RequestResult::Success(); +} + +/** + * Stops an output. + * + * @requestField outputName | String | Output name + * + * @requestType StopOutput + * @complexity 4 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category outputs + */ +RequestResult RequestHandler::StopOutput(const Request &request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + OBSOutputAutoRelease output = request.ValidateOutput("outputName", statusCode, comment); + if (!output) + return RequestResult::Error(statusCode, comment); + + if (!obs_output_active(output)) + return RequestResult::Error(RequestStatus::OutputNotRunning); + + obs_output_stop(output); + + return RequestResult::Success(); +} + +/** + * Gets the settings of an output. + * + * @requestField outputName | String | Output name + * + * @responseField outputSettings | Object | Output settings + * + * @requestType GetOutputSettings + * @complexity 4 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category outputs + */ +RequestResult RequestHandler::GetOutputSettings(const Request &request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + OBSOutputAutoRelease output = request.ValidateOutput("outputName", statusCode, comment); + if (!output) + return RequestResult::Error(statusCode, comment); + + OBSDataAutoRelease outputSettings = obs_output_get_settings(output); + + json responseData; + responseData["outputSettings"] = Utils::Json::ObsDataToJson(outputSettings); + return RequestResult::Success(responseData); +} + +/** + * Sets the settings of an output. + * + * @requestField outputName | String | Output name + * @requestField outputSettings | Object | Output settings + * + * @requestType SetOutputSettings + * @complexity 4 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category outputs + */ +RequestResult RequestHandler::SetOutputSettings(const Request &request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + OBSOutputAutoRelease output = request.ValidateOutput("outputName", statusCode, comment); + if (!(output && request.ValidateObject("outputSettings", statusCode, comment, true))) + return RequestResult::Error(statusCode, comment); + + OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["outputSettings"]); + if (!newSettings) + // This should never happen + return RequestResult::Error(RequestStatus::RequestProcessingFailed, + "An internal data conversion operation failed. Please report this!"); + + obs_output_update(output, newSettings); + + return RequestResult::Success(); +} diff --git a/src/requesthandler/RequestHandler_Stream.cpp b/src/requesthandler/RequestHandler_Stream.cpp index 11a15c8f1..1a0b6ee23 100644 --- a/src/requesthandler/RequestHandler_Stream.cpp +++ b/src/requesthandler/RequestHandler_Stream.cpp @@ -26,6 +26,7 @@ with this program. If not, see * @responseField outputReconnecting | Boolean | Whether the output is currently reconnecting * @responseField outputTimecode | String | Current formatted timecode string for the output * @responseField outputDuration | Number | Current duration in milliseconds for the output + * @responseField outputCongestion | Number | Congestion of the output * @responseField outputBytes | Number | Number of bytes sent by the output * @responseField outputSkippedFrames | Number | Number of frames skipped by the output's process * @responseField outputTotalFrames | Number | Total number of frames delivered by the output's process @@ -48,6 +49,7 @@ RequestResult RequestHandler::GetStreamStatus(const Request &) responseData["outputReconnecting"] = obs_output_reconnecting(streamOutput); responseData["outputTimecode"] = Utils::Obs::StringHelper::DurationToTimecode(outputDuration); responseData["outputDuration"] = outputDuration; + responseData["outputCongestion"] = obs_output_get_congestion(streamOutput); responseData["outputBytes"] = (uint64_t)obs_output_get_total_bytes(streamOutput); responseData["outputSkippedFrames"] = obs_output_get_frames_dropped(streamOutput); responseData["outputTotalFrames"] = obs_output_get_total_frames(streamOutput); diff --git a/src/requesthandler/RequestHandler_Ui.cpp b/src/requesthandler/RequestHandler_Ui.cpp index e986a2a00..266428829 100644 --- a/src/requesthandler/RequestHandler_Ui.cpp +++ b/src/requesthandler/RequestHandler_Ui.cpp @@ -232,7 +232,8 @@ RequestResult RequestHandler::OpenVideoMixProjector(const Request &request) else if (videoMixType == "OBS_WEBSOCKET_VIDEO_MIX_TYPE_MULTIVIEW") projectorType = "Multiview"; else - return RequestResult::Error(RequestStatus::InvalidRequestField, "The field `videoMixType` has an invalid enum value."); + return RequestResult::Error(RequestStatus::InvalidRequestField, + "The field `videoMixType` has an invalid enum value."); int monitorIndex = -1; if (request.Contains("monitorIndex")) { @@ -246,7 +247,8 @@ RequestResult RequestHandler::OpenVideoMixProjector(const Request &request) if (!request.ValidateOptionalString("projectorGeometry", statusCode, comment)) return RequestResult::Error(statusCode, comment); if (monitorIndex != -1) - return RequestResult::Error(RequestStatus::TooManyRequestFields, "`monitorIndex` and `projectorGeometry` are mutually exclusive."); + return RequestResult::Error(RequestStatus::TooManyRequestFields, + "`monitorIndex` and `projectorGeometry` are mutually exclusive."); projectorGeometry = request.RequestData["projectorGeometry"]; } @@ -291,7 +293,8 @@ RequestResult RequestHandler::OpenSourceProjector(const Request &request) if (!request.ValidateOptionalString("projectorGeometry", statusCode, comment)) return RequestResult::Error(statusCode, comment); if (monitorIndex != -1) - return RequestResult::Error(RequestStatus::TooManyRequestFields, "`monitorIndex` and `projectorGeometry` are mutually exclusive."); + return RequestResult::Error(RequestStatus::TooManyRequestFields, + "`monitorIndex` and `projectorGeometry` are mutually exclusive."); projectorGeometry = request.RequestData["projectorGeometry"]; } diff --git a/src/requesthandler/rpc/Request.cpp b/src/requesthandler/rpc/Request.cpp index 9e55237d2..1dc9bef12 100644 --- a/src/requesthandler/rpc/Request.cpp +++ b/src/requesthandler/rpc/Request.cpp @@ -354,3 +354,21 @@ obs_sceneitem_t *Request::ValidateSceneItem(const std::string &sceneKeyName, con obs_sceneitem_addref(sceneItem); return sceneItem; } + +obs_output_t *Request::ValidateOutput(const std::string &keyName, RequestStatus::RequestStatus &statusCode, + std::string &comment) const +{ + if (!ValidateString(keyName, statusCode, comment)) + return nullptr; + + std::string outputName = RequestData[keyName]; + + obs_output_t *ret = obs_get_output_by_name(outputName.c_str()); + if (!ret) { + statusCode = RequestStatus::ResourceNotFound; + comment = std::string("No output was found with the name `") + outputName + "`."; + return nullptr; + } + + return ret; +} diff --git a/src/requesthandler/rpc/Request.h b/src/requesthandler/rpc/Request.h index b36905d67..3ee1149a3 100644 --- a/src/requesthandler/rpc/Request.h +++ b/src/requesthandler/rpc/Request.h @@ -77,6 +77,8 @@ struct Request { obs_sceneitem_t *ValidateSceneItem(const std::string &sceneKeyName, const std::string &sceneItemIdKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const; + obs_output_t *ValidateOutput(const std::string &keyName, RequestStatus::RequestStatus &statusCode, + std::string &comment) const; std::string RequestType; bool HasRequestData; diff --git a/src/utils/Obs.h b/src/utils/Obs.h index 6298a6917..71be4b545 100644 --- a/src/utils/Obs.h +++ b/src/utils/Obs.h @@ -205,6 +205,7 @@ namespace Utils { std::vector GetSceneTransitionList(); std::vector GetSourceFilterList(obs_source_t *source); std::vector GetFilterKindList(); + std::vector GetOutputList(); } namespace ObjectHelper { diff --git a/src/utils/Obs_ArrayHelper.cpp b/src/utils/Obs_ArrayHelper.cpp index 421f276b0..1a48e419e 100644 --- a/src/utils/Obs_ArrayHelper.cpp +++ b/src/utils/Obs_ArrayHelper.cpp @@ -327,3 +327,35 @@ std::vector Utils::Obs::ArrayHelper::GetSourceFilterList(obs_source_t *sou return filters; } + +std::vector Utils::Obs::ArrayHelper::GetOutputList() +{ + std::vector outputs; + + auto cb = [](void *param, obs_output_t *output) { + auto outputs = reinterpret_cast *>(param); + + auto rawFlags = obs_output_get_flags(output); + json flags; + flags["OBS_OUTPUT_AUDIO"] = !!(rawFlags & OBS_OUTPUT_AUDIO); + flags["OBS_OUTPUT_VIDEO"] = !!(rawFlags & OBS_OUTPUT_VIDEO); + flags["OBS_OUTPUT_ENCODED"] = !!(rawFlags & OBS_OUTPUT_ENCODED); + flags["OBS_OUTPUT_MULTI_TRACK"] = !!(rawFlags & OBS_OUTPUT_MULTI_TRACK); + flags["OBS_OUTPUT_SERVICE"] = !!(rawFlags & OBS_OUTPUT_SERVICE); + + json outputJson; + outputJson["outputName"] = obs_output_get_name(output); + outputJson["outputKind"] = obs_output_get_id(output); + outputJson["outputWidth"] = obs_output_get_width(output); + outputJson["outputHeight"] = obs_output_get_height(output); + outputJson["outputActive"] = obs_output_active(output); + outputJson["outputFlags"] = flags; + + outputs->push_back(outputJson); + return true; + }; + + obs_enum_outputs(cb, &outputs); + + return outputs; +}