Merge branch 'preview-api'

This commit is contained in:
Palakis 2017-04-20 22:27:46 +02:00
commit 72ca07f571
7 changed files with 422 additions and 15 deletions

View File

@ -21,6 +21,8 @@ The protocol in general is based on the OBS Remote protocol created by Bill Hami
- ["TransitionDurationChanged"](#transitiondurationchanged)
- ["TransitionListChanged"](#transitionlistchanged)
- ["TransitionBegin"](#transitionbegin)
- ["PreviewSceneChanged"](#previewscenechanged)
- ["StudioModeSwitched"](#studiomodeswitched)
- ["ProfileChanged"](#profilechanged)
- ["ProfileListChanged"](#profilelistchanged)
- ["StreamStarting"](#streamstarting)
@ -43,6 +45,12 @@ The protocol in general is based on the OBS Remote protocol created by Bill Hami
- ["SetCurrentScene"](#setcurrentscene)
- ["GetSceneList"](#getscenelist)
- ["SetSourceRender"](#setsourcerender)
- ["GetStudioModeStatus"](#getstudiomodestatus)
- ["SetPreviewScene"](#setpreviewscene)
- ["TransitionToProgram"](#transitiontoprogram)
- ["EnableStudioMode"](#enablestudiomode)
- ["DisableStudioMode"](#disablestudiomode)
- ["ToggleStudioMode"](#togglestudiomode)
- ["StartStopStreaming"](#startstopstreaming)
- ["StartStopRecording"](#startstoprecording)
- ["StartStreaming"](#startstreaming)
@ -86,6 +94,7 @@ Additional fields will be present in the event message depending on the event ty
#### "SwitchScenes"
OBS is switching to another scene (called at the end of the transition).
- **scene-name** (string) : The name of the scene being switched to.
- **sources** (array of objects) : List of sources composing the scene. Same specification as [`GetCurrentScene`](#getcurrentscene).
---
@ -154,6 +163,19 @@ A transition other than "Cut" has begun.
---
#### "PreviewSceneChanged"
The selected Preview scene changed (only in Studio Mode).
- **scene-name** (string) : Name of the scene being previewed.
- **sources** (array of objects) : List of sources composing the scene. Same specification as [`GetCurrentScene`](#getcurrentscene).
---
#### "StudioModeSwitched"
Studio Mode has been switched on or off.
- **"new-state"** (bool) : new state of Studio Mode: true if enabled, false if disabled.
---
#### "ProfileChanged"
Triggered when switching to another profile or when renaming the current profile.
@ -336,8 +358,73 @@ __Response__ : OK if source exists in the current scene, error otherwise.
---
#### "GetStudioModeStatus"
Tells if Studio Mode is currently enabled or disabled.
__Request fields__ : none
__Response__ : always OK, with these additional fields :
- **"studio-mode"** (bool) : true if OBS is in Studio Mode, false otherwise.
---
#### "GetPreviewScene"
Studio Mode only. Gets the name of the currently Previewed scene, along with a list of its sources.
__Request fields__ : none
__Response__ : OK if Studio Mode is enabled, with the same fields as [`GetCurrentScene`](#getcurrentscene), error otherwise.
---
#### "SetPreviewScene"
Studio Mode only. Sets the specified scene as the Previewed scene in Studio Mode.
__Request fields__ :
- **"scene-name"** (string) : name of the scene to selected as the preview of Studio Mode
__Response__ : OK if Studio Mode is enabled and specified scene exists, error otherwise.
---
#### "TransitionToProgram"
Studio Mode only. Transitions the currently previewed scene to Program (main output).
__Request fields__ :
- **"with-transition" (object, optional) : if specified, use this transition when switching from preview to program. This will change the current transition in the frontend to this one.
__Response__ : OK if studio mode is enabled and optional transition exists, error otherwise.
An object passed as `"with-transition"` in a request must have the following fields :
- **"name"** (string, optional) : transition name
- **"duration"** (integer, optional) : transition duration in milliseconds
---
#### "EnableStudioMode"
Enables Studio Mode.
__Request fields__ : none
__Response__ : always OK. No additional fields.
---
#### "DisableStudioMode"
Disables Studio Mode.
__Request fields__ : none
__Response__ : always OK. No additional fields.
---
#### "ToggleStudioMode"
Toggles Studio Mode on or off.
__Request fields__ : none
__Response__ : always OK. No additional fields.
---
#### "StartStopStreaming"
Toggle streaming on or off.
Toggles streaming on or off.
__Request fields__ : none
__Response__ : always OK. No additional fields.
@ -345,7 +432,7 @@ __Response__ : always OK. No additional fields.
---
#### "StartStopRecording"
Toggle recording on or off.
Toggles recording on or off.
__Request fields__ : none
__Response__ : always OK. No additional fields.

135
Utils.cpp
View File

@ -18,10 +18,13 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Utils.h"
#include <obs-frontend-api.h>
#include <obs.hpp>
#include <QMainWindow>
#include <QSpinBox>
#include <QPainter>
#include "obs-websocket.h"
Q_DECLARE_METATYPE(OBSScene);
obs_data_array_t* string_list_to_array(char** strings, char* key)
{
if (!strings)
@ -238,6 +241,136 @@ void Utils::SetTransitionDuration(int ms)
}
}
bool Utils::SetTransitionByName(const char* transition_name)
{
obs_source_t *transition = GetTransitionFromName(transition_name);
if (transition)
{
obs_frontend_set_current_transition(transition);
obs_source_release(transition);
return true;
}
else
{
return false;
}
}
QPushButton* Utils::GetPreviewModeButtonControl()
{
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
return main->findChild<QPushButton*>("modeSwitch");
}
QListWidget* Utils::GetSceneListControl()
{
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
return main->findChild<QListWidget*>("scenes");
}
obs_scene_t* Utils::SceneListItemToScene(QListWidgetItem* item)
{
if (!item)
return nullptr;
QVariant item_data = item->data(static_cast<int>(Qt::UserRole));
return item_data.value<OBSScene>();
}
QLayout* Utils::GetPreviewLayout()
{
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
return main->findChild<QLayout*>("previewLayout");
}
bool Utils::IsPreviewModeActive()
{
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
// Clue 1 : "Studio Mode" button is toggled on
bool buttonToggledOn = GetPreviewModeButtonControl()->isChecked();
// Clue 2 : Preview layout has more than one item
int previewChildCount = GetPreviewLayout()->count();
blog(LOG_INFO, "preview layout children count : %d", previewChildCount);
return buttonToggledOn || (previewChildCount >= 2);
}
void Utils::EnablePreviewMode()
{
if (!IsPreviewModeActive())
GetPreviewModeButtonControl()->click();
}
void Utils::DisablePreviewMode()
{
if (IsPreviewModeActive())
GetPreviewModeButtonControl()->click();
}
void Utils::TogglePreviewMode()
{
GetPreviewModeButtonControl()->click();
}
obs_scene_t* Utils::GetPreviewScene()
{
if (IsPreviewModeActive())
{
QListWidget* sceneList = GetSceneListControl();
QList<QListWidgetItem*> selected = sceneList->selectedItems();
// Qt::UserRole == QtUserRole::OBSRef
obs_scene_t* scene = Utils::SceneListItemToScene(selected.first());
obs_scene_addref(scene);
return scene;
}
return nullptr;
}
void Utils::SetPreviewScene(const char* name)
{
if (IsPreviewModeActive())
{
QListWidget* sceneList = GetSceneListControl();
QList<QListWidgetItem*> matchingItems = sceneList->findItems(name, Qt::MatchExactly);
if (matchingItems.count() > 0)
sceneList->setCurrentItem(matchingItems.first());
}
}
void Utils::TransitionToProgram()
{
if (!IsPreviewModeActive())
return;
// WARNING : if the layout created in OBS' CreateProgramOptions() changes
// then this won't work as expected
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
// The program options widget is the second item in the left-to-right layout
QWidget* programOptions = GetPreviewLayout()->itemAt(1)->widget();
// The "Transition" button lies in the mainButtonLayout
// which is the first itemin the program options' layout
QLayout* mainButtonLayout = programOptions->layout()->itemAt(1)->layout();
QWidget* transitionBtnWidget = mainButtonLayout->itemAt(0)->widget();
// Try to cast that widget into a button
QPushButton* transitionBtn = qobject_cast<QPushButton*>(transitionBtnWidget);
// Perform a click on that button
transitionBtn->click();
}
const char* Utils::OBSVersionString() {
uint32_t version = obs_get_version();

19
Utils.h
View File

@ -20,6 +20,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#define UTILS_H
#include <QSpinBox>
#include <QPushButton>
#include <QLayout>
#include <QListWidget>
#include <stdio.h>
#include <obs-module.h>
@ -42,6 +45,22 @@ class Utils
static int GetTransitionDuration();
static void SetTransitionDuration(int ms);
static bool SetTransitionByName(const char* transition_name);
static QPushButton* GetPreviewModeButtonControl();
static QLayout* GetPreviewLayout();
static QListWidget* GetSceneListControl();
static obs_scene_t* SceneListItemToScene(QListWidgetItem* item);
static bool IsPreviewModeActive();
static void EnablePreviewMode();
static void DisablePreviewMode();
static void TogglePreviewMode();
static obs_scene_t* GetPreviewScene();
static void SetPreviewScene(const char* name);
static void TransitionToProgram();
static const char* OBSVersionString();
};

View File

@ -19,6 +19,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <util/platform.h>
#include <QTimer>
#include <QPushButton>
#include "Utils.h"
#include "WSEvents.h"
#include "obs-websocket.h"
@ -68,6 +69,12 @@ WSEvents::WSEvents(WSServer *srv)
connect(statusTimer, SIGNAL(timeout()), this, SLOT(StreamStatus()));
statusTimer->start(2000); // equal to frontend's constant BITRATE_UPDATE_SECONDS
QListWidget* sceneList = Utils::GetSceneListControl();
connect(sceneList, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(SelectedSceneChanged(QListWidgetItem*, QListWidgetItem*)));
QPushButton* modeSwitch = Utils::GetPreviewModeButtonControl();
connect(modeSwitch, SIGNAL(clicked(bool)), this, SLOT(ModeSwitchClicked(bool)));
transition_handler = nullptr;
scene_handler = nullptr;
@ -276,14 +283,17 @@ void WSEvents::OnSceneChange()
obs_data_t *data = obs_data_create();
obs_source_t* current_scene = obs_frontend_get_current_scene();
obs_data_array_t* scene_items = Utils::GetSceneItems(current_scene);
connectSceneSignals(current_scene);
obs_data_set_string(data, "scene-name", obs_source_get_name(current_scene));
obs_data_set_array(data, "sources", scene_items);
broadcastUpdate("SwitchScenes", data);
obs_data_release(data);
obs_data_array_release(scene_items);
obs_source_release(current_scene);
obs_data_release(data);
}
void WSEvents::OnSceneListChange()
@ -569,3 +579,34 @@ void WSEvents::OnSceneItemVisibilityChanged(void *param, calldata_t *data)
obs_data_release(fields);
}
void WSEvents::SelectedSceneChanged(QListWidgetItem *current, QListWidgetItem *prev)
{
if (Utils::IsPreviewModeActive())
{
obs_scene_t* scene = Utils::SceneListItemToScene(current);
if (!scene) return;
obs_source_t* scene_source = obs_scene_get_source(scene);
obs_data_array_t* scene_items = Utils::GetSceneItems(scene_source);
obs_data_t* data = obs_data_create();
obs_data_set_string(data, "scene-name", obs_source_get_name(scene_source));
obs_data_set_array(data, "sources", scene_items);
broadcastUpdate("PreviewSceneChanged", data);
obs_data_array_release(scene_items);
obs_data_release(data);
}
}
void WSEvents::ModeSwitchClicked(bool checked)
{
obs_data_t* data = obs_data_create();
obs_data_set_bool(data, "new-state", checked);
broadcastUpdate("StudioModeSwitched", data);
obs_data_release(data);
}

View File

@ -21,6 +21,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#define WSEVENTS_H
#include <obs-frontend-api.h>
#include <QListWidgetItem>
#include "WSServer.h"
class WSEvents : public QObject
@ -41,9 +42,11 @@ class WSEvents : public QObject
const char* GetRecordingTimecode();
private Q_SLOTS:
void deferredInitOperations();
void StreamStatus();
void TransitionDurationChanged(int ms);
void deferredInitOperations();
void SelectedSceneChanged(QListWidgetItem *current, QListWidgetItem *prev);
void ModeSwitchClicked(bool checked);
private:
WSServer *_srv;

View File

@ -71,6 +71,14 @@ WSRequestHandler::WSRequestHandler(QWebSocket *client) :
messageMap["GetCurrentProfile"] = WSRequestHandler::HandleGetCurrentProfile;
messageMap["ListProfiles"] = WSRequestHandler::HandleListProfiles;
messageMap["GetStudioModeStatus"] = WSRequestHandler::HandleGetStudioModeStatus;
messageMap["GetPreviewScene"] = WSRequestHandler::HandleGetPreviewScene;
messageMap["SetPreviewScene"] = WSRequestHandler::HandleSetPreviewScene;
messageMap["TransitionToProgram"] = WSRequestHandler::HandleTransitionToProgram;
messageMap["EnableStudioMode"] = WSRequestHandler::HandleEnableStudioMode;
messageMap["DisableStudioMode"] = WSRequestHandler::HandleDisableStudioMode;
messageMap["ToggleStudioMode"] = WSRequestHandler::HandleToggleStudioMode;
authNotRequired.insert("GetVersion");
authNotRequired.insert("GetAuthRequired");
authNotRequired.insert("Authenticate");
@ -429,20 +437,14 @@ void WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler *owner)
void WSRequestHandler::HandleSetCurrentTransition(WSRequestHandler *owner)
{
const char *name = obs_data_get_string(owner->_requestData, "transition-name");
obs_source_t *transition = Utils::GetTransitionFromName(name);
if (transition)
{
obs_frontend_set_current_transition(transition);
bool success = Utils::SetTransitionByName(name);
if (success)
owner->SendOKResponse();
obs_source_release(transition);
}
else
{
owner->SendErrorResponse("requested transition does not exist");
}
}
void WSRequestHandler::HandleSetTransitionDuration(WSRequestHandler *owner)
{
@ -746,6 +748,120 @@ void WSRequestHandler::HandleListProfiles(WSRequestHandler *owner)
obs_data_array_release(profiles);
}
void WSRequestHandler::HandleGetStudioModeStatus(WSRequestHandler *owner)
{
bool previewActive = Utils::IsPreviewModeActive();
obs_data_t* response = obs_data_create();
obs_data_set_bool(response, "studio-mode", previewActive);
owner->SendOKResponse(response);
obs_data_release(response);
}
void WSRequestHandler::HandleGetPreviewScene(WSRequestHandler *owner)
{
if (!Utils::IsPreviewModeActive())
{
owner->SendErrorResponse("studio mode not enabled");
return;
}
obs_scene_t* preview_scene = Utils::GetPreviewScene();
obs_source_t* source = obs_scene_get_source(preview_scene);
const char *name = obs_source_get_name(source);
obs_data_array_t *scene_items = Utils::GetSceneItems(source);
obs_data_t *data = obs_data_create();
obs_data_set_string(data, "name", name);
obs_data_set_array(data, "sources", scene_items);
owner->SendOKResponse(data);
obs_data_release(data);
obs_data_array_release(scene_items);
obs_scene_release(preview_scene);
}
void WSRequestHandler::HandleSetPreviewScene(WSRequestHandler *owner)
{
if (!Utils::IsPreviewModeActive())
{
owner->SendErrorResponse("studio mode not enabled");
return;
}
if (!obs_data_has_user_value(owner->_requestData, "scene-name"))
{
owner->SendErrorResponse("invalid request parameters");
return;
}
const char* scene_name = obs_data_get_string(owner->_requestData, "scene-name");
Utils::SetPreviewScene(scene_name);
owner->SendOKResponse();
}
void WSRequestHandler::HandleTransitionToProgram(WSRequestHandler *owner)
{
if (!Utils::IsPreviewModeActive())
{
owner->SendErrorResponse("studio mode not enabled");
return;
}
if (obs_data_has_user_value(owner->_requestData, "with-transition"))
{
obs_data_t* transitionInfo = obs_data_get_obj(owner->_requestData, "with-transition");
if (obs_data_has_user_value(transitionInfo, "name"))
{
const char* transitionName = obs_data_get_string(transitionInfo, "name");
bool success = Utils::SetTransitionByName(transitionName);
if (!success)
{
owner->SendErrorResponse("specified transition doesn't exist");
obs_data_release(transitionInfo);
return;
}
}
if (obs_data_has_user_value(transitionInfo, "duration"))
{
int transitionDuration = obs_data_get_int(transitionInfo, "duration");
Utils::SetTransitionDuration(transitionDuration);
}
obs_data_release(transitionInfo);
}
Utils::TransitionToProgram();
owner->SendOKResponse();
}
void WSRequestHandler::HandleEnableStudioMode(WSRequestHandler *owner)
{
Utils::EnablePreviewMode();
owner->SendOKResponse();
}
void WSRequestHandler::HandleDisableStudioMode(WSRequestHandler *owner)
{
Utils::DisablePreviewMode();
owner->SendOKResponse();
}
void WSRequestHandler::HandleToggleStudioMode(WSRequestHandler *owner)
{
Utils::TogglePreviewMode();
owner->SendOKResponse();
}
void WSRequestHandler::ErrNotImplemented(WSRequestHandler *owner)
{
owner->SendErrorResponse("not implemented");

View File

@ -87,6 +87,14 @@ class WSRequestHandler : public QObject
static void HandleSetTransitionDuration(WSRequestHandler *owner);
static void HandleGetTransitionDuration(WSRequestHandler *owner);
static void HandleGetStudioModeStatus(WSRequestHandler *owner);
static void HandleGetPreviewScene(WSRequestHandler *owner);
static void HandleSetPreviewScene(WSRequestHandler *owner);
static void HandleTransitionToProgram(WSRequestHandler *owner);
static void HandleEnableStudioMode(WSRequestHandler *owner);
static void HandleDisableStudioMode(WSRequestHandler *owner);
static void HandleToggleStudioMode(WSRequestHandler *owner);
};
#endif // WSPROTOCOL_H