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)