Request types: get/set/save streaming settings (PR #100)

* Adding support for changing streaming server settings

* Updates after initial code review for customized rtmp settings

* Updating PROTOCOL.MD documentment for streaming service settings

* Changes based on code review
This commit is contained in:
yinzara 2017-07-06 04:07:06 -07:00 committed by Stéphane L
parent acffacd67d
commit e3ad148c15
5 changed files with 311 additions and 7 deletions

View File

@ -104,6 +104,10 @@ The protocol in general is based on the OBS Remote protocol created by Bill Hami
- ["ListSceneCollections"](#listscenecollections)
- ["SetCurrentSceneCollection"](#setcurrentscenecollection)
- ["GetCurrentSceneCollection"](#getcurrentscenecollection)
- **Streaming Server Settings**
- ["GetStreamSettings"](#getstreamsettings)
- ["SetStreamSettings"](#setstreamsettings)
- ["SaveStreamSettings"](#savestreamsettings)
- **Profiles**
- ["ListProfiles"](#listprofiles)
- ["SetCurrentProfile"](#setcurrentprofile)
@ -489,7 +493,9 @@ __Response__ : always OK. No additional fields.
#### "StartStopRecording"
Toggles recording on or off.
__Request fields__ : none
__Request fields__ :
- **"stream"** (object; optional) : See 'stream' parameter in 'StartStreaming'. Ignored if stream is already started.
__Response__ : always OK. No additional fields.
---
@ -497,7 +503,23 @@ __Response__ : always OK. No additional fields.
#### "StartStreaming"
Start streaming.
__Request fields__ : none
__Request fields__ :
- **"stream"** (object; optional) : If specified allows for special configuration of the stream
The 'stream' object has the following fields:
- **"settings"** (object; optional) : The settings for the stream
- **"type"** (string; optional) : If specified ensures the type of the stream matches the given type (usually 'rtmp\_custom' or 'rtmp\_common'). If the currently configured stream type does not match the given stream type, all settings must be specified in the 'settings' object or an error will occur starting the stream.
- **"metadata"** (object; optional) : Adds the given object parameters as encoded query string parameters to the 'key' of the RTMP stream. Used to pass data to the RTMP service about the stream.
The 'settings' object has the following fields:
- **"server"** (string; optional) : The publish URL
- **"key"** (string; optional) : The publish key of the stream
- **"use-auth"** (bool; optional) : should authentication be used when connecting to the streaming server
- **"username"** (string; optional) : if authentication is enabled, the username for access to the streaming server. Ignored if 'use-auth' is not specified as 'true'.
- **"password"** (string; optional) : if authentication is enabled, the password for access to the streaming server. Ignored if 'use-auth' is not specified as 'true'.
The 'metadata' object supports passing any string, numeric or boolean field.
__Response__ : Error if streaming is already active, OK otherwise. No additional fields.
---
@ -746,6 +768,53 @@ __Response__ : OK with these additional fields :
---
#### "GetStreamSettings"
Gets the current streaming server settings
__Request fields__ : none
__Response__ : OK with these additional fields :
- **"type"** (string) : The type of streaming service configuration usually 'rtmp\_custom' or 'rtmp\_common'
- **"settings"** (object) : The actual settings of the stream (i.e. server, key, use-auth, username, password)
The 'settings' object has the following fields however they may vary by 'type':
- **"server"** (string) : The publish URL
- **"key"** (string) : The publish key of the stream
- **"use-auth"** (bool) : should authentication be used when connecting to the streaming server
- **"username"** (string) : if authentication is enabled, the username for access to the streaming server
- **"password"** (string) : if authentication is enabled, the password for access to the streaming server
--
#### "SetStreamSettings"
Sets one or more attributes of the current streaming server settings. Any options not passed will remain unchanged. Returns the updated settings in response.
If 'type' is different than the current streaming service type, all settings are required.
Returns the full settings of the stream (i.e. the same as GetStreamSettings)
__Request fields__ :
- **"type"** (string) : The type of streaming service configuration usually 'rtmp\_custom' or 'rtmp\_common'
- **"settings"** (object) : The actual settings of the stream (i.e. server, key, use-auth, username, password)
- **"save"** (bool) : If specified as true, saves the settings to disk
The 'settings' object has the following fields however they may vary by 'type':
- **"server"** (string; optional) : The publish URL
- **"key"** (string; optional) : The publish key of the stream
- **"use-auth"** (bool; optional) : should authentication be used when connecting to the streaming server
- **"username"** (string; optional) : if authentication is enabled, the username for access to the streaming server
- **"password"** (string; optional) : if authentication is enabled, the password for access to the streaming server
__Response__ : OK with the same fields as the request (except 'save')
---
#### "SaveStreamSettings"
Saves the current streaming server settings to disk
__Request fields__ : none
__Response__ : OK
#### "SetCurrentProfile"
Change the current profile.

View File

@ -20,6 +20,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <obs.hpp>
#include <QMainWindow>
#include <QDir>
#include <QUrl>
#include "Utils.h"
#include "obs-websocket.h"
@ -464,4 +465,58 @@ bool Utils::SetRecordingFolder(const char* path)
config_save(profile);
return true;
}
}
QString* Utils::ParseDataToQueryString(obs_data_t * data)
{
QString* query = nullptr;
if (data)
{
obs_data_item_t* item = obs_data_first(data);
if (item)
{
query = new QString();
bool isFirst = true;
do
{
if (!obs_data_item_has_user_value(item))
continue;
if (!isFirst)
query->append('&');
else
isFirst = false;
const char* attrName = obs_data_item_get_name(item);
query->append(attrName).append("=");
switch (obs_data_item_gettype(item))
{
case OBS_DATA_BOOLEAN:
query->append(obs_data_item_get_bool(item)?"true":"false");
break;
case OBS_DATA_NUMBER:
switch (obs_data_item_numtype(item))
{
case OBS_DATA_NUM_DOUBLE:
query->append(QString::number(obs_data_item_get_double(item)));
break;
case OBS_DATA_NUM_INT:
query->append(QString::number(obs_data_item_get_int(item)));
break;
case OBS_DATA_NUM_INVALID:
break;
}
break;
case OBS_DATA_STRING:
query->append(QUrl::toPercentEncoding(QString(obs_data_item_get_string(item))));
break;
default:
//other types are not supported
break;
}
} while ( obs_data_item_next( &item ) );
}
}
return query;
}

View File

@ -76,6 +76,8 @@ class Utils
static QString FormatIPAddress(QHostAddress &addr);
static const char* GetRecordingFolder();
static bool SetRecordingFolder(const char* path);
static QString* ParseDataToQueryString(obs_data_t * data);
};
#endif // UTILS_H

View File

@ -17,17 +17,22 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include <obs-data.h>
#include "WSRequestHandler.h"
#include "WSEvents.h"
#include "obs-websocket.h"
#include "Config.h"
#include "Utils.h"
#include <qstring.h>
bool str_valid(const char* str)
{
return (str != nullptr && strlen(str) > 0);
}
obs_service_t* WSRequestHandler::_service = nullptr;
WSRequestHandler::WSRequestHandler(QWebSocket* client) :
_messageId(0),
_requestType(""),
@ -81,6 +86,10 @@ WSRequestHandler::WSRequestHandler(QWebSocket* client) :
messageMap["GetCurrentProfile"] = WSRequestHandler::HandleGetCurrentProfile;
messageMap["ListProfiles"] = WSRequestHandler::HandleListProfiles;
messageMap["SetStreamSettings"] = WSRequestHandler::HandleSetStreamSettings;
messageMap["GetStreamSettings"] = WSRequestHandler::HandleGetStreamSettings;
messageMap["SaveStreamSettings"] = WSRequestHandler::HandleSaveStreamSettings;
messageMap["GetStudioModeStatus"] = WSRequestHandler::HandleGetStudioModeStatus;
messageMap["GetPreviewScene"] = WSRequestHandler::HandleGetPreviewScene;
messageMap["SetPreviewScene"] = WSRequestHandler::HandleSetPreviewScene;
@ -91,6 +100,7 @@ WSRequestHandler::WSRequestHandler(QWebSocket* client) :
messageMap["SetTextGDIPlusProperties"] = WSRequestHandler::HandleSetTextGDIPlusProperties;
messageMap["GetTextGDIPlusProperties"] = WSRequestHandler::HandleGetTextGDIPlusProperties;
messageMap["GetBrowserSourceProperties"] = WSRequestHandler::HandleGetBrowserSourceProperties;
messageMap["SetBrowserSourceProperties"] = WSRequestHandler::HandleSetBrowserSourceProperties;
@ -380,11 +390,13 @@ void WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler* req)
void WSRequestHandler::HandleStartStopStreaming(WSRequestHandler* req)
{
if (obs_frontend_streaming_active())
obs_frontend_streaming_stop();
{
HandleStopStreaming(req);
}
else
obs_frontend_streaming_start();
req->SendOKResponse();
{
HandleStartStreaming(req);
}
}
void WSRequestHandler::HandleStartStopRecording(WSRequestHandler* req)
@ -401,8 +413,97 @@ void WSRequestHandler::HandleStartStreaming(WSRequestHandler* req)
{
if (obs_frontend_streaming_active() == false)
{
obs_data_t* streamData = obs_data_get_obj(req->data, "stream");
obs_service_t* currentService = nullptr;
if (streamData)
{
currentService = obs_frontend_get_streaming_service();
obs_service_addref(currentService);
obs_service_t* service = _service;
const char* currentServiceType = obs_service_get_type(currentService);
const char* requestedType = obs_data_has_user_value(streamData, "type") ? obs_data_get_string(streamData, "type") : currentServiceType;
const char* serviceType = service != nullptr ? obs_service_get_type(service) : currentServiceType;
obs_data_t* settings = obs_data_get_obj(streamData, "settings");
obs_data_t* metadata = obs_data_get_obj(streamData, "metadata");
QString* query = Utils::ParseDataToQueryString(metadata);
if (strcmp(requestedType, serviceType) != 0)
{
if (settings)
{
obs_service_release(service);
service = nullptr; //different type so we can't reuse the existing service instance
}
else
{
req->SendErrorResponse("Service type requested does not match currently configured type and no 'settings' were provided");
return;
}
}
else
{
//if type isn't changing we should overlay the settings we got with the existing settings
obs_data_t* existingSettings = obs_service_get_settings(currentService);
obs_data_t* newSettings = obs_data_create(); //by doing this you can send a request to the websocket that only contains a setting you want to change instead of having to do a get and then change them
obs_data_apply(newSettings, existingSettings); //first apply the existing settings
obs_data_apply(newSettings, settings); //then apply the settings from the request should they exist
obs_data_release(settings);
settings = newSettings;
obs_data_release(existingSettings);
}
if (service == nullptr)
{ //create the new custom service setup by the websocket
service = obs_service_create(requestedType, "websocket_custom_service", settings, nullptr);
}
//Supporting adding metadata parameters to key query string
if (query && query->length() > 0) {
const char* key = obs_data_get_string(settings, "key");
int keylen = strlen(key);
bool hasQuestionMark = false;
for (int i = 0; i < keylen; i++) {
if (key[i] == '?') {
hasQuestionMark = true;
break;
}
}
if (hasQuestionMark) {
query->prepend('&');
} else {
query->prepend('?');
}
query->prepend(key);
key = query->toUtf8();
obs_data_set_string(settings, "key", key);
}
obs_service_update(service, settings);
obs_data_release(settings);
obs_data_release(metadata);
_service = service;
obs_frontend_set_streaming_service(_service);
} else if (_service != nullptr) {
obs_service_release(_service);
_service = nullptr;
}
obs_frontend_streaming_start();
if (_service != nullptr) {
obs_frontend_set_streaming_service(currentService);
}
req->SendOKResponse();
obs_service_release(currentService);
}
else
{
@ -909,6 +1010,77 @@ void WSRequestHandler::HandleGetCurrentProfile(WSRequestHandler* req)
obs_data_release(response);
}
void WSRequestHandler::HandleSetStreamSettings(WSRequestHandler* req)
{
obs_service_t* service = obs_frontend_get_streaming_service();
obs_data_t* settings = obs_data_get_obj(req->data, "settings");
if (!settings)
{
req->SendErrorResponse("'settings' are required'");
return;
}
const char* serviceType = obs_service_get_type(service);
const char* requestedType = obs_data_get_string(req->data, "type");
if (requestedType != nullptr && strcmp(requestedType, serviceType) != 0)
{
obs_data_t* hotkeys = obs_hotkeys_save_service(service);
obs_service_release(service);
service = obs_service_create(requestedType, "websocket_custom_service", settings, hotkeys);
obs_data_release(hotkeys);
}
else
{
obs_data_t* existingSettings = obs_service_get_settings(service); //if type isn't changing we should overlay the settings we got with the existing settings
obs_data_t* newSettings = obs_data_create(); //by doing this you can send a request to the websocket that only contains a setting you want to change instead of having to do a get and then change them
obs_data_apply(newSettings, existingSettings); //first apply the existing settings
obs_data_apply(newSettings, settings); //then apply the settings from the request
obs_data_release(settings);
obs_data_release(existingSettings);
obs_service_update(service, settings);
settings = newSettings;
}
if (obs_data_get_bool(req->data, "save")) //if save is specified we should immediately save the streaming service
{
obs_frontend_save_streaming_service();
}
obs_data_t* response = obs_data_create();
obs_data_set_string(response, "type", requestedType);
obs_data_set_obj(response, "settings", settings);
req->SendOKResponse(response);
obs_data_release(settings);
obs_data_release(response);
}
void WSRequestHandler::HandleGetStreamSettings(WSRequestHandler* req)
{
obs_service_t* service = obs_frontend_get_streaming_service();
const char* serviceType = obs_service_get_type(service);
obs_data_t* settings = obs_service_get_settings(service);
obs_data_t* response = obs_data_create();
obs_data_set_string(response, "type", serviceType);
obs_data_set_obj(response, "settings", settings);
req->SendOKResponse(response);
obs_data_release(settings);
obs_data_release(response);
}
void WSRequestHandler::HandleSaveStreamSettings(WSRequestHandler* req)
{
obs_frontend_save_streaming_service();
req->SendOKResponse();
}
void WSRequestHandler::HandleListProfiles(WSRequestHandler* req)
{
obs_data_array_t* profiles = Utils::GetProfiles();

View File

@ -21,6 +21,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#define WSREQUESTHANDLER_H
#include <obs-frontend-api.h>
#include <QtWebSockets/QWebSocket>
#include <QtWebSockets/QWebSocketServer>
@ -35,6 +36,7 @@ class WSRequestHandler : public QObject
bool hasField(const char* name);
private:
static obs_service_t* _service;
QWebSocket* _client;
const char* _messageId;
const char* _requestType;
@ -90,6 +92,10 @@ class WSRequestHandler : public QObject
static void HandleGetCurrentProfile(WSRequestHandler* req);
static void HandleListProfiles(WSRequestHandler* req);
static void HandleSetStreamSettings(WSRequestHandler* req);
static void HandleGetStreamSettings(WSRequestHandler* req);
static void HandleSaveStreamSettings(WSRequestHandler* req);
static void HandleSetTransitionDuration(WSRequestHandler* req);
static void HandleGetTransitionDuration(WSRequestHandler* req);