From cda7aed26e994330432f63c022812d806728551f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lepin?= Date: Sun, 16 Oct 2016 22:02:51 +0200 Subject: [PATCH] Lots of changes. Big update ! --- CMakeLists.txt | 10 ++- Utils.cpp | 30 +++++++ Utils.h | 13 ++++ WSEvents.cpp | 182 +++++++++++++++++++++++++++++++++++++++++++ WSEvents.h | 42 ++++++++++ WSRequestHandler.cpp | 132 +++++++++++++++++++++++++++++++ WSRequestHandler.h | 36 +++++++++ WSServer.cpp | 27 +------ obs-websocket.cpp | 50 ++---------- 9 files changed, 452 insertions(+), 70 deletions(-) create mode 100644 Utils.cpp create mode 100644 Utils.h create mode 100644 WSEvents.cpp create mode 100644 WSEvents.h create mode 100644 WSRequestHandler.cpp create mode 100644 WSRequestHandler.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bbb1f6e..752019af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,10 +12,16 @@ find_package(Qt5WebSockets REQUIRED) set(obs-websocket_SOURCES obs-websocket.cpp - WSServer.cpp) + WSServer.cpp + WSRequestHandler.cpp + WSEvents.cpp + Utils.cpp) set(obs-websocket_HEADERS - WSServer.h) + WSServer.h + WSRequestHandler.h + WSEvents.h + Utils.h) add_library(obs-websocket MODULE ${obs-websocket_SOURCES} diff --git a/Utils.cpp b/Utils.cpp new file mode 100644 index 00000000..4658125d --- /dev/null +++ b/Utils.cpp @@ -0,0 +1,30 @@ +#include "Utils.h" + +obs_data_array_t* Utils::GetSceneItems(obs_source_t *source) { + obs_data_array *items = obs_data_array_create(); + obs_scene *scene = obs_scene_from_source(source); + + /*obs_scene_item *currentItem = scene->first_item; + while (currentItem != NULL) { + obs_data_array_push_back(items, GetSceneItemData(currentItem)); + currentItem = currentItem->next; + }*/ + + return items; +} + +obs_data_t* Utils::GetSceneItemData(obs_scene_item *item) { + if (!item) { + return NULL; + } + + obs_data_t *data = obs_data_create(); + /*obs_data_set_string(data, "name", obs_source_get_name(item->source)); + obs_data_set_double(data, "x", item->pos.x); + obs_data_set_double(data, "y", item->pos.y); + obs_data_set_double(data, "cx", item->bounds.x); + obs_data_set_double(data, "cy", item->bounds.y); + obs_data_set_bool(data, "render", item->visible);*/ + + return data; +} \ No newline at end of file diff --git a/Utils.h b/Utils.h new file mode 100644 index 00000000..27ac7ae8 --- /dev/null +++ b/Utils.h @@ -0,0 +1,13 @@ +#ifndef UTILS_H +#define UTILS_H + +#include + +class Utils +{ + public: + static obs_data_array_t* GetSceneItems(obs_source_t* source); + static obs_data_t* GetSceneItemData(obs_scene_item *item); +}; + +#endif // UTILS_H \ No newline at end of file diff --git a/WSEvents.cpp b/WSEvents.cpp new file mode 100644 index 00000000..9c0b496d --- /dev/null +++ b/WSEvents.cpp @@ -0,0 +1,182 @@ +#include "WSEvents.h" + +WSEvents::WSEvents(WSServer *server) { + _srv = server; + obs_frontend_add_event_callback(WSEvents::FrontendEventHandler, this); + + QTimer *statusTimer = new QTimer(); + connect(statusTimer, SIGNAL(timeout()), this, SLOT(StreamStatus)); + statusTimer->start(1000); +} + +WSEvents::~WSEvents() { + obs_frontend_remove_event_callback(WSEvents::FrontendEventHandler, this); +} + +void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void *private_data) +{ + WSEvents *owner = static_cast(private_data); + + if (event == OBS_FRONTEND_EVENT_SCENE_CHANGED) { + owner->OnSceneChange(); + } + else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTING) { + owner->OnStreamStarting(); + } + else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTED) { + owner->OnStreamStarted(); + } + else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPING) { + owner->OnStreamStopping(); + } + else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPED) { + owner->OnStreamStopped(); + } + else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTING) { + owner->OnRecordingStarting(); + } + else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTED) { + owner->OnRecordingStarted(); + } + else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTING) { + owner->OnRecordingStarting(); + } + else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPED) { + owner->OnRecordingStopped(); + } +} + +void WSEvents::broadcastUpdate(const char *updateType, obs_data_t *additionalFields = NULL) { + obs_source_t *source = obs_frontend_get_current_scene(); + const char *name = obs_source_get_name(source); + + obs_data_t *update = obs_data_create(); + + obs_data_set_string(update, "update-type", updateType); + if (additionalFields != NULL) { + obs_data_apply(update, additionalFields); + } + + _srv->broadcast(obs_data_get_json(update)); + + obs_data_release(update); +} + +void WSEvents::OnSceneChange() { + // Implements an existing update type from bilhamil's OBS Remote + obs_source_t *source = obs_frontend_get_current_scene(); + const char *name = obs_source_get_name(source); + + obs_data_t *data = obs_data_create(); + obs_data_set_string(data, "scene-name", name); + + broadcastUpdate("SwitchScenes", data); + + obs_data_release(data); +} + +void WSEvents::OnStreamStarting() { + // Implements an existing update type from bilhamil's OBS Remote + obs_data_t *data = obs_data_create(); + obs_data_set_bool(data, "preview-only", false); + + broadcastUpdate("StreamStarting", data); + + obs_data_release(data); +} + +void WSEvents::OnStreamStarted() { + // New update type specific to OBS Studio + _streamStartTime = os_gettime_ns(); + _lastBytesSent = 0; + broadcastUpdate("StreamStarted"); +} + +void WSEvents::OnStreamStopping() { + // Implements an existing update type from bilhamil's OBS Remote + obs_data_t *data = obs_data_create(); + obs_data_set_bool(data, "preview-only", false); + + broadcastUpdate("StreamStopping", data); + + obs_data_release(data); +} + +void WSEvents::OnStreamStopped() { + // New update type specific to OBS Studio + _streamStartTime = 0; + broadcastUpdate("StreamStopped"); +} + +void WSEvents::OnRecordingStarting() { + // New update type specific to OBS Studio + broadcastUpdate("RecordingStarting"); +} + +void WSEvents::OnRecordingStarted() { + // New update type specific to OBS Studio + broadcastUpdate("RecordingStarted"); +} + +void WSEvents::OnRecordingStopping() { + // New update type specific to OBS Studio + broadcastUpdate("RecordingStopping"); +} + +void WSEvents::OnRecordingStopped() { + // New update type specific to OBS Studio + broadcastUpdate("RecordingStopped"); +} + +// TODO : Add a timer to trigger StreamStatus +void WSEvents::StreamStatus() { + blog(LOG_INFO, "top StreamStatus"); + + bool streamingActive = obs_frontend_streaming_active(); + bool recordingActive = obs_frontend_recording_active(); + + obs_output_t *streamOutput = obs_frontend_get_streaming_output(); + + if (!streamOutput) { + blog(LOG_INFO, "not this time. no stream output running."); + return; + } + + uint64_t bytesPerSec = 0; + + uint64_t bytesSent = obs_output_get_total_bytes(streamOutput); + uint64_t bytesSentTime = os_gettime_ns(); + + if (bytesSent < _lastBytesSent) { + bytesSent = 0; + } + if (bytesSent == 0) { + _lastBytesSent = 0; + } + + uint64_t bitsBetween = (bytesSent - _lastBytesSent) * 8; + double timePassed = double(bytesSentTime - _lastBytesSentTime) / 1000000000.0; + + uint64_t bitsPerSec = bitsBetween / timePassed; + bytesPerSec = bitsPerSec / 8; + + uint64_t totalStreamTime = (os_gettime_ns() - _streamStartTime); // TODO : convert to seconds + + uint64_t droppedFrames = obs_output_get_frames_dropped(streamOutput); + uint64_t totalFrames = obs_output_get_total_frames(streamOutput); + + obs_data_t *data = obs_data_create(); + obs_data_set_bool(data, "streaming", streamingActive); + obs_data_set_bool(data, "recording", recordingActive); // New in OBS Studio + obs_data_set_bool(data, "preview-only", false); // Retrocompat with OBSRemote + obs_data_set_int(data, "bytes-per-sec", bytesPerSec); + obs_data_set_double(data, "strain", 0.0); // TODO + obs_data_set_int(data, "total-stream-time", totalStreamTime); + obs_data_set_int(data, "num-total-frames", totalFrames); + obs_data_set_int(data, "num-dropped-frames", droppedFrames); + obs_data_set_double(data, "fps", obs_get_active_fps()); + + broadcastUpdate("StreamStatus", data); + + obs_data_release(data); +} \ No newline at end of file diff --git a/WSEvents.h b/WSEvents.h new file mode 100644 index 00000000..d843877d --- /dev/null +++ b/WSEvents.h @@ -0,0 +1,42 @@ +#ifndef WSEVENTS_H +#define WSEVENTS_H + +#include +#include +#include +#include +#include "WSServer.h" + +class WSEvents : public QObject +{ + Q_OBJECT + + public: + explicit WSEvents(WSServer *server); + ~WSEvents(); + static void FrontendEventHandler(enum obs_frontend_event event, void *private_data); + + private Q_SLOTS: + void StreamStatus(); + + private: + WSServer *_srv; + uint64_t _streamStartTime; + uint64_t _lastBytesSent; + uint64_t _lastBytesSentTime; + void broadcastUpdate(const char *updateType, obs_data_t *additionalFields); + + void OnSceneChange(); + + void OnStreamStarting(); + void OnStreamStarted(); + void OnStreamStopping(); + void OnStreamStopped(); + + void OnRecordingStarting(); + void OnRecordingStarted(); + void OnRecordingStopping(); + void OnRecordingStopped(); +}; + +#endif // WSEVENTS_H \ No newline at end of file diff --git a/WSRequestHandler.cpp b/WSRequestHandler.cpp new file mode 100644 index 00000000..c89772ae --- /dev/null +++ b/WSRequestHandler.cpp @@ -0,0 +1,132 @@ +#include "WSRequestHandler.h" + +WSRequestHandler::WSRequestHandler(QWebSocket *client) { + _client = client; + + messageMap["SetCurrentScene"] = WSRequestHandler::HandleSetCurrentScene; + messageMap["GetCurrentScene"] = WSRequestHandler::HandleGetCurrentScene; + messageMap["GetSceneList"] = WSRequestHandler::ErrNotImplemented; + messageMap["SetSourceOrder"] = WSRequestHandler::ErrNotImplemented; + messageMap["SetSourceRender"] = WSRequestHandler::ErrNotImplemented; + messageMap["SetSceneItemPositionAndSize"] = WSRequestHandler::ErrNotImplemented; + messageMap["GetStreamingStatus"] = WSRequestHandler::HandleGetStreamingStatus; + messageMap["StartStopStreaming"] = WSRequestHandler::HandleStartStopStreaming; + messageMap["StartStopRecording"] = WSRequestHandler::HandleStartStopRecording; + messageMap["ToggleMute"] = WSRequestHandler::ErrNotImplemented; + messageMap["GetVolumes"] = WSRequestHandler::ErrNotImplemented; + messageMap["SetVolume"] = WSRequestHandler::ErrNotImplemented; +} + +void WSRequestHandler::handleMessage(const char *message) { + _requestData = obs_data_create_from_json(message); + if (!_requestData) { + blog(LOG_ERROR, "[obs-websockets] invalid JSON payload for '%s'", message); + SendErrorResponse("invalid JSON payload"); + return; + } + + _requestType = obs_data_get_string(_requestData, "request-type"); + _messageId = obs_data_get_int(_requestData, "message-id"); + + void (*handlerFunc)(WSRequestHandler*) = (messageMap[_requestType]); + + if (handlerFunc != NULL) { + handlerFunc(this); + } + else { + SendErrorResponse("invalid request type"); + } +} + +WSRequestHandler::~WSRequestHandler() { + if (_requestData != NULL) { + obs_data_release(_requestData); + } +} + +void WSRequestHandler::SendOKResponse(obs_data_t *additionalFields) { + obs_data_t *response = obs_data_create(); + obs_data_set_string(response, "status", "ok"); + obs_data_set_int(response, "message-id", _messageId); + + if (additionalFields != NULL) { + obs_data_apply(response, additionalFields); + } + + _client->sendTextMessage(obs_data_get_json(response)); + + obs_data_release(response); +} + +void WSRequestHandler::SendErrorResponse(const char *errorMessage) { + obs_data_t *response = obs_data_create(); + obs_data_set_string(response, "status", "error"); + obs_data_set_string(response, "error", errorMessage); + obs_data_set_int(response, "message-id", _messageId); + + _client->sendTextMessage(obs_data_get_json(response)); + + obs_data_release(response); +} + +void WSRequestHandler::HandleSetCurrentScene(WSRequestHandler *owner) { + const char *sceneName = obs_data_get_string(owner->_requestData, "scene-name"); + obs_source_t *source = obs_get_source_by_name(sceneName); + + if (source) { + obs_frontend_set_current_scene(source); + owner->SendOKResponse(); + } + else { + blog(LOG_ERROR, "[obs-websockets] requested scene '%s' doesn't exist !", sceneName); + owner->SendErrorResponse("requested scene does not exist"); + } +} + +void WSRequestHandler::HandleGetCurrentScene(WSRequestHandler *owner) { + obs_source_t *source = obs_frontend_get_current_scene(); + const char *name = obs_source_get_name(source); + + obs_data_t *data = obs_data_create(); + obs_data_set_string(data, "name", name); + obs_data_set_array(data, "sources", Utils::GetSceneItems(source)); + + owner->SendOKResponse(data); + obs_data_release(data); +} + +void WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler *owner) { + obs_data_t *data = obs_data_create(); + obs_data_set_bool(data, "streaming", obs_frontend_streaming_active()); + obs_data_set_bool(data, "recording", obs_frontend_recording_active()); + obs_data_set_bool(data, "preview-only", false); + + owner->SendOKResponse(data); + obs_data_release(data); +} + +void WSRequestHandler::HandleStartStopStreaming(WSRequestHandler *owner) { + if (obs_frontend_streaming_active()) { + obs_frontend_streaming_stop(); + } + else { + obs_frontend_streaming_start(); + } + + owner->SendOKResponse(); +} + +void WSRequestHandler::HandleStartStopRecording(WSRequestHandler *owner) { + if (obs_frontend_recording_active()) { + obs_frontend_recording_stop(); + } + else { + obs_frontend_recording_start(); + } + + owner->SendOKResponse(); +} + +void WSRequestHandler::ErrNotImplemented(WSRequestHandler *owner) { + owner->SendErrorResponse("not implemented"); +} diff --git a/WSRequestHandler.h b/WSRequestHandler.h new file mode 100644 index 00000000..c7785c72 --- /dev/null +++ b/WSRequestHandler.h @@ -0,0 +1,36 @@ +#ifndef WSREQUESTHANDLER_H +#define WSREQUESTHANDLER_H + +#include +#include +#include +#include "Utils.h" +#include "WSServer.h" + +class WSRequestHandler +{ + public: + explicit WSRequestHandler(QWebSocket *client); + ~WSRequestHandler(); + void handleMessage(const char* message); + + private: + QWebSocket *_client; + long _messageId; + const char *_requestType; + obs_data_t *_requestData; + + std::map messageMap; + + void SendOKResponse(obs_data_t *additionalFields = NULL); + void SendErrorResponse(const char *errorMessage); + + static void ErrNotImplemented(WSRequestHandler *owner); + static void HandleSetCurrentScene(WSRequestHandler *owner); + static void HandleGetCurrentScene(WSRequestHandler *owner); + static void HandleGetStreamingStatus(WSRequestHandler *owner); + static void HandleStartStopStreaming(WSRequestHandler *owner); + static void HandleStartStopRecording(WSRequestHandler *owner); +}; + +#endif // WSPROTOCOL_H \ No newline at end of file diff --git a/WSServer.cpp b/WSServer.cpp index 9893405e..47252dbe 100644 --- a/WSServer.cpp +++ b/WSServer.cpp @@ -1,4 +1,5 @@ #include "WSServer.h" +#include "WSRequestHandler.h" #include #include #include @@ -53,29 +54,9 @@ void WSServer::processTextMessage(QString textMessage) { const char *msg = textMessage.toLocal8Bit(); blog(LOG_INFO, "[obs-websockets] new message : %s", msg); - obs_data_t *request = obs_data_create_from_json(msg); - if (!request) { - blog(LOG_ERROR, "[obs-websockets] invalid JSON payload for '%s'", msg); - } - - const char *requestType = obs_data_get_string(request, "request"); - if (strcmp(requestType, "scene_change") == 0) { - const char *sceneName = obs_data_get_string(request, "switch_to"); - - blog(LOG_INFO, "[obs-websockets] processing scene change request to %s", sceneName); - - obs_source_t *source = obs_get_source_by_name(sceneName); - - if (source) { - obs_frontend_set_current_scene(source); - } - else { - blog(LOG_ERROR, "[obs-websockets] requested scene '%s' doesn't exist !", sceneName); - } - - } - - obs_data_release(request); + WSRequestHandler *handler = new WSRequestHandler(pSender); + handler->handleMessage(msg); + delete handler; } } diff --git a/obs-websocket.cpp b/obs-websocket.cpp index 95d27730..2aff9ff2 100644 --- a/obs-websocket.cpp +++ b/obs-websocket.cpp @@ -1,66 +1,26 @@ #include #include +#include "WSEvents.h" #include "WSServer.h" OBS_DECLARE_MODULE() OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US") +WSEvents *eventHandler; WSServer *server; -void obs_frontend_callback(enum obs_frontend_event event, void *) -{ - bool sendMessage = false; - obs_data_t *announce = obs_data_create(); - - if (event == OBS_FRONTEND_EVENT_SCENE_CHANGED) { - obs_source_t *source = obs_frontend_get_current_scene(); - const char *name = obs_source_get_name(source); - - obs_data_set_string(announce, "type", "scene_changed"); - obs_data_set_string(announce, "name", name); - sendMessage = true; - } - else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTED) { - obs_data_set_string(announce, "type", "streaming_started"); - sendMessage = true; - } - else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPED) { - obs_data_set_string(announce, "type", "streaming_stopped"); - sendMessage = true; - } - else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTED) { - obs_data_set_string(announce, "type", "recording_started"); - sendMessage = true; - } - else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPED) { - obs_data_set_string(announce, "type", "recording_stopped"); - sendMessage = true; - } - else if (event == OBS_FRONTEND_EVENT_EXIT) { - obs_data_set_string(announce, "type", "exiting"); - sendMessage = true; - } - - if (sendMessage && server) { - const char *message = obs_data_get_json(announce); - server->broadcast(message); - } - - obs_data_release(announce); -} - bool obs_module_load(void) { blog(LOG_INFO, "[obs-websockets] you can haz websockets"); server = new WSServer(8080); - obs_frontend_add_event_callback(obs_frontend_callback, nullptr); - + eventHandler = new WSEvents(server); + return true; } void obs_module_unload() { - + blog(LOG_INFO, "[obs-websockets] goodbye !"); }