diff --git a/PROTOCOL.md b/PROTOCOL.md index a5f519f5..d982d48f 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -11,8 +11,12 @@ The protocol in general is based on the OBS Remote protocol created by Bill Hami - [Event Types](#event-types) - ["SwitchScenes"](#switchscenes) - ["ScenesChanged"](#sceneschanged) + - ["SceneCollectionChanged"](#scenecollectionchanged) + - ["SceneCollectionListChanged"](#scenecollectionlistchanged) - ["SwitchTransition"](#switchtransition) - ["TransitionListChanged"](#transitionlistchanged) + - ["ProfileChanged"](#profilechanged) + - ["ProfileListChanged"](#profilelistchanged) - ["StreamStarting"](#streamstarting) - ["StreamStarted"](#streamstarted) - ["StreamStopping"](#streamstopping) @@ -45,6 +49,12 @@ The protocol in general is based on the OBS Remote protocol created by Bill Hami - ["ToggleMute"](#togglemute) - ["SetSceneItemPosition"](#setsceneitemposition) - ["SetSceneItemTransform"](#setsceneitemtransform) + - ["SetCurrentSceneCollection"](#setcurrentscenecollection) + - ["GetCurrentSceneCollection"](#getcurrentscenecollection) + - ["ListSceneCollections"](#listscenecollections) + - ["SetCurrentProfile"](#setcurrentprofile) + - ["GetCurrentProfile"](#getcurrentprofile) + - ["ListProfiles"](#listprofiles) * [Authentication](#authentication) ## Events @@ -67,6 +77,16 @@ The scene list has been modified (Scenes have been added, removed, or renamed). --- +#### "SceneCollectionChanged" +Triggered when switching to another scene collection or when renaming the current scene collection. + +--- + +#### "SceneCollectionListChanged" +Triggered when a scene collection is created, added, renamed or removed. + +--- + #### "SwitchTransition" The active transition has been changed. - **transition-name** (string) : The name of the active transition. @@ -78,6 +98,16 @@ The list of available transitions has been modified (Transitions have been added --- +#### "ProfileChanged" +Triggered when switching to another profile or when renaming the current profile. + +--- + +#### "ProfileListChanged" +Triggered when a profile is created, added, renamed or removed. + +--- + #### "StreamStarting" A request to start streaming has been issued. - **preview-only** (bool) : Always false. @@ -390,6 +420,66 @@ __Response__ : OK if specified item exists, error otherwise. --- +#### "SetCurrentSceneCollection" +Change the current scene collection. + +__Request fields__ : +- **"sc-name"** (string) : name of the desired scene collection + +__Response__ : OK if scene collection exists, error otherwise. + +--- + +#### "GetCurrentSceneCollection" +Get the name of the current scene collection. + +__Request fields__ : none + +__Response__ : OK with these additional fields : +- **"sc-name"** (string) : name of the current scene collection + +--- + +#### "ListSceneCollections" +Get a list of available scene collections. + +__Request fields__ : none + +__Response__ : OK with these additional fields : +- **"scene-collections"** (array of objects) : names of available scene collections + +--- + +#### "SetCurrentProfile" +Change the current profile. + +__Request fields__ : +- **"profile-name"** (string) : name of the desired profile + +__Response__ : OK if profile exists, error otherwise. + +--- + +#### "GetCurrentProfile" +Get the name of the current profile. + +__Request fields__ : none + +__Response__ : OK with these additional fields : +- **"profile-name"** (string) : name of the current profile + +--- + +#### "ListProfiles" +Get a list of available profiles. + +__Request fields__ : none + +__Response__ : OK with the additional fields : +- **"profiles"** (array of objects) : names of available profiles + +--- + ### Authentication A call to `GetAuthRequired` gives the client two elements : - A challenge : a random string that will be used to generate the auth response diff --git a/Utils.cpp b/Utils.cpp index a35a7502..201f7d85 100644 --- a/Utils.cpp +++ b/Utils.cpp @@ -18,6 +18,31 @@ with this program. If not, see #include "Utils.h" #include +#include "obs-websocket.h" + +obs_data_array_t* string_list_to_array(char** strings, char* key) +{ + if (!strings) + return obs_data_array_create(); + + obs_data_array_t *list = obs_data_array_create(); + + char* value = ""; + for (int i = 0; value != nullptr; i++) + { + value = strings[i]; + + obs_data_t *item = obs_data_create(); + obs_data_set_string(item, key, value); + + if (value) + obs_data_array_push_back(list, item); + + obs_data_release(item); + } + + return list; +} obs_data_array_t* Utils::GetSceneItems(obs_source_t *source) { obs_data_array_t *items = obs_data_array_create(); @@ -152,6 +177,24 @@ obs_data_t* Utils::GetSceneData(obs_source *source) { return sceneData; } +obs_data_array_t* Utils::GetSceneCollections() +{ + char** scene_collections = obs_frontend_get_scene_collections(); + obs_data_array_t *list = string_list_to_array(scene_collections, "sc-name"); + + bfree(scene_collections); + return list; +} + +obs_data_array_t* Utils::GetProfiles() +{ + char** profiles = obs_frontend_get_profiles(); + obs_data_array_t *list = string_list_to_array(profiles, "profile-name"); + + bfree(profiles); + return list; +} + const char* Utils::OBSVersionString() { uint32_t version = obs_get_version(); diff --git a/Utils.h b/Utils.h index 7fbcd963..54f38df4 100644 --- a/Utils.h +++ b/Utils.h @@ -33,6 +33,9 @@ class Utils static obs_data_array_t* GetScenes(); static obs_data_t* GetSceneData(obs_source *source); + static obs_data_array_t* GetSceneCollections(); + static obs_data_array_t* GetProfiles(); + static const char* OBSVersionString(); }; diff --git a/WSEvents.cpp b/WSEvents.cpp index 0bce95e1..7a4f6af1 100644 --- a/WSEvents.cpp +++ b/WSEvents.cpp @@ -22,7 +22,8 @@ with this program. If not, see #include "WSEvents.h" -WSEvents::WSEvents(WSServer *srv) { +WSEvents::WSEvents(WSServer *srv) +{ _srv = srv; obs_frontend_add_event_callback(WSEvents::FrontendEventHandler, this); @@ -34,7 +35,8 @@ WSEvents::WSEvents(WSServer *srv) { _rec_starttime = 0; } -WSEvents::~WSEvents() { +WSEvents::~WSEvents() +{ obs_frontend_remove_event_callback(WSEvents::FrontendEventHandler, this); } @@ -47,49 +49,78 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void *private // TODO : implement SourceChanged, SourceOrderChanged and RepopulateSources - if (event == OBS_FRONTEND_EVENT_SCENE_CHANGED) { + if (event == OBS_FRONTEND_EVENT_SCENE_CHANGED) + { owner->OnSceneChange(); } - else if (event == OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED) { + else if (event == OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED) + { owner->OnSceneListChange(); } - else if (event == OBS_FRONTEND_EVENT_TRANSITION_CHANGED) { + else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED) + { + owner->OnSceneCollectionChange(); + } + else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED) + { + owner->OnSceneCollectionListChange(); + } + else if (event == OBS_FRONTEND_EVENT_TRANSITION_CHANGED) + { owner->OnTransitionChange(); } - else if (event == OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED) { + else if (event == OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED) + { owner->OnTransitionListChange(); } - else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTING) { + else if (event == OBS_FRONTEND_EVENT_PROFILE_CHANGED) + { + owner->OnProfileChange(); + } + else if (event == OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED) + { + owner->OnProfileListChange(); + } + else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTING) + { owner->OnStreamStarting(); } - else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTED) { + else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTED) + { owner->OnStreamStarted(); } - else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPING) { + else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPING) + { owner->OnStreamStopping(); } - else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPED) { + else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPED) + { owner->OnStreamStopped(); } - else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTING) { + else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTING) + { owner->OnRecordingStarting(); } - else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTED) { + else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTED) + { owner->OnRecordingStarted(); } - else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPING) { + else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPING) + { owner->OnRecordingStopping(); } - else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPED) { + else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPED) + { owner->OnRecordingStopped(); } - else if (event == OBS_FRONTEND_EVENT_EXIT) { - obs_frontend_save(); + else if (event == OBS_FRONTEND_EVENT_EXIT) + { owner->OnExit(); } } -void WSEvents::broadcastUpdate(const char *updateType, obs_data_t *additionalFields = NULL) { +void WSEvents::broadcastUpdate(const char *updateType, obs_data_t *additionalFields = NULL) +{ obs_data_t *update = obs_data_create(); obs_data_set_string(update, "update-type", updateType); @@ -102,19 +133,22 @@ void WSEvents::broadcastUpdate(const char *updateType, obs_data_t *additionalFie obs_data_release(update); } -void WSEvents::OnSceneChange() { +void WSEvents::OnSceneChange() +{ // Implements an existing update type from bilhamil's OBS Remote // Default behavior : get the new scene from the running transition obs_source_t *transition = obs_frontend_get_current_transition(); obs_source_t *new_scene = obs_transition_get_source(transition, OBS_TRANSITION_SOURCE_B); - if (!new_scene) { + if (!new_scene) + { obs_source_release(transition); return; } const char *scene_name = obs_source_get_name(new_scene); - if (!scene_name) { + if (!scene_name) + { // Fallback behaviour : get the new scene straight from the API obs_source_release(new_scene); new_scene = obs_frontend_get_current_scene(); @@ -134,11 +168,23 @@ void WSEvents::OnSceneChange() { obs_source_release(transition); } -void WSEvents::OnSceneListChange() { +void WSEvents::OnSceneListChange() +{ broadcastUpdate("ScenesChanged"); } -void WSEvents::OnTransitionChange() { +void WSEvents::OnSceneCollectionChange() +{ + broadcastUpdate("SceneCollectionChanged"); +} + +void WSEvents::OnSceneCollectionListChange() +{ + broadcastUpdate("SceneCollectionListChanged"); +} + +void WSEvents::OnTransitionChange() +{ obs_source_t *transition = obs_frontend_get_current_transition(); const char *transition_name = obs_source_get_name(transition); @@ -151,11 +197,23 @@ void WSEvents::OnTransitionChange() { obs_source_release(transition); } -void WSEvents::OnTransitionListChange() { +void WSEvents::OnTransitionListChange() +{ broadcastUpdate("TransitionListChanged"); } -void WSEvents::OnStreamStarting() { +void WSEvents::OnProfileChange() +{ + broadcastUpdate("ProfileChanged"); +} + +void WSEvents::OnProfileListChange() +{ + broadcastUpdate("ProfileListChanged"); +} + +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); @@ -165,14 +223,16 @@ void WSEvents::OnStreamStarting() { obs_data_release(data); } -void WSEvents::OnStreamStarted() { +void WSEvents::OnStreamStarted() +{ // New update type specific to OBS Studio _stream_starttime = os_gettime_ns(); _lastBytesSent = 0; broadcastUpdate("StreamStarted"); } -void WSEvents::OnStreamStopping() { +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); @@ -182,40 +242,47 @@ void WSEvents::OnStreamStopping() { obs_data_release(data); } -void WSEvents::OnStreamStopped() { +void WSEvents::OnStreamStopped() +{ // New update type specific to OBS Studio _stream_starttime = 0; broadcastUpdate("StreamStopped"); } -void WSEvents::OnRecordingStarting() { +void WSEvents::OnRecordingStarting() +{ // New update type specific to OBS Studio broadcastUpdate("RecordingStarting"); } -void WSEvents::OnRecordingStarted() { +void WSEvents::OnRecordingStarted() +{ // New update type specific to OBS Studio _rec_starttime = os_gettime_ns(); broadcastUpdate("RecordingStarted"); } -void WSEvents::OnRecordingStopping() { +void WSEvents::OnRecordingStopping() +{ // New update type specific to OBS Studio broadcastUpdate("RecordingStopping"); } -void WSEvents::OnRecordingStopped() { +void WSEvents::OnRecordingStopped() +{ // New update type specific to OBS Studio _rec_starttime = 0; broadcastUpdate("RecordingStopped"); } -void WSEvents::OnExit() { +void WSEvents::OnExit() +{ // New update type specific to OBS Studio broadcastUpdate("Exiting"); } -void WSEvents::StreamStatus() { +void WSEvents::StreamStatus() +{ bool streaming_active = obs_frontend_streaming_active(); bool recording_active = obs_frontend_recording_active(); diff --git a/WSEvents.h b/WSEvents.h index d3e210b7..38efd7da 100644 --- a/WSEvents.h +++ b/WSEvents.h @@ -46,10 +46,15 @@ class WSEvents : public QObject void OnSceneChange(); void OnSceneListChange(); + void OnSceneCollectionChange(); + void OnSceneCollectionListChange(); void OnTransitionChange(); void OnTransitionListChange(); + void OnProfileChange(); + void OnProfileListChange(); + void OnStreamStarting(); void OnStreamStarted(); void OnStreamStopping(); diff --git a/WSRequestHandler.cpp b/WSRequestHandler.cpp index 241231ff..b7921889 100644 --- a/WSRequestHandler.cpp +++ b/WSRequestHandler.cpp @@ -564,7 +564,15 @@ void WSRequestHandler::HandleGetCurrentSceneCollection(WSRequestHandler *owner) void WSRequestHandler::HandleListSceneCollections(WSRequestHandler *owner) { - // TODO + obs_data_array_t *scene_collections = Utils::GetSceneCollections(); + + obs_data_t *response = obs_data_create(); + obs_data_set_array(response, "scene-collections", scene_collections); + + owner->SendOKResponse(response); + + obs_data_release(response); + obs_data_array_release(scene_collections); } void WSRequestHandler::HandleSetCurrentProfile(WSRequestHandler *owner) @@ -595,7 +603,15 @@ void WSRequestHandler::HandleGetCurrentProfile(WSRequestHandler *owner) void WSRequestHandler::HandleListProfiles(WSRequestHandler *owner) { - // TODO + obs_data_array_t *profiles = Utils::GetProfiles(); + + obs_data_t *response = obs_data_create(); + obs_data_set_array(response, "profiles", profiles); + + owner->SendOKResponse(response); + + obs_data_release(response); + obs_data_array_release(profiles); } void WSRequestHandler::ErrNotImplemented(WSRequestHandler *owner)