mirror of
https://github.com/Palakis/obs-websocket.git
synced 2024-08-30 18:12:16 +00:00
Compare commits
106 Commits
Author | SHA1 | Date | |
---|---|---|---|
0672b6a157 | |||
1e71bfa151 | |||
d4c2c8197a | |||
c9baed2df9 | |||
1e2065c84a | |||
537d683dfb | |||
e6f1b9f8c8 | |||
5815d2bfce | |||
7692e93306 | |||
8768b83251 | |||
35d18810fc | |||
ccd40a1834 | |||
5c1f0c3541 | |||
e237e52ae4 | |||
effec90528 | |||
de4a2247c0 | |||
b20b7cbc98 | |||
3c347b9b77 | |||
794c4066d8 | |||
8c8e3072a7 | |||
228708eec4 | |||
dcafaedaa8 | |||
a85297f2c8 | |||
91f7450cbd | |||
e7b074991d | |||
5f83ce2a28 | |||
44af896dee | |||
9eaa9a98ee | |||
629880cd58 | |||
42266ed14f | |||
9dd7a197e4 | |||
6f39da20a9 | |||
1ebb6f9257 | |||
e30e982ef0 | |||
42a80c6185 | |||
0d495f4d65 | |||
71f5e66bd1 | |||
a6f71b68f3 | |||
a527f343cd | |||
f8e1c454d9 | |||
da6e55f09f | |||
d277e2788a | |||
53ba747b78 | |||
b9862acd1d | |||
d1c19382a1 | |||
4141983ccd | |||
2b8b5001e0 | |||
532126561e | |||
3c026c4eef | |||
ae6c15158f | |||
6aa57247f9 | |||
50862ac945 | |||
01ce6faa20 | |||
6e571aef95 | |||
d6091c83e2 | |||
5ca55fc13e | |||
2b60da02e8 | |||
f02297152c | |||
af7c0bbc72 | |||
40727d5a6c | |||
9bdd73dbc6 | |||
3e1ed09f12 | |||
3682c625d7 | |||
70cd52ac95 | |||
3264da4b2f | |||
06c1648f55 | |||
94c3e5d41d | |||
7ae20d8c3b | |||
0ff4411abf | |||
3d68b7c9e5 | |||
f8b1cae0c9 | |||
5bee0fc453 | |||
7162765824 | |||
99aa6be887 | |||
afaaff298f | |||
68cf9af6a3 | |||
8d5752d6b5 | |||
98dbcc4c69 | |||
687d8fd120 | |||
114ace23f7 | |||
3fbc221db0 | |||
f2e6e137a6 | |||
bb232f1b3e | |||
9dc153bc22 | |||
b9bbdf5978 | |||
e0db0e394d | |||
3e9001721e | |||
bbf3b0f86f | |||
7f3eb9f11b | |||
c783c51915 | |||
0816d222c6 | |||
af16c70143 | |||
748b6f6e2e | |||
3bd600ed52 | |||
78e6ad0f59 | |||
2d71dc68f1 | |||
7dc2a00d47 | |||
7ece78a05b | |||
ff2bace1bf | |||
e63ce01bdc | |||
4439ce71d0 | |||
27ec094775 | |||
1bed53e07b | |||
8ae06c0c4f | |||
372db7865b | |||
cfeffc551e |
11
.travis.yml
Normal file
11
.travis.yml
Normal file
@ -0,0 +1,11 @@
|
||||
language: cpp
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
dist: trusty
|
||||
sudo: required
|
||||
before_install: "./CI/install-dependencies-linux.sh"
|
||||
before_script: "./CI/before-script-linux.sh"
|
||||
|
||||
script: cd ./build && make -j4 && cd -
|
6
CI/before-script-linux.sh
Executable file
6
CI/before-script-linux.sh
Executable file
@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
|
||||
mkdir build && cd build
|
||||
cmake -DLIBOBS_INCLUDE_DIR="../../obs-studio/libobs" -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
make -j4
|
61
CI/install-dependencies-linux.sh
Executable file
61
CI/install-dependencies-linux.sh
Executable file
@ -0,0 +1,61 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
|
||||
# OBS Studio deps
|
||||
sudo add-apt-repository ppa:kirillshkrogalev/ffmpeg-next -y
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get install -y \
|
||||
build-essential \
|
||||
checkinstall \
|
||||
cmake \
|
||||
libasound2-dev \
|
||||
libavcodec-ffmpeg-dev \
|
||||
libavdevice-ffmpeg-dev \
|
||||
libavfilter-ffmpeg-dev \
|
||||
libavformat-ffmpeg-dev \
|
||||
libavutil-ffmpeg-dev \
|
||||
libcurl4-openssl-dev \
|
||||
libfontconfig-dev \
|
||||
libfreetype6-dev \
|
||||
libgl1-mesa-dev \
|
||||
libjack-jackd2-dev \
|
||||
libjansson-dev \
|
||||
libpulse-dev \
|
||||
libqt5x11extras5-dev \
|
||||
libspeexdsp-dev \
|
||||
libswresample-ffmpeg-dev \
|
||||
libswscale-ffmpeg-dev \
|
||||
libudev-dev \
|
||||
libv4l-dev \
|
||||
libvlc-dev \
|
||||
libx11-dev \
|
||||
libx264-dev \
|
||||
libxcb-shm0-dev \
|
||||
libxcb-xinerama0-dev \
|
||||
libxcomposite-dev \
|
||||
libxinerama-dev \
|
||||
pkg-config \
|
||||
qtbase5-dev
|
||||
|
||||
# qtwebsockets deps
|
||||
sudo apt-get install -y qt5-qmake
|
||||
|
||||
# obs-websocket deps
|
||||
cd ..
|
||||
git clone https://github.com/qt/qtwebsockets/ ./qtwebsockets
|
||||
cd qtwebsockets
|
||||
git checkout v5.3.0
|
||||
qmake
|
||||
make -j4
|
||||
sudo make install
|
||||
|
||||
# Build obs-studio
|
||||
cd ..
|
||||
git clone https://github.com/jp9000/obs-studio ./obs-studio
|
||||
cd obs-studio
|
||||
mkdir build && cd build
|
||||
cmake -DUNIX_STRUCTURE=1 -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
make -j4
|
||||
sudo make install
|
||||
|
||||
sudo ldconfig
|
@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.2)
|
||||
project(obs-websocket)
|
||||
|
||||
set(CMAKE_PREFIX_PATH "${QTDIR}")
|
||||
@ -13,7 +13,7 @@ find_package(Qt5WebSockets REQUIRED)
|
||||
find_package(Qt5Widgets REQUIRED)
|
||||
|
||||
add_subdirectory(deps/mbedtls EXCLUDE_FROM_ALL)
|
||||
set(ENABLED_PROGRAMS false)
|
||||
set(ENABLE_PROGRAMS false)
|
||||
|
||||
set(obs-websocket_SOURCES
|
||||
obs-websocket.cpp
|
||||
@ -33,10 +33,13 @@ set(obs-websocket_HEADERS
|
||||
Utils.h
|
||||
forms/settings-dialog.h)
|
||||
|
||||
# --- Platform-independent build settings ---
|
||||
add_library(obs-websocket MODULE
|
||||
${obs-websocket_SOURCES}
|
||||
${obs-websocket_HEADERS})
|
||||
|
||||
add_dependencies(obs-websocket mbedcrypto)
|
||||
|
||||
include_directories(
|
||||
"${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api"
|
||||
${Qt5Core_INCLUDES}
|
||||
@ -44,29 +47,72 @@ include_directories(
|
||||
${Qt5Widgets_INCLUDES}
|
||||
${mbedcrypto_INCLUDES}
|
||||
"${CMAKE_SOURCE_DIR}/deps/mbedtls/include")
|
||||
|
||||
target_link_libraries(obs-websocket
|
||||
libobs
|
||||
Qt5::Core
|
||||
Qt5::WebSockets
|
||||
Qt5::Widgets
|
||||
mbedcrypto)
|
||||
# --- End of section ---
|
||||
|
||||
# --- Windows-specific build settings and tasks ---
|
||||
if(WIN32)
|
||||
if(NOT DEFINED OBS_FRONTEND_LIB)
|
||||
set(OBS_FRONTEND_LIB "OBS_FRONTEND_LIB-NOTFOUND" CACHE FILEPATH "OBS frontend library")
|
||||
if(OBS_FRONTEND_LIB EQUAL "OBS_FRONTEND_LIB-NOTFOUND")
|
||||
message(FATAL_ERROR "Could not find OBS Frontend API\'s library !")
|
||||
endif()
|
||||
|
||||
target_link_libraries(obs-websocket
|
||||
"${OBS_FRONTEND_LIB}")
|
||||
|
||||
add_custom_command(TARGET obs-websocket POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy
|
||||
COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy
|
||||
"${QTDIR}/bin/Qt5WebSockets.dll"
|
||||
"${QTDIR}/bin/Qt5Network.dll"
|
||||
"${CMAKE_BINARY_DIR}/$<CONFIG>")
|
||||
|
||||
COMMAND if $<CONFIG:Debug>==1 ("${CMAKE_COMMAND}" -E copy
|
||||
"${QTDIR}/bin/Qt5WebSocketsd.dll"
|
||||
"${QTDIR}/bin/Qt5Networkd.dll"
|
||||
"${CMAKE_BINARY_DIR}/$<CONFIG>")
|
||||
)
|
||||
|
||||
# --- Release package helper ---
|
||||
# The "release" folder has a structure similar OBS' one on Windows
|
||||
set(RELEASE_DIR "${PROJECT_SOURCE_DIR}/release")
|
||||
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(ARCH_NAME "64bit")
|
||||
else()
|
||||
set(ARCH_NAME "32bit")
|
||||
endif()
|
||||
|
||||
add_custom_command(TARGET obs-websocket POST_BUILD
|
||||
COMMAND if $<CONFIG:Release>==1 (
|
||||
"${CMAKE_COMMAND}" -E make_directory
|
||||
"${RELEASE_DIR}/data/obs-plugins/obs-websocket"
|
||||
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
|
||||
|
||||
COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy_directory
|
||||
"${PROJECT_SOURCE_DIR}/data"
|
||||
"${RELEASE_DIR}/data/obs-plugins/obs-websocket")
|
||||
|
||||
COMMAND if $<CONFIG:Release>==1 ("${CMAKE_COMMAND}" -E copy
|
||||
"$<TARGET_FILE:obs-websocket>"
|
||||
"${QTDIR}/bin/Qt5WebSockets.dll"
|
||||
"${QTDIR}/bin/Qt5Network.dll"
|
||||
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
|
||||
)
|
||||
# --- End of sub-section ---
|
||||
|
||||
endif()
|
||||
# --- End of section ---
|
||||
|
||||
# --- Linux-specific build settings and tasks ---
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
|
||||
target_compile_options(mbedcrypto PRIVATE -fPIC)
|
||||
set_target_properties(obs-websocket PROPERTIES PREFIX "")
|
||||
target_link_libraries(obs-websocket
|
||||
@ -74,6 +120,9 @@ if(UNIX AND NOT APPLE)
|
||||
|
||||
install(TARGETS obs-websocket
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/obs-plugins)
|
||||
install(FILES data/locale/en-US.ini data/locale/fr-FR.ini
|
||||
install(FILES data/locale/en-US.ini data/locale/fr-FR.ini data/locale/de-DE.ini
|
||||
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/obs/obs-plugins/obs-websocket/locale")
|
||||
endif()
|
||||
# --- End of section ---
|
||||
|
||||
# TODO : OS X build settings and tasks
|
||||
|
99
Config.cpp
99
Config.cpp
@ -1,6 +1,6 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -19,22 +19,42 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include <mbedtls/base64.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <util/config-file.h>
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
#define CONFIG_SECTION_NAME "obs-websocket"
|
||||
#define CONFIG_PARAM_SECRET "auth_hash"
|
||||
#define CONFIG_PARAM_SALT "auth_salt"
|
||||
#define CONFIG_PARAM_AUTHREQUIRED "auth_required"
|
||||
#define SECTION_NAME "WebsocketAPI"
|
||||
#define PARAM_ENABLE "ServerEnabled"
|
||||
#define PARAM_PORT "ServerPort"
|
||||
#define PARAM_AUTHREQUIRED "AuthRequired"
|
||||
#define PARAM_SECRET "AuthSecret"
|
||||
#define PARAM_SALT "AuthSalt"
|
||||
|
||||
Config *Config::_instance = new Config();
|
||||
|
||||
Config::Config() {
|
||||
Config::Config()
|
||||
{
|
||||
// Default settings
|
||||
ServerEnabled = true;
|
||||
ServerPort = 4444;
|
||||
|
||||
AuthRequired = false;
|
||||
Secret = "";
|
||||
Salt = "";
|
||||
SettingsLoaded = false;
|
||||
|
||||
// OBS Config defaults
|
||||
config_t* obs_config = obs_frontend_get_global_config();
|
||||
if (obs_config)
|
||||
{
|
||||
config_set_default_bool(obs_config, SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||
config_set_default_uint(obs_config, SECTION_NAME, PARAM_PORT, ServerPort);
|
||||
|
||||
config_set_default_bool(obs_config, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
|
||||
config_set_default_string(obs_config, SECTION_NAME, PARAM_SECRET, Secret);
|
||||
config_set_default_string(obs_config, SECTION_NAME, PARAM_SALT, Salt);
|
||||
}
|
||||
|
||||
mbedtls_entropy_init(&entropy);
|
||||
mbedtls_ctr_drbg_init(&rng);
|
||||
mbedtls_ctr_drbg_seed(&rng, mbedtls_entropy_func, &entropy, nullptr, 0);
|
||||
@ -43,12 +63,40 @@ Config::Config() {
|
||||
SessionChallenge = GenerateSalt();
|
||||
}
|
||||
|
||||
Config::~Config() {
|
||||
Config::~Config()
|
||||
{
|
||||
mbedtls_ctr_drbg_free(&rng);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
}
|
||||
|
||||
const char* Config::GenerateSalt() {
|
||||
void Config::Load()
|
||||
{
|
||||
config_t* obs_config = obs_frontend_get_global_config();
|
||||
|
||||
ServerEnabled = config_get_bool(obs_config, SECTION_NAME, PARAM_ENABLE);
|
||||
ServerPort = config_get_uint(obs_config, SECTION_NAME, PARAM_PORT);
|
||||
|
||||
AuthRequired = config_get_bool(obs_config, SECTION_NAME, PARAM_AUTHREQUIRED);
|
||||
Secret = config_get_string(obs_config, SECTION_NAME, PARAM_SECRET);
|
||||
Salt = config_get_string(obs_config, SECTION_NAME, PARAM_SALT);
|
||||
}
|
||||
|
||||
void Config::Save()
|
||||
{
|
||||
config_t* obs_config = obs_frontend_get_global_config();
|
||||
|
||||
config_set_bool(obs_config, SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||
config_set_uint(obs_config, SECTION_NAME, PARAM_PORT, ServerPort);
|
||||
|
||||
config_set_bool(obs_config, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
|
||||
config_set_string(obs_config, SECTION_NAME, PARAM_SECRET, Secret);
|
||||
config_set_string(obs_config, SECTION_NAME, PARAM_SALT, Salt);
|
||||
|
||||
config_save(obs_config);
|
||||
}
|
||||
|
||||
const char* Config::GenerateSalt()
|
||||
{
|
||||
// Generate 32 random chars
|
||||
unsigned char *random_chars = (unsigned char *)bzalloc(32);
|
||||
mbedtls_ctr_drbg_random(&rng, random_chars, 32);
|
||||
@ -63,7 +111,8 @@ const char* Config::GenerateSalt() {
|
||||
return (char *)salt;
|
||||
}
|
||||
|
||||
const char* Config::GenerateSecret(const char *password, const char *salt) {
|
||||
const char* Config::GenerateSecret(const char *password, const char *salt)
|
||||
{
|
||||
size_t passwordLength = strlen(password);
|
||||
size_t saltLength = strlen(salt);
|
||||
|
||||
@ -88,7 +137,8 @@ const char* Config::GenerateSecret(const char *password, const char *salt) {
|
||||
return (char*)challenge;
|
||||
}
|
||||
|
||||
void Config::SetPassword(const char *password) {
|
||||
void Config::SetPassword(const char *password)
|
||||
{
|
||||
const char *new_salt = GenerateSalt();
|
||||
const char *new_challenge = GenerateSecret(password, new_salt);
|
||||
|
||||
@ -96,7 +146,8 @@ void Config::SetPassword(const char *password) {
|
||||
this->Secret = new_challenge;
|
||||
}
|
||||
|
||||
bool Config::CheckAuth(const char *response) {
|
||||
bool Config::CheckAuth(const char *response)
|
||||
{
|
||||
size_t secretLength = strlen(this->Secret);
|
||||
size_t sessChallengeLength = strlen(this->SessionChallenge);
|
||||
|
||||
@ -125,29 +176,7 @@ bool Config::CheckAuth(const char *response) {
|
||||
}
|
||||
}
|
||||
|
||||
void Config::OBSSaveCallback(obs_data_t *save_data, bool saving, void *private_data) {
|
||||
Config *conf = static_cast<Config *>(private_data);
|
||||
|
||||
if (saving) {
|
||||
obs_data_t *settings = obs_data_create();
|
||||
obs_data_set_bool(settings, CONFIG_PARAM_AUTHREQUIRED, conf->AuthRequired);
|
||||
obs_data_set_string(settings, CONFIG_PARAM_SECRET, conf->Secret);
|
||||
obs_data_set_string(settings, CONFIG_PARAM_SALT, conf->Salt);
|
||||
|
||||
obs_data_set_obj(save_data, CONFIG_SECTION_NAME, settings);
|
||||
}
|
||||
else {
|
||||
obs_data_t *settings = obs_data_get_obj(save_data, CONFIG_SECTION_NAME);
|
||||
if (settings) {
|
||||
conf->AuthRequired = obs_data_get_bool(settings, CONFIG_PARAM_AUTHREQUIRED);
|
||||
conf->Secret = obs_data_get_string(settings, CONFIG_PARAM_SECRET);
|
||||
conf->Salt = obs_data_get_string(settings, CONFIG_PARAM_SALT);
|
||||
|
||||
conf->SettingsLoaded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Config* Config::Current() {
|
||||
Config* Config::Current()
|
||||
{
|
||||
return _instance;
|
||||
}
|
||||
|
13
Config.h
13
Config.h
@ -1,6 +1,6 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -19,19 +19,24 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#include <obs-module.h>
|
||||
#include <mbedtls/entropy.h>
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
|
||||
class Config {
|
||||
class Config
|
||||
{
|
||||
public:
|
||||
Config();
|
||||
~Config();
|
||||
void Load();
|
||||
void Save();
|
||||
|
||||
void SetPassword(const char *password);
|
||||
bool CheckAuth(const char *userChallenge);
|
||||
const char* GenerateSalt();
|
||||
static const char* GenerateSecret(const char *password, const char *salt);
|
||||
static void OBSSaveCallback(obs_data_t *save_data, bool saving, void *);
|
||||
|
||||
bool ServerEnabled;
|
||||
uint64_t ServerPort;
|
||||
|
||||
bool AuthRequired;
|
||||
const char *Secret;
|
||||
|
349
PROTOCOL.md
349
PROTOCOL.md
@ -6,58 +6,206 @@ Messages exchanged between the client and the server are JSON objects.
|
||||
The protocol in general is based on the OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio.
|
||||
|
||||
### Table of contents
|
||||
- [Events](#events)
|
||||
- [Requests](#requests)
|
||||
- [Authentication](#authentication)
|
||||
* [Events](#events)
|
||||
- [Description](#description)
|
||||
- [Event Types](#event-types)
|
||||
- ["SwitchScenes"](#switchscenes)
|
||||
- ["ScenesChanged"](#sceneschanged)
|
||||
- ["SourceOrderChanged"](#sourceorderchanged)
|
||||
- ["SceneItemAdded"](#sceneitemadded)
|
||||
- ["SceneItemRemoved"](#sceneitemremoved)
|
||||
- ["SceneItemVisibilityChanged"](#sceneitemvisibilitychanged)
|
||||
- ["SceneCollectionChanged"](#scenecollectionchanged)
|
||||
- ["SceneCollectionListChanged"](#scenecollectionlistchanged)
|
||||
- ["SwitchTransition"](#switchtransition)
|
||||
- ["TransitionDurationChanged"](#transitiondurationchanged)
|
||||
- ["TransitionListChanged"](#transitionlistchanged)
|
||||
- ["TransitionBegin"](#transitionbegin)
|
||||
- ["ProfileChanged"](#profilechanged)
|
||||
- ["ProfileListChanged"](#profilelistchanged)
|
||||
- ["StreamStarting"](#streamstarting)
|
||||
- ["StreamStarted"](#streamstarted)
|
||||
- ["StreamStopping"](#streamstopping)
|
||||
- ["StreamStopped"](#streamstopped)
|
||||
- ["RecordingStarting"](#recordingstarting)
|
||||
- ["RecordingStarted"](#recordingstarted)
|
||||
- ["RecordingStopping"](#recordingstopping)
|
||||
- ["RecordingStopped"](#recordingstopped)
|
||||
- ["StreamStatus"](#streamstatus)
|
||||
- ["Exiting"](#exiting)
|
||||
* [Requests](#requests)
|
||||
- [Description](#description-1)
|
||||
- [Request Types](#request-types)
|
||||
- ["GetVersion"](#getversion)
|
||||
- ["GetAuthRequired"](#getauthrequired)
|
||||
- ["Authenticate"](#authenticate)
|
||||
- ["GetCurrentScene"](#getcurrentscene)
|
||||
- ["SetCurrentScene"](#setcurrentscene)
|
||||
- ["GetSceneList"](#getscenelist)
|
||||
- ["SetSourceRender"](#setsourcerender)
|
||||
- ["StartStopStreaming"](#startstopstreaming)
|
||||
- ["StartStopRecording"](#startstoprecording)
|
||||
- ["GetStreamingStatus"](#getstreamingstatus)
|
||||
- ["GetTransitionList"](#gettransitionlist)
|
||||
- ["GetCurrentTransition"](#getcurrenttransition)
|
||||
- ["SetCurrentTransition"](#setcurrenttransition)
|
||||
- ["SetTransitionDuration"](#settransitionduration)
|
||||
- ["SetVolume"](#setvolume)
|
||||
- ["GetVolume"](#getvolume)
|
||||
- ["SetMute"](#setmute)
|
||||
- ["ToggleMute"](#togglemute)
|
||||
- ["SetSceneItemPosition"](#setsceneitemposition)
|
||||
- ["SetSceneItemTransform"](#setsceneitemtransform)
|
||||
- ["SetCurrentSceneCollection"](#setcurrentscenecollection)
|
||||
- ["GetCurrentSceneCollection"](#getcurrentscenecollection)
|
||||
- ["ListSceneCollections"](#listscenecollections)
|
||||
- ["SetCurrentProfile"](#setcurrentprofile)
|
||||
- ["GetCurrentProfile"](#getcurrentprofile)
|
||||
- ["ListProfiles"](#listprofiles)
|
||||
* [Authentication](#authentication)
|
||||
|
||||
## Events
|
||||
### Description
|
||||
Events are sent exclusively by the server and broadcast to each connected client.
|
||||
An event message will contain at least one field :
|
||||
- **update-type** (string) : the type of event
|
||||
- **stream-timecode** (string, optional) : time elapsed between now and stream start (only present if OBS Studio is streaming)
|
||||
- **rec-timecode** (string, optional) : time elapsed between now and recording start (only present if OBS Studio is recording)
|
||||
|
||||
Timecodes are in the following format : HH:MM:SS.mmm
|
||||
|
||||
Additional fields will be present in the event message depending on the event type.
|
||||
|
||||
### Event Types
|
||||
#### "SwitchScenes"
|
||||
OBS is switching to another scene.
|
||||
OBS is switching to another scene (called at the end of the transition).
|
||||
- **scene-name** (string) : The name of the scene being switched to.
|
||||
|
||||
---
|
||||
|
||||
#### "ScenesChanged"
|
||||
The scene list has been modified (Scenes have been added, removed, or renamed).
|
||||
|
||||
---
|
||||
|
||||
#### "SourceOrderChanged"
|
||||
Scene items have been reordered.
|
||||
- **"scene-name"** (string) : name of the scene where items have been reordered
|
||||
|
||||
---
|
||||
|
||||
#### "SceneItemAdded"
|
||||
An item has been added to the current scene.
|
||||
- **"scene-name"** (string) : name of the scene
|
||||
- **"item-name"** (string) : name of the item added to **scene-name**
|
||||
|
||||
---
|
||||
|
||||
#### "SceneItemRemoved"
|
||||
An item has been removed from the current scene.
|
||||
- **"scene-name"** (string) : name of the scene
|
||||
- **"item-name"** (string) : name of the item removed from **scene-name**
|
||||
|
||||
---
|
||||
|
||||
#### "SceneItemVisibilityChanged"
|
||||
An item's visibility has been toggled.
|
||||
- **"scene-name"** (string) : name of the scene
|
||||
- **"item-name"** (string) : name of the item in **scene-name**
|
||||
- **"item-visible"** (bool) : new visibility of item **item-name**
|
||||
|
||||
---
|
||||
|
||||
#### "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.
|
||||
|
||||
---
|
||||
|
||||
#### "TransitionDurationChanged"
|
||||
Triggered when the transition duration has changed.
|
||||
- **"new-duration"** (integer) : new transition duration
|
||||
|
||||
---
|
||||
|
||||
#### "TransitionListChanged"
|
||||
The list of available transitions has been modified (Transitions have been added, removed, or renamed).
|
||||
|
||||
---
|
||||
|
||||
#### "TransitionBegin"
|
||||
A transition other than "Cut" has begun.
|
||||
|
||||
---
|
||||
|
||||
#### "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.
|
||||
|
||||
---
|
||||
|
||||
#### "StreamStarted"
|
||||
Streaming started successfully.
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "StreamStopping"
|
||||
A request to stop streaming has been issued.
|
||||
- **preview-only** (bool) : Always false.
|
||||
|
||||
---
|
||||
|
||||
#### "StreamStopped"
|
||||
Streaming stopped successfully.
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "RecordingStarting"
|
||||
A request to start recording has been issued.
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "RecordingStarted"
|
||||
Recording started successfully.
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "RecordingStopping"
|
||||
A request to stop streaming has been issued.
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "RecordingStopped"
|
||||
Recording stopped successfully.
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "StreamStatus"
|
||||
Sent every 2 seconds with the following information :
|
||||
- **streaming** (bool) : Current Streaming state.
|
||||
@ -71,9 +219,13 @@ Sent every 2 seconds with the following information :
|
||||
- **num-dropped-frames** (integer) : Number of frames dropped by the encoder since the stream started.
|
||||
- **fps** (double) : Current framerate.
|
||||
|
||||
---
|
||||
|
||||
#### "Exiting"
|
||||
*New in OBS Studio*
|
||||
OBS is exiting.
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
## Requests
|
||||
|
||||
@ -98,7 +250,10 @@ Returns the latest version of the plugin and the API.
|
||||
__Request fields__ : none
|
||||
__Response__ : always OK, with these additional fields :
|
||||
- **"version"** (double) : OBSRemote API version. Fixed to 1.1 for retrocompatibility.
|
||||
- **"obs-websocket-version"** (string) : obs-websocket version
|
||||
- **"obs-websocket-version"** (string) : obs-websocket version string
|
||||
- **"obs-studio-version"** (string) : OBS Studio version string
|
||||
|
||||
---
|
||||
|
||||
#### "GetAuthRequired"
|
||||
Tells the client if authentication is required. If it is, authentication parameters "challenge" and "salt" are passed in the response fields (see "Authentication").
|
||||
@ -109,6 +264,8 @@ __Response__ : always OK, with these additional fields :
|
||||
- **"challenge"** (string)
|
||||
- **"salt"** (string)
|
||||
|
||||
---
|
||||
|
||||
#### "Authenticate"
|
||||
Try to authenticate the client on the server.
|
||||
|
||||
@ -117,6 +274,8 @@ __Request fields__ :
|
||||
|
||||
__Response__ : OK if auth succeeded, error if invalid credentials. No additional fields.
|
||||
|
||||
---
|
||||
|
||||
#### "GetCurrentScene"
|
||||
Get the current scene's name and items.
|
||||
|
||||
@ -131,9 +290,13 @@ Objects in the "sources" array have the following fields :
|
||||
- **"volume"** (double) : audio volume of the source, ranging from 0.0 to 1.0
|
||||
- **"x"** (double) : X coordinate of the top-left corner of the item in the scene
|
||||
- **"y"** (double) : Y coordinate of the top-left corner of the item in the scene
|
||||
- **"source_cx"** (integer) : width of the item (without scale applied)
|
||||
- **"source_cy"** (integer) : height of the item (without scale applied)
|
||||
- **"cx"** (double) : width of the item (with scale applied)
|
||||
- **"cy"** (double) : height of the item (with scale applied)
|
||||
|
||||
---
|
||||
|
||||
#### "SetCurrentScene"
|
||||
Switch to the scene specified in "scene-name".
|
||||
|
||||
@ -142,6 +305,8 @@ __Request fields__ :
|
||||
|
||||
__Response__ : always OK if scene exists, error if it doesn't. No additional fields
|
||||
|
||||
---
|
||||
|
||||
#### "GetSceneList"
|
||||
List OBS' scenes.
|
||||
|
||||
@ -150,21 +315,28 @@ __Response__ : always OK, with these additional fields :
|
||||
- **"current-scene"** (string) : name of the currently active scene
|
||||
- **"scenes"** (array of objects) : ordered list of scene descriptions (see `GetCurrentScene` for reference)
|
||||
|
||||
---
|
||||
|
||||
#### "SetSourceRender"
|
||||
Show or hide a specific source in the current scene.
|
||||
|
||||
__Request fields__ :
|
||||
- **"source"** (string) : name of the source in the currently active scene.
|
||||
- **"render"** (bool) : desired visibility
|
||||
- **"scene-name"** (string; optional) : name of the scene the source belongs to. defaults to current scene.
|
||||
|
||||
__Response__ : OK if source exists in the current scene, error otherwise.
|
||||
|
||||
---
|
||||
|
||||
#### "StartStopStreaming"
|
||||
Toggle streaming on or off.
|
||||
|
||||
__Request fields__ : none
|
||||
__Response__ : always OK. No additional fields.
|
||||
|
||||
---
|
||||
|
||||
#### "StartStopRecording"
|
||||
Toggle recording on or off.
|
||||
|
||||
@ -172,6 +344,8 @@ __Request fields__ : none
|
||||
__Response__ : always OK. No additional fields.
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "GetStreamingStatus"
|
||||
Get current streaming and recording status.
|
||||
|
||||
@ -181,6 +355,8 @@ __Response__ : always OK, with these additional fields :
|
||||
- **"recording"** (bool) : recording status (active or not)
|
||||
- **"preview-only"** (bool) : always false. Retrocompat with OBSRemote.
|
||||
|
||||
---
|
||||
|
||||
#### "GetTransitionList"
|
||||
List all transitions available in the frontend's dropdown menu.
|
||||
|
||||
@ -194,15 +370,20 @@ Objects in the "transitions" array have only one field :
|
||||
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "GetCurrentTransition"
|
||||
Get the name of the currently selected transition in the frontend's dropdown menu.
|
||||
|
||||
__Request fields__ : none
|
||||
__Request__ : always OK, with these additional fields :
|
||||
__Response__ : always OK, with these additional fields :
|
||||
- **"name"** (string) : name of the selected transition
|
||||
- **"duration"** (integer, only if transition supports this) : transition duration
|
||||
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "SetCurrentTransition"
|
||||
__Request fields__ :
|
||||
- **"transition-name"** (string) : The name of the transition.
|
||||
@ -211,6 +392,160 @@ __Response__ : OK if specified transition exists, error otherwise.
|
||||
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "SetTransitionDuration"
|
||||
Set the duration of the currently selected transition.
|
||||
|
||||
__Request fields__ :
|
||||
- **"duration"** (integer) : desired transition duration in milliseconds
|
||||
|
||||
__Response__ : always OK.
|
||||
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "SetVolume"
|
||||
Set the volume of a specific source.
|
||||
|
||||
__Request fields__ :
|
||||
- **"source"** (string) : the name of the source
|
||||
- **"volume"** (double) : the desired volume
|
||||
|
||||
__Response__ : OK if specified source exists, error otherwise.
|
||||
|
||||
*Updated for OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "GetVolume"
|
||||
Get the volume of a specific source.
|
||||
|
||||
__Request fields__ :
|
||||
- **"source"** (string) : name of the source
|
||||
|
||||
__Response__ : OK if source exists, with these additional fields :
|
||||
- **"name"** (string) : name of the requested source
|
||||
- **"volume"** (double) : volume of the requested source, on a linear scale (0.0 to 1.0)
|
||||
- **"muted"** (bool) : mute status of the requested source
|
||||
|
||||
*Updated for OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "SetMute"
|
||||
Mutes or unmutes a specific source.
|
||||
|
||||
__Request fields__ :
|
||||
- **"source"** (string) : the name of the source
|
||||
- **"mute"** (bool) : the desired mute status
|
||||
|
||||
__Response__ : OK if specified source exists, error otherwise.
|
||||
|
||||
*Updated for OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "ToggleMute"
|
||||
Inverts the mute status of a specific source.
|
||||
|
||||
__Request fields__ :
|
||||
- **"source"** (string) : the name of the source
|
||||
|
||||
__Response__ : OK if specified source exists, error otherwise.
|
||||
|
||||
*Updated for OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "SetSceneItemPosition"
|
||||
__Request fields__ :
|
||||
- **"item"** (string) : The name of the scene item.
|
||||
- **"x"** (float) : x coordinate
|
||||
- **"y"** (float) : y coordinate
|
||||
- **"scene-name"** (string) : scene the item belongs to. defaults to current scene.
|
||||
|
||||
__Response__ : OK if specified item exists, error otherwise.
|
||||
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "SetSceneItemTransform"
|
||||
__Request fields__ :
|
||||
- **"item"** (string) : The name of the scene item.
|
||||
- **"x-scale"** (float) : width scale factor
|
||||
- **"y-scale"** (float) : height scale factor
|
||||
- **"rotation"** (float) : item rotation (in degrees)
|
||||
- **"scene-name"** (string) : scene the item belongs to. defaults to current scene.
|
||||
|
||||
__Response__ : OK if specified item exists, error otherwise.
|
||||
|
||||
*New in OBS Studio*
|
||||
|
||||
---
|
||||
|
||||
#### "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
|
||||
|
25
README.md
25
README.md
@ -1,26 +1,32 @@
|
||||
obs-websocket
|
||||
==============
|
||||
Websocket API for OBS Studio.
|
||||
Remote control of OBS Studio made easy.
|
||||
|
||||
## Downloads
|
||||
Binaries for Windows and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section.
|
||||
|
||||
## Using obs-websocket
|
||||
The Websocket API server runs on port 4444 and a settings window is available in "Websocket server settings" under OBS' "Tools" menu. The obs-websocket protocol is documented in [PROTOCOL.md](PROTOCOL.md).
|
||||
A web client and frontend made by [t2t2](https://github.com/t2t2/obs-tablet-remote) (compatible with tablets and other touch interfaces) is available here : http://t2t2.github.io/obs-tablet-remote/
|
||||
|
||||
Here's a list of available language APIs for obs-websocket :
|
||||
- Javascript (browser & nodejs) : [obs-websocket-js](https://github.com/haganbmj/obs-websocket-js) by haganbmj
|
||||
|
||||
There's currently no frontend available for obs-websocket.
|
||||
It is **highly recommended** to protect obs-websocket with a password against unauthorized control. To do this, open the "Websocket server settings" dialog under OBS' "Tools" menu. In the settings dialogs, you can enable or disable authentication and set a password for it.
|
||||
|
||||
### Possible use cases
|
||||
- Remote control OBS from a phone or tablet on the same local network
|
||||
- Change your stream overlay/graphics based on the current scene (like the AGDQ overlay does)
|
||||
- Automate scene switching with a third-party program (e.g. : auto-pilot, foot pedal, ...)
|
||||
|
||||
### For developers
|
||||
The server is a typical Websockets server running by default on port 4444 (the port number can be changed in the Settings dialog).
|
||||
The protocol understood by the server is documented in [PROTOCOL.md](PROTOCOL.md).
|
||||
|
||||
Here's a list of available language APIs for obs-websocket :
|
||||
- Javascript (browser & nodejs) : [obs-websocket-js](https://github.com/haganbmj/obs-websocket-js) by haganbmj
|
||||
|
||||
I'd like to know what you're building with or for obs-websocket. If you do something in this fashion, feel free to drop me an email at `contact at slepin dot fr` !
|
||||
|
||||
## Compiling obs-websocket
|
||||
### Prerequisites
|
||||
You'll need QT 5 with QtWebSockets, CMake, and a working development environment for OBS Studio installed on your computer.
|
||||
You'll need [QT 5.7.0](https://download.qt.io/official_releases/qt/5.7/5.7.0/), CMake, and a working development environment for OBS Studio installed on your computer.
|
||||
|
||||
### Windows
|
||||
In cmake-gui, you'll have to set the following variables :
|
||||
@ -43,3 +49,8 @@ sudo make install
|
||||
|
||||
### OS X
|
||||
*To do*
|
||||
|
||||
### Automated Builds
|
||||
- Windows : [](https://ci.appveyor.com/project/Palakis/obs-websocket/history)
|
||||
- Linux & OS X : coming soon
|
||||
|
||||
|
117
Utils.cpp
117
Utils.cpp
@ -1,6 +1,6 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -17,6 +17,34 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include "Utils.h"
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QMainWindow>
|
||||
#include <QSpinBox>
|
||||
#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();
|
||||
@ -46,8 +74,12 @@ obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t *item) {
|
||||
vec2 pos;
|
||||
obs_sceneitem_get_pos(item, &pos);
|
||||
|
||||
vec2 bounds;
|
||||
obs_sceneitem_get_bounds(item, &bounds);
|
||||
vec2 scale;
|
||||
obs_sceneitem_get_scale(item, &scale);
|
||||
|
||||
obs_source_t* item_source = obs_sceneitem_get_source(item);
|
||||
float item_width = float(obs_source_get_width(item_source));
|
||||
float item_height = float(obs_source_get_height(item_source));
|
||||
|
||||
obs_data_t *data = obs_data_create();
|
||||
obs_data_set_string(data, "name", obs_source_get_name(obs_sceneitem_get_source(item)));
|
||||
@ -55,8 +87,10 @@ obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t *item) {
|
||||
obs_data_set_double(data, "volume", obs_source_get_volume(obs_sceneitem_get_source(item)));
|
||||
obs_data_set_double(data, "x", pos.x);
|
||||
obs_data_set_double(data, "y", pos.y);
|
||||
obs_data_set_double(data, "cx", bounds.x);
|
||||
obs_data_set_double(data, "cy", bounds.y);
|
||||
obs_data_set_int(data, "source_cx", (int)item_width);
|
||||
obs_data_set_int(data, "source_cy", (int)item_height);
|
||||
obs_data_set_double(data, "cx", item_width * scale.x);
|
||||
obs_data_set_double(data, "cy", item_height * scale.y);
|
||||
obs_data_set_bool(data, "render", obs_sceneitem_visible(item));
|
||||
|
||||
return data;
|
||||
@ -115,6 +149,18 @@ obs_source_t* Utils::GetTransitionFromName(const char *search_name) {
|
||||
return found_transition;
|
||||
}
|
||||
|
||||
obs_source_t* Utils::GetSceneFromNameOrCurrent(const char *scene_name) {
|
||||
obs_source_t* scene;
|
||||
if (!scene_name || !strlen(scene_name)) {
|
||||
scene = obs_frontend_get_current_scene();
|
||||
}
|
||||
else {
|
||||
scene = obs_get_source_by_name(scene_name);
|
||||
}
|
||||
|
||||
return scene;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetScenes() {
|
||||
obs_frontend_source_list sceneList = {};
|
||||
obs_frontend_get_scenes(&sceneList);
|
||||
@ -144,3 +190,64 @@ obs_data_t* Utils::GetSceneData(obs_source *source) {
|
||||
obs_data_array_release(scene_items);
|
||||
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;
|
||||
}
|
||||
|
||||
QSpinBox* Utils::GetTransitionDurationControl()
|
||||
{
|
||||
QMainWindow *window = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return window->findChild<QSpinBox*>("transitionDuration");
|
||||
}
|
||||
|
||||
int Utils::GetTransitionDuration()
|
||||
{
|
||||
QSpinBox* control = GetTransitionDurationControl();
|
||||
if (control)
|
||||
{
|
||||
return control->value();
|
||||
}
|
||||
else
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void Utils::SetTransitionDuration(int ms)
|
||||
{
|
||||
QSpinBox* control = GetTransitionDurationControl();
|
||||
|
||||
if (control && ms >= 0)
|
||||
{
|
||||
control->setValue(ms);
|
||||
}
|
||||
}
|
||||
|
||||
const char* Utils::OBSVersionString() {
|
||||
uint32_t version = obs_get_version();
|
||||
|
||||
uint8_t major, minor, patch;
|
||||
major = (version >> 24) & 0xFF;
|
||||
minor = (version >> 16) & 0xFF;
|
||||
patch = version & 0xFF;
|
||||
|
||||
char *result = (char*)bmalloc(sizeof(char) * 12);
|
||||
sprintf(result, "%d.%d.%d", major, minor, patch);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
15
Utils.h
15
Utils.h
@ -1,6 +1,6 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -19,8 +19,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#include <QSpinBox>
|
||||
#include <stdio.h>
|
||||
#include <obs-module.h>
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
class Utils
|
||||
{
|
||||
@ -29,9 +30,19 @@ class Utils
|
||||
static obs_data_t* GetSceneItemData(obs_scene_item *item);
|
||||
static obs_sceneitem_t* GetSceneItemFromName(obs_source_t *source, const char *name);
|
||||
static obs_source_t* GetTransitionFromName(const char *search_name);
|
||||
static obs_source_t* GetSceneFromNameOrCurrent(const char *scene_name);
|
||||
|
||||
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 QSpinBox* GetTransitionDurationControl();
|
||||
static int GetTransitionDuration();
|
||||
static void SetTransitionDuration(int ms);
|
||||
|
||||
static const char* OBSVersionString();
|
||||
};
|
||||
|
||||
#endif // UTILS_H
|
427
WSEvents.cpp
427
WSEvents.cpp
@ -1,6 +1,7 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2017 Brendan Hagan <https://github.com/haganbmj>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -16,18 +17,78 @@ 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 <util/platform.h>
|
||||
#include <QTimer>
|
||||
#include "Utils.h"
|
||||
#include "WSEvents.h"
|
||||
#include "obs-websocket.h"
|
||||
|
||||
WSEvents::WSEvents(WSServer *server) {
|
||||
_srv = server;
|
||||
bool transition_is_cut(obs_source_t *transition)
|
||||
{
|
||||
if (!transition)
|
||||
return false;
|
||||
|
||||
if (obs_source_get_type(transition) == OBS_SOURCE_TYPE_TRANSITION
|
||||
&& strcmp(obs_source_get_id(transition), "cut_transition") == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* ns_to_timestamp(uint64_t ns)
|
||||
{
|
||||
uint64_t ms = ns / (1000 * 1000);
|
||||
uint64_t secs = ms / 1000;
|
||||
uint64_t minutes = secs / 60;
|
||||
|
||||
uint64_t hours_part = minutes / 60;
|
||||
uint64_t minutes_part = minutes % 60;
|
||||
uint64_t secs_part = secs % 60;
|
||||
uint64_t ms_part = ms % 1000;
|
||||
|
||||
char* ts = (char*)bmalloc(64);
|
||||
sprintf(ts, "%02d:%02d:%02d.%03d", hours_part, minutes_part, secs_part, ms_part);
|
||||
|
||||
return ts;
|
||||
}
|
||||
|
||||
WSEvents::WSEvents(WSServer *srv)
|
||||
{
|
||||
_srv = srv;
|
||||
obs_frontend_add_event_callback(WSEvents::FrontendEventHandler, this);
|
||||
|
||||
QSpinBox* duration_control = Utils::GetTransitionDurationControl();
|
||||
connect(duration_control, SIGNAL(valueChanged(int)), this, SLOT(TransitionDurationChanged(int)));
|
||||
|
||||
QTimer *statusTimer = new QTimer();
|
||||
connect(statusTimer, SIGNAL(timeout()), this, SLOT(StreamStatus()));
|
||||
statusTimer->start(2000); // equal to frontend's constant BITRATE_UPDATE_SECONDS
|
||||
|
||||
transition_handler = nullptr;
|
||||
scene_handler = nullptr;
|
||||
|
||||
WSEvents* instance = this;
|
||||
QTimer::singleShot(1000, [instance]() {
|
||||
obs_source_t* transition = obs_frontend_get_current_transition();
|
||||
instance->connectTransitionSignals(transition);
|
||||
obs_source_release(transition);
|
||||
|
||||
obs_source_t* scene = obs_frontend_get_current_scene();
|
||||
instance->connectSceneSignals(scene);
|
||||
obs_source_release(scene);
|
||||
});
|
||||
|
||||
_streaming_active = false;
|
||||
_recording_active = false;
|
||||
|
||||
_stream_starttime = 0;
|
||||
_rec_starttime = 0;
|
||||
}
|
||||
|
||||
WSEvents::~WSEvents() {
|
||||
WSEvents::~WSEvents()
|
||||
{
|
||||
obs_frontend_remove_event_callback(WSEvents::FrontendEventHandler, this);
|
||||
}
|
||||
|
||||
@ -35,51 +96,105 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void *private
|
||||
{
|
||||
WSEvents *owner = static_cast<WSEvents *>(private_data);
|
||||
|
||||
// TODO : implement SourceChanged, SourceOrderChanged and RepopulateSources
|
||||
if (!owner->_srv)
|
||||
return;
|
||||
|
||||
if (event == OBS_FRONTEND_EVENT_SCENE_CHANGED) {
|
||||
// TODO : implement SourceOrderChanged and RepopulateSources
|
||||
|
||||
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_STREAMING_STARTING) {
|
||||
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)
|
||||
{
|
||||
owner->OnTransitionListChange();
|
||||
}
|
||||
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->_streaming_active = true;
|
||||
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->_streaming_active = false;
|
||||
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->_recording_active = true;
|
||||
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->_recording_active = false;
|
||||
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) {
|
||||
obs_source_t *source = obs_frontend_get_current_scene();
|
||||
const char *name = obs_source_get_name(source);
|
||||
|
||||
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);
|
||||
|
||||
const char* ts = nullptr;
|
||||
if (_streaming_active)
|
||||
{
|
||||
ts = ns_to_timestamp(os_gettime_ns() - _stream_starttime);
|
||||
obs_data_set_string(update, "stream-timecode", ts);
|
||||
bfree((void*)ts);
|
||||
}
|
||||
|
||||
if (_recording_active)
|
||||
{
|
||||
ts = ns_to_timestamp(os_gettime_ns() - _rec_starttime);
|
||||
obs_data_set_string(update, "rec-timecode", ts);
|
||||
bfree((void*)ts);
|
||||
}
|
||||
|
||||
if (additionalFields != NULL) {
|
||||
obs_data_apply(update, additionalFields);
|
||||
}
|
||||
@ -87,46 +202,115 @@ void WSEvents::broadcastUpdate(const char *updateType, obs_data_t *additionalFie
|
||||
_srv->broadcast(obs_data_get_json(update));
|
||||
|
||||
obs_data_release(update);
|
||||
obs_source_release(source);
|
||||
}
|
||||
|
||||
void WSEvents::OnSceneChange() {
|
||||
void WSEvents::connectTransitionSignals(obs_source_t* transition)
|
||||
{
|
||||
if (transition_handler)
|
||||
{
|
||||
signal_handler_disconnect(transition_handler, "transition_start", OnTransitionBegin, this);
|
||||
}
|
||||
|
||||
if (!transition_is_cut(transition))
|
||||
{
|
||||
transition_handler = obs_source_get_signal_handler(transition);
|
||||
signal_handler_connect(transition_handler, "transition_start", OnTransitionBegin, this); }
|
||||
else
|
||||
{
|
||||
transition_handler = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void WSEvents::connectSceneSignals(obs_source_t* scene)
|
||||
{
|
||||
// TODO : connect to all scenes, not just the current one.
|
||||
|
||||
if (scene_handler)
|
||||
{
|
||||
signal_handler_disconnect(scene_handler, "reorder", OnSceneReordered, this);
|
||||
signal_handler_disconnect(scene_handler, "item_add", OnSceneItemAdd, this);
|
||||
signal_handler_disconnect(scene_handler, "item_remove", OnSceneItemDelete, this);
|
||||
signal_handler_disconnect(scene_handler, "item_visible", OnSceneItemVisibilityChanged, this);
|
||||
}
|
||||
|
||||
scene_handler = obs_source_get_signal_handler(scene);
|
||||
signal_handler_connect(scene_handler, "reorder", OnSceneReordered, this);
|
||||
signal_handler_connect(scene_handler, "item_add", OnSceneItemAdd, this);
|
||||
signal_handler_connect(scene_handler, "item_remove", OnSceneItemDelete, this);
|
||||
signal_handler_connect(scene_handler, "item_visible", OnSceneItemVisibilityChanged, this);
|
||||
}
|
||||
|
||||
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) {
|
||||
obs_source_release(transition);
|
||||
return;
|
||||
}
|
||||
|
||||
const char *scene_name = obs_source_get_name(new_scene);
|
||||
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();
|
||||
|
||||
if (new_scene) {
|
||||
scene_name = obs_source_get_name(new_scene);
|
||||
}
|
||||
}
|
||||
|
||||
obs_data_t *data = obs_data_create();
|
||||
obs_data_set_string(data, "scene-name", scene_name);
|
||||
|
||||
obs_source_t* current_scene = obs_frontend_get_current_scene();
|
||||
connectSceneSignals(current_scene);
|
||||
|
||||
obs_data_set_string(data, "scene-name", obs_source_get_name(current_scene));
|
||||
|
||||
broadcastUpdate("SwitchScenes", data);
|
||||
|
||||
obs_data_release(data);
|
||||
obs_source_release(new_scene);
|
||||
obs_source_release(transition);
|
||||
obs_source_release(current_scene);
|
||||
}
|
||||
|
||||
void WSEvents::OnSceneListChange() {
|
||||
void WSEvents::OnSceneListChange()
|
||||
{
|
||||
broadcastUpdate("ScenesChanged");
|
||||
}
|
||||
|
||||
void WSEvents::OnStreamStarting() {
|
||||
void WSEvents::OnSceneCollectionChange()
|
||||
{
|
||||
broadcastUpdate("SceneCollectionChanged");
|
||||
|
||||
scene_handler = nullptr;
|
||||
transition_handler = nullptr;
|
||||
|
||||
OnTransitionListChange();
|
||||
OnTransitionChange();
|
||||
|
||||
OnSceneListChange();
|
||||
OnSceneChange();
|
||||
}
|
||||
|
||||
void WSEvents::OnSceneCollectionListChange()
|
||||
{
|
||||
broadcastUpdate("SceneCollectionListChanged");
|
||||
}
|
||||
|
||||
void WSEvents::OnTransitionChange()
|
||||
{
|
||||
obs_source_t* current_transition = obs_frontend_get_current_transition();
|
||||
connectTransitionSignals(current_transition);
|
||||
|
||||
obs_data_t *data = obs_data_create();
|
||||
obs_data_set_string(data, "transition-name", obs_source_get_name(current_transition));
|
||||
|
||||
broadcastUpdate("SwitchTransition", data);
|
||||
|
||||
obs_data_release(data);
|
||||
obs_source_release(current_transition);
|
||||
}
|
||||
|
||||
void WSEvents::OnTransitionListChange()
|
||||
{
|
||||
broadcastUpdate("TransitionListChanged");
|
||||
}
|
||||
|
||||
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);
|
||||
@ -136,14 +320,16 @@ void WSEvents::OnStreamStarting() {
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void WSEvents::OnStreamStarted() {
|
||||
void WSEvents::OnStreamStarted()
|
||||
{
|
||||
// New update type specific to OBS Studio
|
||||
_streamStartTime = os_gettime_ns();
|
||||
_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);
|
||||
@ -153,38 +339,47 @@ void WSEvents::OnStreamStopping() {
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void WSEvents::OnStreamStopped() {
|
||||
void WSEvents::OnStreamStopped()
|
||||
{
|
||||
// New update type specific to OBS Studio
|
||||
_streamStartTime = 0;
|
||||
_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();
|
||||
|
||||
@ -215,15 +410,12 @@ void WSEvents::StreamStatus() {
|
||||
_lastBytesSent = bytes_sent;
|
||||
_lastBytesSentTime = bytes_sent_time;
|
||||
|
||||
uint64_t totalStreamTime = (os_gettime_ns() - _streamStartTime) / 1000000000;
|
||||
uint64_t totalStreamTime = (os_gettime_ns() - _stream_starttime) / 1000000000;
|
||||
|
||||
int total_frames = obs_output_get_total_frames(stream_output);
|
||||
int dropped_frames = obs_output_get_frames_dropped(stream_output);
|
||||
|
||||
float strain = 0.0;
|
||||
if (total_frames > 0) {
|
||||
strain = (dropped_frames / total_frames) * 100.0;
|
||||
}
|
||||
float strain = obs_output_get_congestion(stream_output);
|
||||
|
||||
obs_data_t *data = obs_data_create();
|
||||
obs_data_set_bool(data, "streaming", streaming_active);
|
||||
@ -242,3 +434,108 @@ void WSEvents::StreamStatus() {
|
||||
obs_data_release(data);
|
||||
obs_output_release(stream_output);
|
||||
}
|
||||
|
||||
void WSEvents::TransitionDurationChanged(int ms)
|
||||
{
|
||||
obs_data_t* fields = obs_data_create();
|
||||
obs_data_set_int(fields, "new-duration", ms);
|
||||
|
||||
broadcastUpdate("TransitionDurationChanged", fields);
|
||||
|
||||
obs_data_release(fields);
|
||||
}
|
||||
|
||||
void WSEvents::OnTransitionBegin(void* param, calldata_t* data)
|
||||
{
|
||||
UNUSED_PARAMETER(data);
|
||||
|
||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||
instance->broadcastUpdate("TransitionBegin");
|
||||
|
||||
blog(LOG_INFO, "transition begin");
|
||||
}
|
||||
|
||||
void WSEvents::OnSceneReordered(void *param, calldata_t *data)
|
||||
{
|
||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||
|
||||
obs_scene_t* scene = nullptr;
|
||||
calldata_get_ptr(data, "scene", &scene);
|
||||
|
||||
obs_data_t *fields = obs_data_create();
|
||||
obs_data_set_string(fields, "scene-name", obs_source_get_name(obs_scene_get_source(scene)));
|
||||
|
||||
instance->broadcastUpdate("SourceOrderChanged", fields);
|
||||
|
||||
obs_data_release(fields);
|
||||
}
|
||||
|
||||
void WSEvents::OnSceneItemAdd(void *param, calldata_t *data)
|
||||
{
|
||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||
|
||||
obs_scene_t* scene = nullptr;
|
||||
calldata_get_ptr(data, "scene", &scene);
|
||||
|
||||
obs_sceneitem_t* scene_item = nullptr;
|
||||
calldata_get_ptr(data, "item", &scene_item);
|
||||
|
||||
const char* scene_name = obs_source_get_name(obs_scene_get_source(scene));
|
||||
const char* sceneitem_name = obs_source_get_name(obs_sceneitem_get_source(scene_item));
|
||||
|
||||
obs_data_t* fields = obs_data_create();
|
||||
obs_data_set_string(fields, "scene-name", scene_name);
|
||||
obs_data_set_string(fields, "item-name", sceneitem_name);
|
||||
|
||||
instance->broadcastUpdate("SceneItemAdded", fields);
|
||||
|
||||
obs_data_release(fields);
|
||||
}
|
||||
|
||||
void WSEvents::OnSceneItemDelete(void *param, calldata_t *data)
|
||||
{
|
||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||
|
||||
obs_scene_t* scene = nullptr;
|
||||
calldata_get_ptr(data, "scene", &scene);
|
||||
|
||||
obs_sceneitem_t* scene_item = nullptr;
|
||||
calldata_get_ptr(data, "item", &scene_item);
|
||||
|
||||
const char* scene_name = obs_source_get_name(obs_scene_get_source(scene));
|
||||
const char* sceneitem_name = obs_source_get_name(obs_sceneitem_get_source(scene_item));
|
||||
|
||||
obs_data_t* fields = obs_data_create();
|
||||
obs_data_set_string(fields, "scene-name", scene_name);
|
||||
obs_data_set_string(fields, "item-name", sceneitem_name);
|
||||
|
||||
instance->broadcastUpdate("SceneItemRemoved", fields);
|
||||
|
||||
obs_data_release(fields);
|
||||
}
|
||||
|
||||
void WSEvents::OnSceneItemVisibilityChanged(void *param, calldata_t *data)
|
||||
{
|
||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||
|
||||
obs_scene_t* scene = nullptr;
|
||||
calldata_get_ptr(data, "scene", &scene);
|
||||
|
||||
obs_sceneitem_t* scene_item = nullptr;
|
||||
calldata_get_ptr(data, "item", &scene_item);
|
||||
|
||||
bool visible = false;
|
||||
calldata_get_bool(data, "visible", &visible);
|
||||
|
||||
const char* scene_name = obs_source_get_name(obs_scene_get_source(scene));
|
||||
const char* sceneitem_name = obs_source_get_name(obs_sceneitem_get_source(scene_item));
|
||||
|
||||
obs_data_t* fields = obs_data_create();
|
||||
obs_data_set_string(fields, "scene-name", scene_name);
|
||||
obs_data_set_string(fields, "item-name", sceneitem_name);
|
||||
obs_data_set_bool(fields, "item-visible", visible);
|
||||
|
||||
instance->broadcastUpdate("SceneItemVisibilityChanged", fields);
|
||||
|
||||
obs_data_release(fields);
|
||||
}
|
37
WSEvents.h
37
WSEvents.h
@ -1,6 +1,7 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2017 Brendan Hagan <https://github.com/haganbmj>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -19,10 +20,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#ifndef WSEVENTS_H
|
||||
#define WSEVENTS_H
|
||||
|
||||
#include <QtWebSockets/QWebSocket>
|
||||
#include <QTimer>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <util/platform.h>
|
||||
#include "WSServer.h"
|
||||
|
||||
class WSEvents : public QObject
|
||||
@ -30,22 +28,42 @@ class WSEvents : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WSEvents(WSServer *server);
|
||||
explicit WSEvents(WSServer *srv);
|
||||
~WSEvents();
|
||||
static void FrontendEventHandler(enum obs_frontend_event event, void *private_data);
|
||||
void connectTransitionSignals(obs_source_t* transition);
|
||||
void connectSceneSignals(obs_source_t* scene);
|
||||
|
||||
private Q_SLOTS:
|
||||
void StreamStatus();
|
||||
void TransitionDurationChanged(int ms);
|
||||
|
||||
private:
|
||||
WSServer *_srv;
|
||||
uint64_t _streamStartTime;
|
||||
signal_handler_t *transition_handler;
|
||||
signal_handler_t *scene_handler;
|
||||
|
||||
bool _streaming_active;
|
||||
bool _recording_active;
|
||||
|
||||
uint64_t _stream_starttime;
|
||||
uint64_t _rec_starttime;
|
||||
|
||||
uint64_t _lastBytesSent;
|
||||
uint64_t _lastBytesSentTime;
|
||||
|
||||
void broadcastUpdate(const char *updateType, obs_data_t *additionalFields);
|
||||
|
||||
void OnSceneChange();
|
||||
void OnSceneListChange();
|
||||
void OnSceneCollectionChange();
|
||||
void OnSceneCollectionListChange();
|
||||
|
||||
void OnTransitionChange();
|
||||
void OnTransitionListChange();
|
||||
|
||||
void OnProfileChange();
|
||||
void OnProfileListChange();
|
||||
|
||||
void OnStreamStarting();
|
||||
void OnStreamStarted();
|
||||
@ -58,6 +76,13 @@ class WSEvents : public QObject
|
||||
void OnRecordingStopped();
|
||||
|
||||
void OnExit();
|
||||
|
||||
static void OnTransitionBegin(void *param, calldata_t *data);
|
||||
|
||||
static void OnSceneReordered(void *param, calldata_t *data);
|
||||
static void OnSceneItemAdd(void *param, calldata_t *data);
|
||||
static void OnSceneItemDelete(void *param, calldata_t *data);
|
||||
static void OnSceneItemVisibilityChanged(void *param, calldata_t *data);
|
||||
};
|
||||
|
||||
#endif // WSEVENTS_H
|
@ -1,6 +1,7 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2017 Mikhail Swift <https://github.com/mikhailswift>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -22,7 +23,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "Utils.h"
|
||||
|
||||
WSRequestHandler::WSRequestHandler(QWebSocket *client) :
|
||||
_authenticated(false),
|
||||
_messageId(0),
|
||||
_requestType(""),
|
||||
_requestData(nullptr)
|
||||
@ -36,37 +36,53 @@ WSRequestHandler::WSRequestHandler(QWebSocket *client) :
|
||||
messageMap["SetCurrentScene"] = WSRequestHandler::HandleSetCurrentScene;
|
||||
messageMap["GetCurrentScene"] = WSRequestHandler::HandleGetCurrentScene;
|
||||
messageMap["GetSceneList"] = WSRequestHandler::HandleGetSceneList;
|
||||
messageMap["SetSourceOrder"] = WSRequestHandler::ErrNotImplemented;
|
||||
messageMap["SetSourceRender"] = WSRequestHandler::HandleSetSourceRender;
|
||||
messageMap["SetSceneItemPositionAndSize"] = WSRequestHandler::ErrNotImplemented;
|
||||
|
||||
messageMap["SetSourceRender"] = WSRequestHandler::HandleSetSourceRender; // Retrocompat
|
||||
messageMap["SetSceneItemRender"] = WSRequestHandler::HandleSetSourceRender;
|
||||
messageMap["SetSceneItemPosition"] = WSRequestHandler::HandleSetSceneItemPosition;
|
||||
messageMap["SetSceneItemTransform"] = WSRequestHandler::HandleSetSceneItemTransform;
|
||||
|
||||
messageMap["GetStreamingStatus"] = WSRequestHandler::HandleGetStreamingStatus;
|
||||
messageMap["StartStopStreaming"] = WSRequestHandler::HandleStartStopStreaming;
|
||||
messageMap["StartStopRecording"] = WSRequestHandler::HandleStartStopRecording;
|
||||
messageMap["ToggleMute"] = WSRequestHandler::ErrNotImplemented;
|
||||
messageMap["GetVolumes"] = WSRequestHandler::ErrNotImplemented;
|
||||
messageMap["SetVolume"] = WSRequestHandler::ErrNotImplemented;
|
||||
|
||||
messageMap["GetTransitionList"] = WSRequestHandler::HandleGetTransitionList;
|
||||
messageMap["GetCurrentTransition"] = WSRequestHandler::HandleGetCurrentTransition;
|
||||
messageMap["SetCurrentTransition"] = WSRequestHandler::HandleSetCurrentTransition;
|
||||
messageMap["SetTransitionDuration"] = WSRequestHandler::HandleSetTransitionDuration;
|
||||
|
||||
messageMap["SetVolume"] = WSRequestHandler::HandleSetVolume;
|
||||
messageMap["GetVolume"] = WSRequestHandler::HandleGetVolume;
|
||||
messageMap["ToggleMute"] = WSRequestHandler::HandleToggleMute;
|
||||
messageMap["SetMute"] = WSRequestHandler::HandleSetMute;
|
||||
|
||||
messageMap["SetCurrentSceneCollection"] = WSRequestHandler::HandleSetCurrentSceneCollection;
|
||||
messageMap["GetCurrentSceneCollection"] = WSRequestHandler::HandleGetCurrentSceneCollection;
|
||||
messageMap["ListSceneCollections"] = WSRequestHandler::HandleListSceneCollections;
|
||||
|
||||
messageMap["SetCurrentProfile"] = WSRequestHandler::HandleSetCurrentProfile;
|
||||
messageMap["GetCurrentProfile"] = WSRequestHandler::HandleGetCurrentProfile;
|
||||
messageMap["ListProfiles"] = WSRequestHandler::HandleListProfiles;
|
||||
|
||||
authNotRequired.insert("GetVersion");
|
||||
authNotRequired.insert("GetAuthRequired");
|
||||
authNotRequired.insert("Authenticate");
|
||||
|
||||
blog(LOG_INFO, "[obs-websockets] new client connected from %s:%d", _client->peerAddress().toString().toLocal8Bit(), _client->peerPort());
|
||||
|
||||
connect(_client, &QWebSocket::textMessageReceived, this, &WSRequestHandler::processTextMessage);
|
||||
connect(_client, &QWebSocket::disconnected, this, &WSRequestHandler::socketDisconnected);
|
||||
}
|
||||
|
||||
void WSRequestHandler::processTextMessage(QString textMessage) {
|
||||
QByteArray msgData = textMessage.toLocal8Bit();
|
||||
void WSRequestHandler::processIncomingMessage(QString textMessage)
|
||||
{
|
||||
QByteArray msgData = textMessage.toUtf8();
|
||||
const char *msg = msgData;
|
||||
|
||||
_requestData = obs_data_create_from_json(msg);
|
||||
if (!_requestData) {
|
||||
blog(LOG_ERROR, "[obs-websockets] invalid JSON payload for '%s'", msg);
|
||||
if (!_requestData)
|
||||
{
|
||||
if (!msg)
|
||||
{
|
||||
msg = "<null pointer>";
|
||||
}
|
||||
|
||||
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg);
|
||||
SendErrorResponse("invalid JSON payload");
|
||||
return;
|
||||
}
|
||||
@ -75,8 +91,8 @@ void WSRequestHandler::processTextMessage(QString textMessage) {
|
||||
_messageId = obs_data_get_string(_requestData, "message-id");
|
||||
|
||||
if (Config::Current()->AuthRequired
|
||||
&& !_authenticated
|
||||
&& authNotRequired.find(_requestType) == authNotRequired.end())
|
||||
&& (_client->property(PROP_AUTHENTICATED).toBool() == false)
|
||||
&& (authNotRequired.find(_requestType) == authNotRequired.end()))
|
||||
{
|
||||
SendErrorResponse("Not Authenticated");
|
||||
return;
|
||||
@ -84,44 +100,34 @@ void WSRequestHandler::processTextMessage(QString textMessage) {
|
||||
|
||||
void (*handlerFunc)(WSRequestHandler*) = (messageMap[_requestType]);
|
||||
|
||||
if (handlerFunc != NULL) {
|
||||
if (handlerFunc != NULL)
|
||||
{
|
||||
handlerFunc(this);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
SendErrorResponse("invalid request type");
|
||||
}
|
||||
|
||||
obs_data_release(_requestData);
|
||||
}
|
||||
|
||||
void WSRequestHandler::socketDisconnected() {
|
||||
blog(LOG_INFO, "[obs-websockets] client %s:%d disconnected", _client->peerAddress().toString().toStdString(), _client->peerPort());
|
||||
|
||||
_authenticated = false;
|
||||
_client->deleteLater();
|
||||
emit disconnected();
|
||||
}
|
||||
|
||||
void WSRequestHandler::sendTextMessage(QString textMessage) {
|
||||
_client->sendTextMessage(textMessage);
|
||||
}
|
||||
|
||||
bool WSRequestHandler::isAuthenticated() {
|
||||
return _authenticated;
|
||||
}
|
||||
|
||||
WSRequestHandler::~WSRequestHandler() {
|
||||
if (_requestData != NULL) {
|
||||
WSRequestHandler::~WSRequestHandler()
|
||||
{
|
||||
if (_requestData != NULL)
|
||||
{
|
||||
obs_data_release(_requestData);
|
||||
}
|
||||
}
|
||||
|
||||
void WSRequestHandler::SendOKResponse(obs_data_t *additionalFields) {
|
||||
void WSRequestHandler::SendOKResponse(obs_data_t *additionalFields)
|
||||
{
|
||||
obs_data_t *response = obs_data_create();
|
||||
obs_data_set_string(response, "status", "ok");
|
||||
obs_data_set_string(response, "message-id", _messageId);
|
||||
|
||||
if (additionalFields != NULL) {
|
||||
if (additionalFields != NULL)
|
||||
{
|
||||
obs_data_apply(response, additionalFields);
|
||||
}
|
||||
|
||||
@ -130,7 +136,8 @@ void WSRequestHandler::SendOKResponse(obs_data_t *additionalFields) {
|
||||
obs_data_release(response);
|
||||
}
|
||||
|
||||
void WSRequestHandler::SendErrorResponse(const char *errorMessage) {
|
||||
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);
|
||||
@ -141,24 +148,30 @@ void WSRequestHandler::SendErrorResponse(const char *errorMessage) {
|
||||
obs_data_release(response);
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleGetVersion(WSRequestHandler *owner) {
|
||||
void WSRequestHandler::HandleGetVersion(WSRequestHandler *owner)
|
||||
{
|
||||
const char* obs_version = Utils::OBSVersionString();
|
||||
|
||||
obs_data_t *data = obs_data_create();
|
||||
obs_data_set_double(data, "version", 1.1);
|
||||
obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION);
|
||||
//obs_data_set_string(data, "obs-studio-version", OBS_VERSION); // Wrong
|
||||
obs_data_set_string(data, "obs-studio-version", obs_version);
|
||||
|
||||
owner->SendOKResponse(data);
|
||||
|
||||
obs_data_release(data);
|
||||
bfree((void*)obs_version);
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleGetAuthRequired(WSRequestHandler *owner) {
|
||||
void WSRequestHandler::HandleGetAuthRequired(WSRequestHandler *owner)
|
||||
{
|
||||
bool authRequired = Config::Current()->AuthRequired;
|
||||
|
||||
obs_data_t *data = obs_data_create();
|
||||
obs_data_set_bool(data, "authRequired", authRequired);
|
||||
|
||||
if (authRequired) {
|
||||
if (authRequired)
|
||||
{
|
||||
obs_data_set_string(data, "challenge", Config::Current()->SessionChallenge);
|
||||
obs_data_set_string(data, "salt", Config::Current()->Salt);
|
||||
}
|
||||
@ -168,43 +181,50 @@ void WSRequestHandler::HandleGetAuthRequired(WSRequestHandler *owner) {
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleAuthenticate(WSRequestHandler *owner) {
|
||||
void WSRequestHandler::HandleAuthenticate(WSRequestHandler *owner)
|
||||
{
|
||||
const char *auth = obs_data_get_string(owner->_requestData, "auth");
|
||||
if (!auth || strlen(auth) < 1) {
|
||||
if (!auth || strlen(auth) < 1)
|
||||
{
|
||||
owner->SendErrorResponse("auth not specified!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(owner->_authenticated) && Config::Current()->CheckAuth(auth)) {
|
||||
owner->_authenticated = true;
|
||||
if ((owner->_client->property(PROP_AUTHENTICATED).toBool() == false) && Config::Current()->CheckAuth(auth))
|
||||
{
|
||||
owner->_client->setProperty(PROP_AUTHENTICATED, true);
|
||||
owner->SendOKResponse();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
owner->SendErrorResponse("Authentication Failed.");
|
||||
}
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleSetCurrentScene(WSRequestHandler *owner) {
|
||||
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) {
|
||||
if (source)
|
||||
{
|
||||
obs_frontend_set_current_scene(source);
|
||||
owner->SendOKResponse();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
owner->SendErrorResponse("requested scene does not exist");
|
||||
}
|
||||
|
||||
obs_source_release(source);
|
||||
}
|
||||
|
||||
// Indirectly causes memory leaks
|
||||
void WSRequestHandler::HandleGetCurrentScene(WSRequestHandler *owner) {
|
||||
void WSRequestHandler::HandleGetCurrentScene(WSRequestHandler *owner)
|
||||
{
|
||||
obs_source_t *current_scene = obs_frontend_get_current_scene();
|
||||
const char *name = obs_source_get_name(current_scene);
|
||||
|
||||
obs_data_array_t *scene_items = Utils::GetSceneItems(current_scene); // Causes memory leaks
|
||||
obs_data_array_t *scene_items = Utils::GetSceneItems(current_scene);
|
||||
|
||||
obs_data_t *data = obs_data_create();
|
||||
obs_data_set_string(data, "name", name);
|
||||
@ -217,7 +237,8 @@ void WSRequestHandler::HandleGetCurrentScene(WSRequestHandler *owner) {
|
||||
obs_source_release(current_scene);
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleGetSceneList(WSRequestHandler *owner) {
|
||||
void WSRequestHandler::HandleGetSceneList(WSRequestHandler *owner)
|
||||
{
|
||||
obs_source_t *current_scene = obs_frontend_get_current_scene();
|
||||
obs_data_array_t *scenes = Utils::GetScenes();
|
||||
|
||||
@ -232,30 +253,40 @@ void WSRequestHandler::HandleGetSceneList(WSRequestHandler *owner) {
|
||||
obs_source_release(current_scene);
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleSetSourceRender(WSRequestHandler *owner) {
|
||||
void WSRequestHandler::HandleSetSourceRender(WSRequestHandler *owner)
|
||||
{
|
||||
const char *itemName = obs_data_get_string(owner->_requestData, "source");
|
||||
bool isVisible = obs_data_get_bool(owner->_requestData, "render");
|
||||
if (itemName == NULL) {
|
||||
if (itemName == NULL)
|
||||
{
|
||||
owner->SendErrorResponse("invalid request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
obs_source_t* currentScene = obs_frontend_get_current_scene();
|
||||
const char *sceneName = obs_data_get_string(owner->_requestData, "scene-name");
|
||||
obs_source_t *scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (scene == NULL) {
|
||||
owner->SendErrorResponse("requested scene doesn't exist");
|
||||
return;
|
||||
}
|
||||
|
||||
obs_sceneitem_t *sceneItem = Utils::GetSceneItemFromName(currentScene, itemName);
|
||||
if (sceneItem != NULL) {
|
||||
obs_sceneitem_t *sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||
if (sceneItem != NULL)
|
||||
{
|
||||
obs_sceneitem_set_visible(sceneItem, isVisible);
|
||||
obs_sceneitem_release(sceneItem);
|
||||
owner->SendOKResponse();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
owner->SendErrorResponse("specified scene item doesn't exist");
|
||||
}
|
||||
|
||||
obs_source_release(currentScene);
|
||||
obs_source_release(scene);
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler *owner) {
|
||||
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());
|
||||
@ -265,35 +296,43 @@ void WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler *owner) {
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleStartStopStreaming(WSRequestHandler *owner) {
|
||||
if (obs_frontend_streaming_active()) {
|
||||
void WSRequestHandler::HandleStartStopStreaming(WSRequestHandler *owner)
|
||||
{
|
||||
if (obs_frontend_streaming_active())
|
||||
{
|
||||
obs_frontend_streaming_stop();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
obs_frontend_streaming_start();
|
||||
}
|
||||
|
||||
owner->SendOKResponse();
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleStartStopRecording(WSRequestHandler *owner) {
|
||||
if (obs_frontend_recording_active()) {
|
||||
void WSRequestHandler::HandleStartStopRecording(WSRequestHandler *owner)
|
||||
{
|
||||
if (obs_frontend_recording_active())
|
||||
{
|
||||
obs_frontend_recording_stop();
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
obs_frontend_recording_start();
|
||||
}
|
||||
|
||||
owner->SendOKResponse();
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleGetTransitionList(WSRequestHandler *owner) {
|
||||
void WSRequestHandler::HandleGetTransitionList(WSRequestHandler *owner)
|
||||
{
|
||||
obs_source_t *current_transition = obs_frontend_get_current_transition();
|
||||
obs_frontend_source_list transitionList = {};
|
||||
obs_frontend_get_transitions(&transitionList);
|
||||
|
||||
obs_data_array_t* transitions = obs_data_array_create();
|
||||
for (size_t i = 0; i < transitionList.sources.num; i++) {
|
||||
for (size_t i = 0; i < transitionList.sources.num; i++)
|
||||
{
|
||||
obs_source_t* transition = transitionList.sources.array[i];
|
||||
|
||||
obs_data_t *obj = obs_data_create();
|
||||
@ -315,33 +354,297 @@ void WSRequestHandler::HandleGetTransitionList(WSRequestHandler *owner) {
|
||||
obs_source_release(current_transition);
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler *owner) {
|
||||
void WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler *owner)
|
||||
{
|
||||
obs_source_t *current_transition = obs_frontend_get_current_transition();
|
||||
|
||||
obs_data_t *response = obs_data_create();
|
||||
obs_data_set_string(response, "name", obs_source_get_name(current_transition));
|
||||
|
||||
if (!obs_transition_fixed(current_transition))
|
||||
{
|
||||
obs_data_set_int(response, "duration", Utils::GetTransitionDuration());
|
||||
}
|
||||
|
||||
owner->SendOKResponse(response);
|
||||
|
||||
obs_data_release(response);
|
||||
obs_source_release(current_transition);
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleSetCurrentTransition(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) {
|
||||
if (transition)
|
||||
{
|
||||
obs_frontend_set_current_transition(transition);
|
||||
owner->SendOKResponse();
|
||||
|
||||
obs_source_release(transition);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
owner->SendErrorResponse("requested transition does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
void WSRequestHandler::ErrNotImplemented(WSRequestHandler *owner) {
|
||||
void WSRequestHandler::HandleSetTransitionDuration(WSRequestHandler *owner)
|
||||
{
|
||||
int ms = obs_data_get_int(owner->_requestData, "duration");
|
||||
Utils::SetTransitionDuration(ms);
|
||||
owner->SendOKResponse();
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleSetVolume(WSRequestHandler *owner)
|
||||
{
|
||||
const char *item_name = obs_data_get_string(owner->_requestData, "source");
|
||||
float item_volume = obs_data_get_double(owner->_requestData, "volume");
|
||||
|
||||
if (item_name == NULL || item_volume < 0.0 || item_volume > 1.0)
|
||||
{
|
||||
owner->SendErrorResponse("invalid request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
obs_source_t* item = obs_get_source_by_name(item_name);
|
||||
if (!item)
|
||||
{
|
||||
owner->SendErrorResponse("specified source doesn't exist");
|
||||
return;
|
||||
}
|
||||
|
||||
obs_source_set_volume(item, item_volume);
|
||||
owner->SendOKResponse();
|
||||
|
||||
obs_source_release(item);
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleGetVolume(WSRequestHandler *owner)
|
||||
{
|
||||
const char *item_name = obs_data_get_string(owner->_requestData, "source");
|
||||
|
||||
if (item_name)
|
||||
{
|
||||
obs_source_t* item = obs_get_source_by_name(item_name);
|
||||
|
||||
obs_data_t* response = obs_data_create();
|
||||
obs_data_set_string(response, "name", item_name);
|
||||
obs_data_set_double(response, "volume", obs_source_get_volume(item));
|
||||
obs_data_set_bool(response, "muted", obs_source_muted(item));
|
||||
|
||||
owner->SendOKResponse(response);
|
||||
|
||||
obs_data_release(response);
|
||||
obs_source_release(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
owner->SendErrorResponse("invalid request parameters");
|
||||
}
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleToggleMute(WSRequestHandler *owner) {
|
||||
const char *item_name = obs_data_get_string(owner->_requestData, "source");
|
||||
if (item_name == NULL)
|
||||
{
|
||||
owner->SendErrorResponse("invalid request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
obs_source_t* item = obs_get_source_by_name(item_name);
|
||||
if (!item)
|
||||
{
|
||||
owner->SendErrorResponse("invalid request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
obs_source_set_muted(item, !obs_source_muted(item));
|
||||
owner->SendOKResponse();
|
||||
|
||||
obs_source_release(item);
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleSetMute(WSRequestHandler *owner)
|
||||
{
|
||||
const char *item_name = obs_data_get_string(owner->_requestData, "source");
|
||||
bool mute = obs_data_get_bool(owner->_requestData, "mute");
|
||||
if (item_name == NULL)
|
||||
{
|
||||
owner->SendErrorResponse("invalid request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
obs_source_t* item = obs_get_source_by_name(item_name);
|
||||
if (!item)
|
||||
{
|
||||
owner->SendErrorResponse("specified source doesn't exist");
|
||||
return;
|
||||
}
|
||||
|
||||
obs_source_set_muted(item, mute);
|
||||
owner->SendOKResponse();
|
||||
|
||||
obs_source_release(item);
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleSetSceneItemPosition(WSRequestHandler *owner)
|
||||
{
|
||||
const char *item_name = obs_data_get_string(owner->_requestData, "item");
|
||||
if (!item_name)
|
||||
{
|
||||
owner->SendErrorResponse("invalid request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
const char *scene_name = obs_data_get_string(owner->_requestData, "scene-name");
|
||||
obs_source_t *scene = Utils::GetSceneFromNameOrCurrent(scene_name);
|
||||
if (scene == NULL) {
|
||||
owner->SendErrorResponse("requested scene could not be found");
|
||||
return;
|
||||
}
|
||||
|
||||
vec2 item_position = {0};
|
||||
item_position.x = obs_data_get_double(owner->_requestData, "x");
|
||||
item_position.y = obs_data_get_double(owner->_requestData, "y");
|
||||
|
||||
obs_sceneitem_t *scene_item = Utils::GetSceneItemFromName(scene, item_name);
|
||||
|
||||
if (scene_item)
|
||||
{
|
||||
obs_sceneitem_set_pos(scene_item, &item_position);
|
||||
|
||||
obs_sceneitem_release(scene_item);
|
||||
owner->SendOKResponse();
|
||||
}
|
||||
else
|
||||
{
|
||||
owner->SendErrorResponse("specified scene item doesn't exist");
|
||||
}
|
||||
|
||||
obs_source_release(scene);
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler *owner)
|
||||
{
|
||||
const char *item_name = obs_data_get_string(owner->_requestData, "item");
|
||||
if (!item_name)
|
||||
{
|
||||
owner->SendErrorResponse("invalid request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
const char *scene_name = obs_data_get_string(owner->_requestData, "scene-name");
|
||||
obs_source_t* scene = Utils::GetSceneFromNameOrCurrent(scene_name);
|
||||
if (scene == NULL) {
|
||||
owner->SendErrorResponse("requested scene doesn't exist");
|
||||
return;
|
||||
}
|
||||
|
||||
vec2 scale;
|
||||
scale.x = obs_data_get_double(owner->_requestData, "x-scale");
|
||||
scale.y = obs_data_get_double(owner->_requestData, "y-scale");
|
||||
|
||||
float rotation = obs_data_get_double(owner->_requestData, "rotation");
|
||||
|
||||
obs_sceneitem_t *scene_item = Utils::GetSceneItemFromName(scene, item_name);
|
||||
|
||||
if (scene_item)
|
||||
{
|
||||
obs_sceneitem_set_scale(scene_item, &scale);
|
||||
obs_sceneitem_set_rot(scene_item, rotation);
|
||||
|
||||
obs_sceneitem_release(scene_item);
|
||||
owner->SendOKResponse();
|
||||
}
|
||||
else
|
||||
{
|
||||
owner->SendErrorResponse("specified scene item doesn't exist");
|
||||
}
|
||||
|
||||
obs_source_release(scene);
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleSetCurrentSceneCollection(WSRequestHandler *owner)
|
||||
{
|
||||
const char* scene_collection = obs_data_get_string(owner->_requestData, "sc-name");
|
||||
|
||||
if (scene_collection)
|
||||
{
|
||||
// TODO : Check if profile exists
|
||||
obs_frontend_set_current_scene_collection(scene_collection);
|
||||
owner->SendOKResponse();
|
||||
}
|
||||
else
|
||||
{
|
||||
owner->SendErrorResponse("invalid request parameters");
|
||||
}
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleGetCurrentSceneCollection(WSRequestHandler *owner)
|
||||
{
|
||||
obs_data_t *response = obs_data_create();
|
||||
obs_data_set_string(response, "sc-name", obs_frontend_get_current_scene_collection());
|
||||
|
||||
owner->SendOKResponse(response);
|
||||
|
||||
obs_data_release(response);
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleListSceneCollections(WSRequestHandler *owner)
|
||||
{
|
||||
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)
|
||||
{
|
||||
const char* profile_name = obs_data_get_string(owner->_requestData, "profile-name");
|
||||
|
||||
if (profile_name)
|
||||
{
|
||||
// TODO : check if profile exists
|
||||
obs_frontend_set_current_profile(profile_name);
|
||||
owner->SendOKResponse();
|
||||
}
|
||||
else
|
||||
{
|
||||
owner->SendErrorResponse("invalid request parameters");
|
||||
}
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleGetCurrentProfile(WSRequestHandler *owner)
|
||||
{
|
||||
obs_data_t *response = obs_data_create();
|
||||
obs_data_set_string(response, "profile-name", obs_frontend_get_current_profile());
|
||||
|
||||
owner->SendOKResponse(response);
|
||||
|
||||
obs_data_release(response);
|
||||
}
|
||||
|
||||
void WSRequestHandler::HandleListProfiles(WSRequestHandler *owner)
|
||||
{
|
||||
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)
|
||||
{
|
||||
owner->SendErrorResponse("not implemented");
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2017 Mikhail Swift <https://github.com/mikhailswift>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -19,10 +20,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#ifndef WSREQUESTHANDLER_H
|
||||
#define WSREQUESTHANDLER_H
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <QtWebSockets/QWebSocket>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QtWebSockets/QWebSocket>
|
||||
#include <QtWebSockets/QWebSocketServer>
|
||||
|
||||
class WSRequestHandler : public QObject
|
||||
{
|
||||
@ -31,25 +31,16 @@ class WSRequestHandler : public QObject
|
||||
public:
|
||||
explicit WSRequestHandler(QWebSocket *client);
|
||||
~WSRequestHandler();
|
||||
void sendTextMessage(QString textMessage);
|
||||
bool isAuthenticated();
|
||||
|
||||
private Q_SLOTS:
|
||||
void processTextMessage(QString textMessage);
|
||||
void socketDisconnected();
|
||||
|
||||
Q_SIGNALS:
|
||||
void disconnected();
|
||||
void processIncomingMessage(QString textMessage);
|
||||
|
||||
private:
|
||||
QWebSocket *_client;
|
||||
bool _authenticated;
|
||||
const char *_messageId;
|
||||
const char *_requestType;
|
||||
obs_data_t *_requestData;
|
||||
|
||||
std::map<std::string, void(*)(WSRequestHandler*)> messageMap;
|
||||
std::set<std::string> authNotRequired;
|
||||
QMap<QString, void(*)(WSRequestHandler*)> messageMap;
|
||||
QSet<QString> authNotRequired;
|
||||
|
||||
void SendOKResponse(obs_data_t *additionalFields = NULL);
|
||||
void SendErrorResponse(const char *errorMessage);
|
||||
@ -62,7 +53,10 @@ class WSRequestHandler : public QObject
|
||||
static void HandleSetCurrentScene(WSRequestHandler *owner);
|
||||
static void HandleGetCurrentScene(WSRequestHandler *owner);
|
||||
static void HandleGetSceneList(WSRequestHandler *owner);
|
||||
|
||||
static void HandleSetSourceRender(WSRequestHandler *owner);
|
||||
static void HandleSetSceneItemPosition(WSRequestHandler *owner);
|
||||
static void HandleSetSceneItemTransform(WSRequestHandler *owner);
|
||||
|
||||
static void HandleGetStreamingStatus(WSRequestHandler *owner);
|
||||
static void HandleStartStopStreaming(WSRequestHandler *owner);
|
||||
@ -71,6 +65,21 @@ class WSRequestHandler : public QObject
|
||||
static void HandleGetTransitionList(WSRequestHandler *owner);
|
||||
static void HandleGetCurrentTransition(WSRequestHandler *owner);
|
||||
static void HandleSetCurrentTransition(WSRequestHandler *owner);
|
||||
|
||||
static void HandleSetVolume(WSRequestHandler *owner);
|
||||
static void HandleGetVolume(WSRequestHandler *owner);
|
||||
static void HandleToggleMute(WSRequestHandler *owner);
|
||||
static void HandleSetMute(WSRequestHandler *owner);
|
||||
|
||||
static void HandleSetCurrentSceneCollection(WSRequestHandler *owner);
|
||||
static void HandleGetCurrentSceneCollection(WSRequestHandler *owner);
|
||||
static void HandleListSceneCollections(WSRequestHandler *owner);
|
||||
|
||||
static void HandleSetCurrentProfile(WSRequestHandler *owner);
|
||||
static void HandleGetCurrentProfile(WSRequestHandler *owner);
|
||||
static void HandleListProfiles(WSRequestHandler *owner);
|
||||
|
||||
static void HandleSetTransitionDuration(WSRequestHandler *owner);
|
||||
};
|
||||
|
||||
#endif // WSPROTOCOL_H
|
||||
|
113
WSServer.cpp
113
WSServer.cpp
@ -1,6 +1,6 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -16,73 +16,134 @@ 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 "WSServer.h"
|
||||
#include "WSRequestHandler.h"
|
||||
#include "Config.h"
|
||||
#include <QtWebSockets/QWebSocketServer>
|
||||
#include <QtWebSockets/QWebSocket>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
#include "WSServer.h"
|
||||
#include "obs-websocket.h"
|
||||
#include "Config.h"
|
||||
|
||||
QT_USE_NAMESPACE
|
||||
|
||||
WSServer::WSServer(quint16 port, QObject *parent) :
|
||||
WSServer* WSServer::Instance = new WSServer();
|
||||
|
||||
WSServer::WSServer(QObject *parent) :
|
||||
QObject(parent),
|
||||
_wsServer(Q_NULLPTR),
|
||||
_clients()
|
||||
_clients(),
|
||||
_clMutex(QMutex::Recursive)
|
||||
{
|
||||
_serverThread = new QThread();
|
||||
|
||||
_wsServer = new QWebSocketServer(
|
||||
QStringLiteral("OBS Websocket API"),
|
||||
QStringLiteral("obs-websocket"),
|
||||
QWebSocketServer::NonSecureMode,
|
||||
this);
|
||||
|
||||
_wsServer->moveToThread(_serverThread);
|
||||
_serverThread->start();
|
||||
|
||||
bool serverStarted = _wsServer->listen(QHostAddress::Any, port);
|
||||
if (serverStarted) {
|
||||
connect(_wsServer, &QWebSocketServer::newConnection, this, &WSServer::onNewConnection);
|
||||
}
|
||||
}
|
||||
|
||||
WSServer::~WSServer()
|
||||
{
|
||||
Stop();
|
||||
|
||||
delete _serverThread;
|
||||
}
|
||||
|
||||
void WSServer::Start(quint16 port)
|
||||
{
|
||||
if (port == _wsServer->serverPort())
|
||||
return;
|
||||
|
||||
if(_wsServer->isListening())
|
||||
Stop();
|
||||
|
||||
bool serverStarted = _wsServer->listen(QHostAddress::Any, port);
|
||||
if (serverStarted)
|
||||
{
|
||||
connect(_wsServer, &QWebSocketServer::newConnection, this, &WSServer::onNewConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::Stop()
|
||||
{
|
||||
_clMutex.lock();
|
||||
Q_FOREACH(QWebSocket *pClient, _clients)
|
||||
{
|
||||
pClient->close();
|
||||
}
|
||||
_clMutex.unlock();
|
||||
|
||||
_wsServer->close();
|
||||
qDeleteAll(_clients.begin(), _clients.end());
|
||||
}
|
||||
|
||||
void WSServer::broadcast(QString message)
|
||||
{
|
||||
Q_FOREACH(WSRequestHandler *pClient, _clients) {
|
||||
if (Config::Current()->AuthRequired == true
|
||||
&& pClient->isAuthenticated() == false) {
|
||||
_clMutex.lock();
|
||||
|
||||
Q_FOREACH(QWebSocket *pClient, _clients)
|
||||
{
|
||||
if (Config::Current()->AuthRequired
|
||||
&& (pClient->property(PROP_AUTHENTICATED).toBool() == false))
|
||||
{
|
||||
// Skip this client if unauthenticated
|
||||
continue;
|
||||
}
|
||||
|
||||
pClient->sendTextMessage(message);
|
||||
}
|
||||
|
||||
_clMutex.unlock();
|
||||
}
|
||||
|
||||
void WSServer::onNewConnection()
|
||||
{
|
||||
QWebSocket *pSocket = _wsServer->nextPendingConnection();
|
||||
|
||||
if (pSocket) {
|
||||
WSRequestHandler *pHandler = new WSRequestHandler(pSocket);
|
||||
if (pSocket)
|
||||
{
|
||||
connect(pSocket, &QWebSocket::textMessageReceived, this, &WSServer::textMessageReceived);
|
||||
connect(pSocket, &QWebSocket::disconnected, this, &WSServer::socketDisconnected);
|
||||
pSocket->setProperty(PROP_AUTHENTICATED, false);
|
||||
|
||||
connect(pHandler, &WSRequestHandler::disconnected, this, &WSServer::socketDisconnected);
|
||||
_clients << pHandler;
|
||||
_clMutex.lock();
|
||||
_clients << pSocket;
|
||||
_clMutex.unlock();
|
||||
|
||||
QByteArray client_ip = pSocket->peerAddress().toString().toUtf8();
|
||||
blog(LOG_INFO, "new client connection from %s:%d", client_ip.constData(), pSocket->peerPort());
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::textMessageReceived(QString message)
|
||||
{
|
||||
QWebSocket *pSocket = qobject_cast<QWebSocket *>(sender());
|
||||
|
||||
if (pSocket)
|
||||
{
|
||||
WSRequestHandler handler(pSocket);
|
||||
handler.processIncomingMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::socketDisconnected()
|
||||
{
|
||||
WSRequestHandler *pClient = qobject_cast<WSRequestHandler *>(sender());
|
||||
QWebSocket *pSocket = qobject_cast<QWebSocket *>(sender());
|
||||
|
||||
if (pClient) {
|
||||
_clients.removeAll(pClient);
|
||||
pClient->deleteLater();
|
||||
if (pSocket)
|
||||
{
|
||||
pSocket->setProperty(PROP_AUTHENTICATED, false);
|
||||
|
||||
_clMutex.lock();
|
||||
_clients.removeAll(pSocket);
|
||||
_clMutex.unlock();
|
||||
|
||||
pSocket->deleteLater();
|
||||
|
||||
QByteArray client_ip = pSocket->peerAddress().toString().toUtf8();
|
||||
blog(LOG_INFO, "client %s:%d disconnected", client_ip.constData(), pSocket->peerPort());
|
||||
}
|
||||
}
|
16
WSServer.h
16
WSServer.h
@ -1,6 +1,6 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -21,28 +21,34 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QByteArray>
|
||||
#include "WSRequestHandler.h"
|
||||
#include <QtCore/QMutex>
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
|
||||
QT_FORWARD_DECLARE_CLASS(QWebSocket)
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
class WSServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WSServer(quint16 port, QObject *parent = Q_NULLPTR);
|
||||
explicit WSServer(QObject *parent = Q_NULLPTR);
|
||||
virtual ~WSServer();
|
||||
void Start(quint16 port);
|
||||
void Stop();
|
||||
void broadcast(QString message);
|
||||
static WSServer* Instance;
|
||||
|
||||
private Q_SLOTS:
|
||||
void onNewConnection();
|
||||
void textMessageReceived(QString message);
|
||||
void socketDisconnected();
|
||||
|
||||
private:
|
||||
QWebSocketServer *_wsServer;
|
||||
QList<WSRequestHandler *> _clients;
|
||||
QList<QWebSocket *> _clients;
|
||||
QMutex _clMutex;
|
||||
QThread *_serverThread;
|
||||
};
|
||||
|
||||
|
50
appveyor.yml
Normal file
50
appveyor.yml
Normal file
@ -0,0 +1,50 @@
|
||||
environment:
|
||||
CURL_VERSION: 7.39.0
|
||||
|
||||
install:
|
||||
- git submodule update --init --recursive
|
||||
- cd C:\projects\
|
||||
- if not exist dependencies2013.zip curl -kLO https://obsproject.com/downloads/dependencies2013.zip -f --retry 5 -C -
|
||||
- 7z x dependencies2013.zip -odependencies2013
|
||||
- if not exist qt570.zip curl -kLO https://www.slepin.fr/obs-websocket/ci/qt570.zip -f --retry 5 -C -
|
||||
- 7z x qt570.zip -o"Qt5.7.0"
|
||||
- set DepsPath32=%CD%\dependencies2013\win32
|
||||
- set DepsPath64=%CD%\dependencies2013\win64
|
||||
- set QTDIR32=%CD%\Qt5.7.0\msvc2013
|
||||
- set QTDIR64=%CD%\Qt5.7.0\msvc2013_64
|
||||
- set build_config=Release
|
||||
- git clone --recursive https://github.com/jp9000/obs-studio
|
||||
- cd C:\projects\obs-studio\
|
||||
- git checkout 18.0.0
|
||||
- mkdir build
|
||||
- mkdir build32
|
||||
- mkdir build64
|
||||
- cd ./build32
|
||||
- cmake -G "Visual Studio 12 2013" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
||||
- cd ../build64
|
||||
- cmake -G "Visual Studio 12 2013 Win64" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
||||
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build32\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build64\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||
- cd C:\projects\obs-websocket\
|
||||
- mkdir build32
|
||||
- mkdir build64
|
||||
- cd ./build32
|
||||
- cmake -G "Visual Studio 12 2013" -DQTDIR="%QTDIR32%" -DLibObs_DIR="C:\projects\obs-studio\build32\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
|
||||
- cd ../build64
|
||||
- cmake -G "Visual Studio 12 2013 Win64" -DQTDIR="%QTDIR64%" -DLibObs_DIR="C:\projects\obs-studio\build64\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
|
||||
|
||||
build_script:
|
||||
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build32\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build64\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||
|
||||
before_deploy:
|
||||
- 7z a "C:\projects\obs-websocket\build.zip" C:\projects\obs-websocket\release\*
|
||||
|
||||
deploy_script:
|
||||
- ps: Push-AppveyorArtifact "C:\projects\obs-websocket\build.zip" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1).zip"
|
||||
|
||||
test: off
|
||||
|
||||
cache:
|
||||
- C:\projects\dependencies2013.zip
|
||||
- C:\projects\qt570.zip
|
6
data/locale/de-DE.ini
Normal file
6
data/locale/de-DE.ini
Normal file
@ -0,0 +1,6 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Websocket-Server Einstellungen"
|
||||
OBSWebsocket.Settings.DialogTitle="Websocket-Server Einstellungen"
|
||||
OBSWebsocket.Settings.ServerEnable="Websocket-Server aktivieren"
|
||||
OBSWebsocket.Settings.ServerPort="Server Port"
|
||||
OBSWebsocket.Settings.AuthRequired="Authentifizierung erforderlich"
|
||||
OBSWebsocket.Settings.Password="Passwort"
|
@ -1,4 +1,6 @@
|
||||
Menu.SettingsItem="Websocket server settings"
|
||||
Settings.DialogTitle="obs-websocket"
|
||||
Settings.AuthRequired="Enable authentication"
|
||||
Settings.Password="Password"
|
||||
OBSWebsocket.Menu.SettingsItem="Websocket server settings"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Enable Websocket server"
|
||||
OBSWebsocket.Settings.ServerPort="Server Port"
|
||||
OBSWebsocket.Settings.AuthRequired="Enable authentication"
|
||||
OBSWebsocket.Settings.Password="Password"
|
@ -1,4 +1,6 @@
|
||||
Menu.SettingsItem="Paramètres du serveur Websocket"
|
||||
Settings.DialogTitle="obs-websocket"
|
||||
Settings.AuthRequired="Activer l'authentification"
|
||||
Settings.Password="Mot de passe"
|
||||
OBSWebsocket.Menu.SettingsItem="Paramètres du serveur Websocket"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Activer le serveur Websockets"
|
||||
OBSWebsocket.Settings.ServerPort="Port du serveur"
|
||||
OBSWebsocket.Settings.AuthRequired="Activer l'authentification"
|
||||
OBSWebsocket.Settings.Password="Mot de passe"
|
6
data/locale/zh-CN.ini
Normal file
6
data/locale/zh-CN.ini
Normal file
@ -0,0 +1,6 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Websocket 服务器设置"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket 设置"
|
||||
OBSWebsocket.Settings.ServerEnable="启用 Websocket 服务器"
|
||||
OBSWebsocket.Settings.ServerPort="服务器端口"
|
||||
OBSWebsocket.Settings.AuthRequired="启用密码认证"
|
||||
OBSWebsocket.Settings.Password="密码"
|
6
data/locale/zh-TW.ini
Normal file
6
data/locale/zh-TW.ini
Normal file
@ -0,0 +1,6 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Websocket 伺服器設定"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket 設定"
|
||||
OBSWebsocket.Settings.ServerEnable="啟用 Websocket 伺服器"
|
||||
OBSWebsocket.Settings.ServerPort="伺服器端口"
|
||||
OBSWebsocket.Settings.AuthRequired="啟用密碼認證"
|
||||
OBSWebsocket.Settings.Password="密碼"
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -17,9 +17,12 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
#include "obs-websocket.h"
|
||||
#include "Config.h"
|
||||
#include "WSServer.h"
|
||||
#include "settings-dialog.h"
|
||||
#include "ui_settings-dialog.h"
|
||||
#include "Config.h"
|
||||
|
||||
#define CHANGE_ME "changeme"
|
||||
|
||||
@ -35,12 +38,19 @@ SettingsDialog::SettingsDialog(QWidget *parent) :
|
||||
AuthCheckboxChanged();
|
||||
}
|
||||
|
||||
void SettingsDialog::showEvent(QShowEvent *event) {
|
||||
ui->authRequired->setChecked(Config::Current()->AuthRequired);
|
||||
void SettingsDialog::showEvent(QShowEvent *event)
|
||||
{
|
||||
Config* conf = Config::Current();
|
||||
|
||||
ui->serverEnabled->setChecked(conf->ServerEnabled);
|
||||
ui->serverPort->setValue(conf->ServerPort);
|
||||
|
||||
ui->authRequired->setChecked(conf->AuthRequired);
|
||||
ui->password->setText(CHANGE_ME);
|
||||
}
|
||||
|
||||
void SettingsDialog::ToggleShowHide() {
|
||||
void SettingsDialog::ToggleShowHide()
|
||||
{
|
||||
if (!isVisible()) {
|
||||
setVisible(true);
|
||||
}
|
||||
@ -49,7 +59,8 @@ void SettingsDialog::ToggleShowHide() {
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsDialog::AuthCheckboxChanged() {
|
||||
void SettingsDialog::AuthCheckboxChanged()
|
||||
{
|
||||
if (ui->authRequired->isChecked()) {
|
||||
ui->password->setEnabled(true);
|
||||
}
|
||||
@ -58,28 +69,47 @@ void SettingsDialog::AuthCheckboxChanged() {
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsDialog::FormAccepted() {
|
||||
if (ui->authRequired->isChecked()) {
|
||||
if (ui->password->text() != CHANGE_ME) {
|
||||
QByteArray pwd = ui->password->text().toLocal8Bit();
|
||||
void SettingsDialog::FormAccepted()
|
||||
{
|
||||
Config* conf = Config::Current();
|
||||
|
||||
conf->ServerEnabled = ui->serverEnabled->isChecked();
|
||||
conf->ServerPort = ui->serverPort->value();
|
||||
|
||||
if (ui->authRequired->isChecked())
|
||||
{
|
||||
if (ui->password->text() != CHANGE_ME)
|
||||
{
|
||||
QByteArray pwd = ui->password->text().toUtf8();
|
||||
const char *new_password = pwd;
|
||||
|
||||
blog(LOG_INFO, "new password : %s", new_password);
|
||||
Config::Current()->SetPassword(new_password);
|
||||
conf->SetPassword(new_password);
|
||||
}
|
||||
|
||||
if (strcmp(Config::Current()->Secret, "") != 0) {
|
||||
Config::Current()->AuthRequired = true;
|
||||
if (strcmp(Config::Current()->Secret, "") != 0)
|
||||
{
|
||||
conf->AuthRequired = true;
|
||||
}
|
||||
else {
|
||||
Config::Current()->AuthRequired = false;
|
||||
else
|
||||
{
|
||||
conf->AuthRequired = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
Config::Current()->AuthRequired = false;
|
||||
else
|
||||
{
|
||||
conf->AuthRequired = false;
|
||||
}
|
||||
|
||||
obs_frontend_save();
|
||||
conf->Save();
|
||||
|
||||
if (conf->ServerEnabled)
|
||||
{
|
||||
WSServer::Instance->Start(conf->ServerPort);
|
||||
}
|
||||
else
|
||||
{
|
||||
WSServer::Instance->Stop();
|
||||
}
|
||||
}
|
||||
|
||||
SettingsDialog::~SettingsDialog()
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -6,8 +6,8 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>354</width>
|
||||
<height>110</height>
|
||||
<width>407</width>
|
||||
<height>155</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -17,7 +17,7 @@
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Settings.DialogTitle</string>
|
||||
<string>OBSWebsocket.Settings.DialogTitle</string>
|
||||
</property>
|
||||
<property name="sizeGripEnabled">
|
||||
<bool>false</bool>
|
||||
@ -28,24 +28,54 @@
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="lbl_password">
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="authRequired">
|
||||
<property name="text">
|
||||
<string>Settings.Password</string>
|
||||
<string>OBSWebsocket.Settings.AuthRequired</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="lbl_password">
|
||||
<property name="text">
|
||||
<string>OBSWebsocket.Settings.Password</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="password">
|
||||
<property name="echoMode">
|
||||
<enum>QLineEdit::Password</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="authRequired">
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="serverEnabled">
|
||||
<property name="text">
|
||||
<string>Settings.AuthRequired</string>
|
||||
<string>OBSWebsocket.Settings.ServerEnable</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="lbl_serverPort">
|
||||
<property name="text">
|
||||
<string>OBSWebsocket.Settings.ServerPort</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QSpinBox" name="serverPort">
|
||||
<property name="minimum">
|
||||
<number>1024</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>65535</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>4444</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -2,7 +2,7 @@
|
||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||
|
||||
#define MyAppName "obs-websocket"
|
||||
#define MyAppVersion "0.3.1"
|
||||
#define MyAppVersion "4.0.0"
|
||||
#define MyAppPublisher "St<53>phane Lepin"
|
||||
#define MyAppURL "http://github.com/Palakis/obs-websocket"
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -19,10 +19,12 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include <obs-module.h>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QAction>
|
||||
#include <QMainWindow>
|
||||
#include <QTimer>
|
||||
|
||||
#include "obs-websocket.h"
|
||||
#include "WSEvents.h"
|
||||
#include "WSServer.h"
|
||||
#include "WSEvents.h"
|
||||
#include "Config.h"
|
||||
#include "forms/settings-dialog.h"
|
||||
|
||||
@ -30,22 +32,27 @@ OBS_DECLARE_MODULE()
|
||||
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
|
||||
|
||||
WSEvents *eventHandler;
|
||||
WSServer *server;
|
||||
SettingsDialog *settings_dialog;
|
||||
|
||||
bool obs_module_load(void)
|
||||
{
|
||||
blog(LOG_INFO, "[obs-websockets] you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION);
|
||||
blog(LOG_INFO, "you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION);
|
||||
|
||||
server = new WSServer(4444);
|
||||
eventHandler = new WSEvents(server);
|
||||
// Core setup
|
||||
Config* config = Config::Current();
|
||||
config->Load();
|
||||
|
||||
obs_frontend_add_save_callback(Config::OBSSaveCallback, Config::Current());
|
||||
if (config->ServerEnabled)
|
||||
WSServer::Instance->Start(config->ServerPort);
|
||||
|
||||
QAction *menu_action = (QAction*)obs_frontend_add_tools_menu_qaction(obs_module_text("Menu.SettingsItem"));
|
||||
eventHandler = new WSEvents(WSServer::Instance);
|
||||
|
||||
// UI setup
|
||||
QAction *menu_action = (QAction*)obs_frontend_add_tools_menu_qaction(obs_module_text("OBSWebsocket.Menu.SettingsItem"));
|
||||
|
||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||
settings_dialog = new SettingsDialog();
|
||||
QMainWindow* main_window = (QMainWindow*)obs_frontend_get_main_window();
|
||||
settings_dialog = new SettingsDialog(main_window);
|
||||
obs_frontend_pop_ui_translation();
|
||||
|
||||
auto menu_cb = [] {
|
||||
@ -53,11 +60,14 @@ bool obs_module_load(void)
|
||||
};
|
||||
menu_action->connect(menu_action, &QAction::triggered, menu_cb);
|
||||
|
||||
// Loading finished
|
||||
blog(LOG_INFO, "module loaded!");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void obs_module_unload()
|
||||
{
|
||||
blog(LOG_INFO, "[obs-websockets] goodbye !");
|
||||
blog(LOG_INFO, "goodbye!");
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@ -19,6 +19,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#ifndef OBSWEBSOCKET_H
|
||||
#define OBSWEBSOCKET_H
|
||||
|
||||
#define OBS_WEBSOCKET_VERSION "0.3.2"
|
||||
#define PROP_AUTHENTICATED "wsclient_authenticated"
|
||||
#define OBS_WEBSOCKET_VERSION "4.0.0"
|
||||
|
||||
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)
|
||||
|
||||
#endif // OBSWEBSOCKET_H
|
Reference in New Issue
Block a user