Compare commits

...

106 Commits
0.3.2 ... 4.0.0

Author SHA1 Message Date
0672b6a157 Fixed typo in PROTOCOL.md 2017-03-02 15:55:46 +01:00
1e71bfa151 Fixed a crash when switching to another scene after switching to another scene collection 2017-03-02 15:36:48 +01:00
d4c2c8197a Merge branch 'master' of github.com:Palakis/obs-websocket 2017-03-02 15:07:11 +01:00
c9baed2df9 Fixes #24 2017-03-02 15:02:26 +01:00
1e2065c84a Merge pull request #50 from G-monitor/chinese
Add chinese translation
2017-03-02 14:24:28 +01:00
537d683dfb Add chinese translation 2017-03-02 17:46:10 +08:00
e6f1b9f8c8 Travis: trying something 2017-03-02 01:08:57 +01:00
5815d2bfce Lower minimum cmake version 2017-03-02 00:49:35 +01:00
7692e93306 Travis: install Qt Websockets from source 2017-03-02 00:44:23 +01:00
8768b83251 Added execute permission to Travis CI scripts 2017-03-02 00:29:36 +01:00
35d18810fc Setting up Travis CI 2017-03-02 00:25:58 +01:00
ccd40a1834 Update README.md 2017-03-01 23:29:13 +01:00
5c1f0c3541 CI: Move cached deps outside of obs-studio source dir 2017-03-01 22:46:17 +01:00
e237e52ae4 CI: Force Qt 5.7.0 like OBS Releases 2017-03-01 22:43:29 +01:00
effec90528 More fixes for appveyor 2017-03-01 22:13:34 +01:00
de4a2247c0 CI: Change MSVC version 2017-03-01 22:06:07 +01:00
b20b7cbc98 Appveyor artifact name change 2017-03-01 22:02:06 +01:00
3c347b9b77 Oops, typo 2017-03-01 21:50:20 +01:00
794c4066d8 Refactored appveyor.yml 2017-03-01 21:49:42 +01:00
8c8e3072a7 Fix appveyor artifact packaging 2017-03-01 21:45:40 +01:00
228708eec4 Added update type SceneItemVisibilityChanged 2017-03-01 21:41:42 +01:00
dcafaedaa8 What the hell happens with LibObs_DIR 2017-03-01 21:22:14 +01:00
a85297f2c8 CI: Build artifact from obs-websocket release folder 2017-03-01 21:12:37 +01:00
91f7450cbd CI Fix 2017-03-01 21:07:50 +01:00
e7b074991d Fix appveyor CI + fix build script 2017-03-01 20:59:35 +01:00
5f83ce2a28 Setting up Appveyor CI 2017-03-01 20:46:21 +01:00
44af896dee Force C++11 on Linux 2017-03-01 17:53:44 +01:00
9eaa9a98ee Use UTF-8 strings for the password 2017-03-01 15:42:38 +01:00
629880cd58 Update README.md 2017-02-28 20:40:51 +01:00
42266ed14f Fixes #17 2017-02-28 16:56:03 +01:00
9dd7a197e4 Update PROTOCOL.md 2017-02-28 16:05:41 +01:00
6f39da20a9 Removed TransitionEnd event + changed TransitionBegin triggering 2017-02-28 16:01:06 +01:00
1ebb6f9257 Transition events + scene change compatibility with OBS 18.0.0 2017-02-28 15:40:40 +01:00
e30e982ef0 Added update type TransitionDurationChanged 2017-02-27 13:24:25 +01:00
42a80c6185 Ability to get and set transition duration 2017-02-27 11:53:44 +01:00
0d495f4d65 Merge pull request #46 from mikhailswift/add_scene_name_to_other_source_commands
Add scene name to SetSceneItemPosition and SetSceneItemTransform
2017-02-27 00:08:06 +01:00
71f5e66bd1 Added back accidental nuked new line 2017-02-26 18:03:20 -05:00
a6f71b68f3 Update protocol.md 2017-02-26 18:02:46 -05:00
a527f343cd Moved some repeated code to utils, added source_name to other commands 2017-02-26 18:01:09 -05:00
f8e1c454d9 Merge pull request #45 from mikhailswift/toggle_source_on_specified_scene
Toggle source on specified scene
2017-02-26 23:24:02 +01:00
da6e55f09f Used same error message elsewhere, fixed null check 2017-02-26 17:13:50 -05:00
d277e2788a Fixed missed changes from currentScene -> scene 2017-02-26 16:40:29 -05:00
53ba747b78 Update protocol readme for added parameter 2017-02-26 16:36:38 -05:00
b9862acd1d Add scene-name optional parmeter to set source render command 2017-02-26 16:35:08 -05:00
d1c19382a1 Fixes #34 2017-02-26 19:46:09 +01:00
4141983ccd Fixed a potential bug + WIP on scene collection/profile change 2017-02-26 17:04:48 +01:00
2b8b5001e0 Merge branch 'master' of github.com:Palakis/obs-websocket 2017-02-25 17:43:52 +01:00
532126561e Rollback changes : my dev environment had the wrong Qt version (5.7.0-1 instead of 5.7.0) 2017-02-25 17:42:58 +01:00
3c026c4eef Update README.md 2017-02-25 17:33:46 +01:00
ae6c15158f Bugfix : copy issue in CMakeLists.txt 2017-02-25 16:39:55 +01:00
6aa57247f9 Better build instructions (including release packager on Windows) 2017-02-25 16:32:20 +01:00
50862ac945 Typo fix 2017-02-25 14:48:09 +01:00
01ce6faa20 Add german translation to CMakeLists.txt 2017-02-24 20:33:45 +01:00
6e571aef95 Fixed issue with exception "Must construct a QApplication before a QWidget" 2017-02-24 20:26:19 +01:00
d6091c83e2 A bit of cleaning 2017-02-24 20:09:42 +01:00
5ca55fc13e sprintf is more cross-platform 2017-02-24 18:30:04 +01:00
2b60da02e8 sprintf_s() is not cross-platform 2017-02-24 17:40:41 +01:00
f02297152c Update Utils.h 2017-02-24 17:37:44 +01:00
af7c0bbc72 Removed a log message with a password in it 2017-02-24 00:50:16 +01:00
40727d5a6c Changed config key names from snake_case to CamelCase 2017-02-23 22:40:11 +01:00
9bdd73dbc6 Merge pull request #41 from Frahmer/german-translation-2
Add german translation
2017-02-23 22:34:22 +01:00
3e1ed09f12 Update README.md 2017-02-23 22:31:56 +01:00
3682c625d7 Add german translation
Add "ServerEnable" and "ServerPort" translation.
2017-02-23 22:30:08 +01:00
70cd52ac95 Update README.md 2017-02-23 22:29:42 +01:00
3264da4b2f Update README.md 2017-02-23 22:29:24 +01:00
06c1648f55 Updated PROTOCOL.md : new table of contents 2017-02-23 22:21:06 +01:00
94c3e5d41d Update PROTOCOL.md : better lisibility 2017-02-23 22:07:14 +01:00
7ae20d8c3b Semver : version number row left shift to 4.0.0 2017-02-23 21:36:00 +01:00
0ff4411abf Bugfix : deadlock when closing the server
Caused by a non-recursive mutex
2017-02-23 21:25:26 +01:00
3d68b7c9e5 Updated locale variables names + WIP Dynamic Server Settings 2017-02-23 21:01:24 +01:00
f8b1cae0c9 Merge pull request #40 from Frahmer/german-translation
Add german translation
2017-02-23 18:28:54 +01:00
5bee0fc453 Add german translation 2017-02-22 19:30:15 +01:00
7162765824 Fix compiler warning 2017-02-20 23:03:00 +01:00
99aa6be887 Updated PROTOCOL.md and fixed some types 2017-02-17 17:01:25 +01:00
afaaff298f Fixes #35 2017-02-17 11:41:41 +01:00
68cf9af6a3 Provide a item's base size in its infos 2017-02-17 11:25:46 +01:00
8d5752d6b5 Version bump to the next release during dev 2017-02-15 20:18:06 +01:00
98dbcc4c69 Copyright update and credit where it's due 2017-02-15 18:33:32 +01:00
687d8fd120 PROTOCOL.md fix 2017-02-15 18:27:37 +01:00
114ace23f7 Added OBS version number in the response fields of GetVersion 2017-02-15 18:25:44 +01:00
3fbc221db0 Style changes 2017-02-15 17:09:34 +01:00
f2e6e137a6 Fixed #21 2017-02-15 11:35:35 +01:00
bb232f1b3e Merge pull request #37 from mikhailswift/add_mute_source_command
Add mute source commands
2017-02-13 21:17:16 +01:00
9dc153bc22 Bugfix : std's map and set can cause crashes 2017-02-13 19:03:52 +01:00
b9bbdf5978 Renamed pClient to pHandler 2017-02-13 18:25:07 +01:00
e0db0e394d Added missing return statements to ToggleMute and SetMute handlers 2017-02-13 12:09:44 -05:00
3e9001721e Fixed incorrect header level for SetMute and ToggleMute 2017-02-13 12:08:36 -05:00
bbf3b0f86f updated mistake in protocol.md 2017-02-12 23:06:00 -05:00
7f3eb9f11b update spaces to tabs in wsrequesthandler for toggle and set mute 2017-02-12 22:54:29 -05:00
c783c51915 update spaces to tabs in wsrequesthandler for set and toggle mute 2017-02-12 22:52:20 -05:00
0816d222c6 Fixed mistake in ToggleMute 2017-02-12 17:55:06 -05:00
af16c70143 Added ToggleMute and SetMute handlers, updated PROTOCOL.md 2017-02-12 18:46:02 +00:00
748b6f6e2e Protect clients object list with a QMutex 2017-02-12 19:20:32 +01:00
3bd600ed52 Preliminary work on event timestamping 2017-02-05 02:00:21 +01:00
78e6ad0f59 Fixes #27 2017-02-05 01:46:38 +01:00
2d71dc68f1 WTH is this 2017-02-05 00:36:55 +01:00
7dc2a00d47 Fixes #10 and #28 2017-02-04 21:38:19 +01:00
7ece78a05b Merge pull request #33 from haganbmj/master
[Protocol] Add OnTransitionChange/OnTransitionListChange (Fixes #19)
2017-02-04 19:06:01 +01:00
ff2bace1bf [Protocol] Add OnTransitionChange/OnTransitionListChange (Fixes #19) 2017-02-03 01:30:54 -05:00
e63ce01bdc Update README.md 2017-01-17 14:07:04 +01:00
4439ce71d0 Really fixed IP address issue 2016-12-13 00:08:32 +01:00
27ec094775 Fixed char pointer issue in log messages 2016-12-12 21:08:59 +01:00
1bed53e07b Changed websocket server name 2016-12-12 18:53:41 +01:00
8ae06c0c4f Update README.md 2016-12-10 21:51:51 +01:00
372db7865b Update README.md 2016-12-10 21:50:19 +01:00
cfeffc551e Bumped installer version 2016-12-10 21:24:22 +01:00
28 changed files with 1809 additions and 338 deletions

11
.travis.yml Normal file
View 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
View 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

View 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

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.5) cmake_minimum_required(VERSION 3.2)
project(obs-websocket) project(obs-websocket)
set(CMAKE_PREFIX_PATH "${QTDIR}") set(CMAKE_PREFIX_PATH "${QTDIR}")
@ -13,7 +13,7 @@ find_package(Qt5WebSockets REQUIRED)
find_package(Qt5Widgets REQUIRED) find_package(Qt5Widgets REQUIRED)
add_subdirectory(deps/mbedtls EXCLUDE_FROM_ALL) add_subdirectory(deps/mbedtls EXCLUDE_FROM_ALL)
set(ENABLED_PROGRAMS false) set(ENABLE_PROGRAMS false)
set(obs-websocket_SOURCES set(obs-websocket_SOURCES
obs-websocket.cpp obs-websocket.cpp
@ -33,10 +33,13 @@ set(obs-websocket_HEADERS
Utils.h Utils.h
forms/settings-dialog.h) forms/settings-dialog.h)
# --- Platform-independent build settings ---
add_library(obs-websocket MODULE add_library(obs-websocket MODULE
${obs-websocket_SOURCES} ${obs-websocket_SOURCES}
${obs-websocket_HEADERS}) ${obs-websocket_HEADERS})
add_dependencies(obs-websocket mbedcrypto) add_dependencies(obs-websocket mbedcrypto)
include_directories( include_directories(
"${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api" "${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api"
${Qt5Core_INCLUDES} ${Qt5Core_INCLUDES}
@ -44,29 +47,72 @@ include_directories(
${Qt5Widgets_INCLUDES} ${Qt5Widgets_INCLUDES}
${mbedcrypto_INCLUDES} ${mbedcrypto_INCLUDES}
"${CMAKE_SOURCE_DIR}/deps/mbedtls/include") "${CMAKE_SOURCE_DIR}/deps/mbedtls/include")
target_link_libraries(obs-websocket target_link_libraries(obs-websocket
libobs libobs
Qt5::Core Qt5::Core
Qt5::WebSockets Qt5::WebSockets
Qt5::Widgets Qt5::Widgets
mbedcrypto) mbedcrypto)
# --- End of section ---
# --- Windows-specific build settings and tasks ---
if(WIN32) if(WIN32)
if(NOT DEFINED OBS_FRONTEND_LIB)
set(OBS_FRONTEND_LIB "OBS_FRONTEND_LIB-NOTFOUND" CACHE FILEPATH "OBS frontend library") 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 !") message(FATAL_ERROR "Could not find OBS Frontend API\'s library !")
endif() endif()
target_link_libraries(obs-websocket target_link_libraries(obs-websocket
"${OBS_FRONTEND_LIB}") "${OBS_FRONTEND_LIB}")
add_custom_command(TARGET obs-websocket POST_BUILD 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/Qt5WebSockets.dll"
"${QTDIR}/bin/Qt5Network.dll" "${QTDIR}/bin/Qt5Network.dll"
"${CMAKE_BINARY_DIR}/$<CONFIG>") "${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() 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) if(UNIX AND NOT APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
target_compile_options(mbedcrypto PRIVATE -fPIC) target_compile_options(mbedcrypto PRIVATE -fPIC)
set_target_properties(obs-websocket PROPERTIES PREFIX "") set_target_properties(obs-websocket PROPERTIES PREFIX "")
target_link_libraries(obs-websocket target_link_libraries(obs-websocket
@ -74,6 +120,9 @@ if(UNIX AND NOT APPLE)
install(TARGETS obs-websocket install(TARGETS obs-websocket
LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/obs-plugins) 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") DESTINATION "${CMAKE_INSTALL_PREFIX}/share/obs/obs-plugins/obs-websocket/locale")
endif() endif()
# --- End of section ---
# TODO : OS X build settings and tasks

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket 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 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 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/base64.h>
#include <mbedtls/sha256.h> #include <mbedtls/sha256.h>
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <util/config-file.h>
#include "Config.h" #include "Config.h"
#define CONFIG_SECTION_NAME "obs-websocket" #define SECTION_NAME "WebsocketAPI"
#define CONFIG_PARAM_SECRET "auth_hash" #define PARAM_ENABLE "ServerEnabled"
#define CONFIG_PARAM_SALT "auth_salt" #define PARAM_PORT "ServerPort"
#define CONFIG_PARAM_AUTHREQUIRED "auth_required" #define PARAM_AUTHREQUIRED "AuthRequired"
#define PARAM_SECRET "AuthSecret"
#define PARAM_SALT "AuthSalt"
Config *Config::_instance = new Config(); Config *Config::_instance = new Config();
Config::Config() { Config::Config()
{
// Default settings // Default settings
ServerEnabled = true;
ServerPort = 4444;
AuthRequired = false; AuthRequired = false;
Secret = ""; Secret = "";
Salt = ""; Salt = "";
SettingsLoaded = false; 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_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&rng); mbedtls_ctr_drbg_init(&rng);
mbedtls_ctr_drbg_seed(&rng, mbedtls_entropy_func, &entropy, nullptr, 0); mbedtls_ctr_drbg_seed(&rng, mbedtls_entropy_func, &entropy, nullptr, 0);
@ -43,12 +63,40 @@ Config::Config() {
SessionChallenge = GenerateSalt(); SessionChallenge = GenerateSalt();
} }
Config::~Config() { Config::~Config()
{
mbedtls_ctr_drbg_free(&rng); mbedtls_ctr_drbg_free(&rng);
mbedtls_entropy_free(&entropy); 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 // Generate 32 random chars
unsigned char *random_chars = (unsigned char *)bzalloc(32); unsigned char *random_chars = (unsigned char *)bzalloc(32);
mbedtls_ctr_drbg_random(&rng, random_chars, 32); mbedtls_ctr_drbg_random(&rng, random_chars, 32);
@ -63,7 +111,8 @@ const char* Config::GenerateSalt() {
return (char *)salt; 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 passwordLength = strlen(password);
size_t saltLength = strlen(salt); size_t saltLength = strlen(salt);
@ -88,7 +137,8 @@ const char* Config::GenerateSecret(const char *password, const char *salt) {
return (char*)challenge; return (char*)challenge;
} }
void Config::SetPassword(const char *password) { void Config::SetPassword(const char *password)
{
const char *new_salt = GenerateSalt(); const char *new_salt = GenerateSalt();
const char *new_challenge = GenerateSecret(password, new_salt); const char *new_challenge = GenerateSecret(password, new_salt);
@ -96,7 +146,8 @@ void Config::SetPassword(const char *password) {
this->Secret = new_challenge; this->Secret = new_challenge;
} }
bool Config::CheckAuth(const char *response) { bool Config::CheckAuth(const char *response)
{
size_t secretLength = strlen(this->Secret); size_t secretLength = strlen(this->Secret);
size_t sessChallengeLength = strlen(this->SessionChallenge); 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* Config::Current()
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() {
return _instance; return _instance;
} }

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket 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 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 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 #ifndef CONFIG_H
#define CONFIG_H #define CONFIG_H
#include <obs-module.h>
#include <mbedtls/entropy.h> #include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h> #include <mbedtls/ctr_drbg.h>
class Config { class Config
{
public: public:
Config(); Config();
~Config(); ~Config();
void Load();
void Save();
void SetPassword(const char *password); void SetPassword(const char *password);
bool CheckAuth(const char *userChallenge); bool CheckAuth(const char *userChallenge);
const char* GenerateSalt(); const char* GenerateSalt();
static const char* GenerateSecret(const char *password, const char *salt); 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; bool AuthRequired;
const char *Secret; const char *Secret;

View File

@ -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. 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 ### Table of contents
- [Events](#events) * [Events](#events)
- [Requests](#requests) - [Description](#description)
- [Authentication](#authentication) - [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 ## Events
### Description ### Description
Events are sent exclusively by the server and broadcast to each connected client. Events are sent exclusively by the server and broadcast to each connected client.
An event message will contain at least one field : An event message will contain at least one field :
- **update-type** (string) : the type of event - **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. Additional fields will be present in the event message depending on the event type.
### Event Types ### Event Types
#### "SwitchScenes" #### "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. - **scene-name** (string) : The name of the scene being switched to.
---
#### "ScenesChanged" #### "ScenesChanged"
The scene list has been modified (Scenes have been added, removed, or renamed). 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" #### "StreamStarting"
A request to start streaming has been issued. A request to start streaming has been issued.
- **preview-only** (bool) : Always false. - **preview-only** (bool) : Always false.
---
#### "StreamStarted" #### "StreamStarted"
Streaming started successfully. Streaming started successfully.
*New in OBS Studio* *New in OBS Studio*
---
#### "StreamStopping" #### "StreamStopping"
A request to stop streaming has been issued. A request to stop streaming has been issued.
- **preview-only** (bool) : Always false. - **preview-only** (bool) : Always false.
---
#### "StreamStopped" #### "StreamStopped"
Streaming stopped successfully. Streaming stopped successfully.
*New in OBS Studio* *New in OBS Studio*
---
#### "RecordingStarting" #### "RecordingStarting"
A request to start recording has been issued. A request to start recording has been issued.
*New in OBS Studio* *New in OBS Studio*
---
#### "RecordingStarted" #### "RecordingStarted"
Recording started successfully. Recording started successfully.
*New in OBS Studio* *New in OBS Studio*
---
#### "RecordingStopping" #### "RecordingStopping"
A request to stop streaming has been issued. A request to stop streaming has been issued.
*New in OBS Studio* *New in OBS Studio*
---
#### "RecordingStopped" #### "RecordingStopped"
Recording stopped successfully. Recording stopped successfully.
*New in OBS Studio* *New in OBS Studio*
---
#### "StreamStatus" #### "StreamStatus"
Sent every 2 seconds with the following information : Sent every 2 seconds with the following information :
- **streaming** (bool) : Current Streaming state. - **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. - **num-dropped-frames** (integer) : Number of frames dropped by the encoder since the stream started.
- **fps** (double) : Current framerate. - **fps** (double) : Current framerate.
---
#### "Exiting" #### "Exiting"
*New in OBS Studio*
OBS is exiting. OBS is exiting.
*New in OBS Studio*
---
## Requests ## Requests
@ -98,7 +250,10 @@ Returns the latest version of the plugin and the API.
__Request fields__ : none __Request fields__ : none
__Response__ : always OK, with these additional fields : __Response__ : always OK, with these additional fields :
- **"version"** (double) : OBSRemote API version. Fixed to 1.1 for retrocompatibility. - **"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" #### "GetAuthRequired"
Tells the client if authentication is required. If it is, authentication parameters "challenge" and "salt" are passed in the response fields (see "Authentication"). 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) - **"challenge"** (string)
- **"salt"** (string) - **"salt"** (string)
---
#### "Authenticate" #### "Authenticate"
Try to authenticate the client on the server. 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. __Response__ : OK if auth succeeded, error if invalid credentials. No additional fields.
---
#### "GetCurrentScene" #### "GetCurrentScene"
Get the current scene's name and items. 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 - **"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 - **"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 - **"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) - **"cx"** (double) : width of the item (with scale applied)
- **"cy"** (double) : height of the item (with scale applied) - **"cy"** (double) : height of the item (with scale applied)
---
#### "SetCurrentScene" #### "SetCurrentScene"
Switch to the scene specified in "scene-name". 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 __Response__ : always OK if scene exists, error if it doesn't. No additional fields
---
#### "GetSceneList" #### "GetSceneList"
List OBS' scenes. List OBS' scenes.
@ -150,21 +315,28 @@ __Response__ : always OK, with these additional fields :
- **"current-scene"** (string) : name of the currently active scene - **"current-scene"** (string) : name of the currently active scene
- **"scenes"** (array of objects) : ordered list of scene descriptions (see `GetCurrentScene` for reference) - **"scenes"** (array of objects) : ordered list of scene descriptions (see `GetCurrentScene` for reference)
---
#### "SetSourceRender" #### "SetSourceRender"
Show or hide a specific source in the current scene. Show or hide a specific source in the current scene.
__Request fields__ : __Request fields__ :
- **"source"** (string) : name of the source in the currently active scene. - **"source"** (string) : name of the source in the currently active scene.
- **"render"** (bool) : desired visibility - **"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. __Response__ : OK if source exists in the current scene, error otherwise.
---
#### "StartStopStreaming" #### "StartStopStreaming"
Toggle streaming on or off. Toggle streaming on or off.
__Request fields__ : none __Request fields__ : none
__Response__ : always OK. No additional fields. __Response__ : always OK. No additional fields.
---
#### "StartStopRecording" #### "StartStopRecording"
Toggle recording on or off. Toggle recording on or off.
@ -172,6 +344,8 @@ __Request fields__ : none
__Response__ : always OK. No additional fields. __Response__ : always OK. No additional fields.
*New in OBS Studio* *New in OBS Studio*
---
#### "GetStreamingStatus" #### "GetStreamingStatus"
Get current streaming and recording status. Get current streaming and recording status.
@ -181,6 +355,8 @@ __Response__ : always OK, with these additional fields :
- **"recording"** (bool) : recording status (active or not) - **"recording"** (bool) : recording status (active or not)
- **"preview-only"** (bool) : always false. Retrocompat with OBSRemote. - **"preview-only"** (bool) : always false. Retrocompat with OBSRemote.
---
#### "GetTransitionList" #### "GetTransitionList"
List all transitions available in the frontend's dropdown menu. 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* *New in OBS Studio*
---
#### "GetCurrentTransition" #### "GetCurrentTransition"
Get the name of the currently selected transition in the frontend's dropdown menu. Get the name of the currently selected transition in the frontend's dropdown menu.
__Request fields__ : none __Request fields__ : none
__Request__ : always OK, with these additional fields : __Response__ : always OK, with these additional fields :
- **"name"** (string) : name of the selected transition - **"name"** (string) : name of the selected transition
- **"duration"** (integer, only if transition supports this) : transition duration
*New in OBS Studio* *New in OBS Studio*
---
#### "SetCurrentTransition" #### "SetCurrentTransition"
__Request fields__ : __Request fields__ :
- **"transition-name"** (string) : The name of the transition. - **"transition-name"** (string) : The name of the transition.
@ -211,6 +392,160 @@ __Response__ : OK if specified transition exists, error otherwise.
*New in OBS Studio* *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 ### Authentication
A call to `GetAuthRequired` gives the client two elements : A call to `GetAuthRequired` gives the client two elements :
- A challenge : a random string that will be used to generate the auth response - A challenge : a random string that will be used to generate the auth response

View File

@ -1,26 +1,32 @@
obs-websocket obs-websocket
============== ==============
Websocket API for OBS Studio. Remote control of OBS Studio made easy.
## Downloads ## Downloads
Binaries for Windows and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section. Binaries for Windows and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section.
## Using obs-websocket ## 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 : 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.
- Javascript (browser & nodejs) : [obs-websocket-js](https://github.com/haganbmj/obs-websocket-js) by haganbmj
There's currently no frontend available for obs-websocket.
### Possible use cases ### Possible use cases
- Remote control OBS from a phone or tablet on the same local network - 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) - 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, ...) - 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 ## Compiling obs-websocket
### Prerequisites ### 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 ### Windows
In cmake-gui, you'll have to set the following variables : In cmake-gui, you'll have to set the following variables :
@ -43,3 +49,8 @@ sudo make install
### OS X ### OS X
*To do* *To do*
### Automated Builds
- Windows : [![Automated Build status for Windows](https://ci.appveyor.com/api/projects/status/github/Palakis/obs-websocket)](https://ci.appveyor.com/project/Palakis/obs-websocket/history)
- Linux & OS X : coming soon

117
Utils.cpp
View File

@ -1,6 +1,6 @@
/* /*
obs-websocket 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 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 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 "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* Utils::GetSceneItems(obs_source_t *source) {
obs_data_array_t *items = obs_data_array_create(); obs_data_array_t *items = obs_data_array_create();
@ -46,8 +74,12 @@ obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t *item) {
vec2 pos; vec2 pos;
obs_sceneitem_get_pos(item, &pos); obs_sceneitem_get_pos(item, &pos);
vec2 bounds; vec2 scale;
obs_sceneitem_get_bounds(item, &bounds); 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_t *data = obs_data_create();
obs_data_set_string(data, "name", obs_source_get_name(obs_sceneitem_get_source(item))); 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, "volume", obs_source_get_volume(obs_sceneitem_get_source(item)));
obs_data_set_double(data, "x", pos.x); obs_data_set_double(data, "x", pos.x);
obs_data_set_double(data, "y", pos.y); obs_data_set_double(data, "y", pos.y);
obs_data_set_double(data, "cx", bounds.x); obs_data_set_int(data, "source_cx", (int)item_width);
obs_data_set_double(data, "cy", bounds.y); 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)); obs_data_set_bool(data, "render", obs_sceneitem_visible(item));
return data; return data;
@ -115,6 +149,18 @@ obs_source_t* Utils::GetTransitionFromName(const char *search_name) {
return found_transition; 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_data_array_t* Utils::GetScenes() {
obs_frontend_source_list sceneList = {}; obs_frontend_source_list sceneList = {};
obs_frontend_get_scenes(&sceneList); obs_frontend_get_scenes(&sceneList);
@ -144,3 +190,64 @@ obs_data_t* Utils::GetSceneData(obs_source *source) {
obs_data_array_release(scene_items); obs_data_array_release(scene_items);
return sceneData; 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
View File

@ -1,6 +1,6 @@
/* /*
obs-websocket 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 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 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 #ifndef UTILS_H
#define UTILS_H #define UTILS_H
#include <QSpinBox>
#include <stdio.h>
#include <obs-module.h> #include <obs-module.h>
#include <obs-frontend-api.h>
class Utils class Utils
{ {
@ -29,9 +30,19 @@ class Utils
static obs_data_t* GetSceneItemData(obs_scene_item *item); static obs_data_t* GetSceneItemData(obs_scene_item *item);
static obs_sceneitem_t* GetSceneItemFromName(obs_source_t *source, const char *name); 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* GetTransitionFromName(const char *search_name);
static obs_source_t* GetSceneFromNameOrCurrent(const char *scene_name);
static obs_data_array_t* GetScenes(); static obs_data_array_t* GetScenes();
static obs_data_t* GetSceneData(obs_source *source); 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 #endif // UTILS_H

View File

@ -1,6 +1,7 @@
/* /*
obs-websocket 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 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 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/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#include <util/platform.h>
#include <QTimer>
#include "Utils.h"
#include "WSEvents.h" #include "WSEvents.h"
#include "obs-websocket.h"
WSEvents::WSEvents(WSServer *server) { bool transition_is_cut(obs_source_t *transition)
_srv = server; {
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); 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(); QTimer *statusTimer = new QTimer();
connect(statusTimer, SIGNAL(timeout()), this, SLOT(StreamStatus())); connect(statusTimer, SIGNAL(timeout()), this, SLOT(StreamStatus()));
statusTimer->start(2000); // equal to frontend's constant BITRATE_UPDATE_SECONDS 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); 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); 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(); owner->OnSceneChange();
} }
else if (event == OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED) { else if (event == OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED)
{
owner->OnSceneListChange(); 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(); owner->OnStreamStarting();
} }
else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTED) { else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTED)
{
owner->_streaming_active = true;
owner->OnStreamStarted(); owner->OnStreamStarted();
} }
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPING) { else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPING)
{
owner->OnStreamStopping(); owner->OnStreamStopping();
} }
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPED) { else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPED)
{
owner->_streaming_active = false;
owner->OnStreamStopped(); owner->OnStreamStopped();
} }
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTING) { else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTING)
{
owner->OnRecordingStarting(); owner->OnRecordingStarting();
} }
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTED) { else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTED)
{
owner->_recording_active = true;
owner->OnRecordingStarted(); owner->OnRecordingStarted();
} }
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPING) { else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPING)
{
owner->OnRecordingStopping(); owner->OnRecordingStopping();
} }
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPED) { else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPED)
{
owner->_recording_active = false;
owner->OnRecordingStopped(); owner->OnRecordingStopped();
} }
else if (event == OBS_FRONTEND_EVENT_EXIT) { else if (event == OBS_FRONTEND_EVENT_EXIT)
obs_frontend_save(); {
owner->OnExit(); owner->OnExit();
} }
} }
void WSEvents::broadcastUpdate(const char *updateType, obs_data_t *additionalFields = NULL) { void WSEvents::broadcastUpdate(const char *updateType, obs_data_t *additionalFields = NULL)
obs_source_t *source = obs_frontend_get_current_scene(); {
const char *name = obs_source_get_name(source);
obs_data_t *update = obs_data_create(); obs_data_t *update = obs_data_create();
obs_data_set_string(update, "update-type", updateType); 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) { if (additionalFields != NULL) {
obs_data_apply(update, additionalFields); 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)); _srv->broadcast(obs_data_get_json(update));
obs_data_release(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 // 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_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); broadcastUpdate("SwitchScenes", data);
obs_data_release(data); obs_data_release(data);
obs_source_release(new_scene); obs_source_release(current_scene);
obs_source_release(transition);
} }
void WSEvents::OnSceneListChange() { void WSEvents::OnSceneListChange()
{
broadcastUpdate("ScenesChanged"); 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 // Implements an existing update type from bilhamil's OBS Remote
obs_data_t *data = obs_data_create(); obs_data_t *data = obs_data_create();
obs_data_set_bool(data, "preview-only", false); obs_data_set_bool(data, "preview-only", false);
@ -136,14 +320,16 @@ void WSEvents::OnStreamStarting() {
obs_data_release(data); obs_data_release(data);
} }
void WSEvents::OnStreamStarted() { void WSEvents::OnStreamStarted()
{
// New update type specific to OBS Studio // New update type specific to OBS Studio
_streamStartTime = os_gettime_ns(); _stream_starttime = os_gettime_ns();
_lastBytesSent = 0; _lastBytesSent = 0;
broadcastUpdate("StreamStarted"); broadcastUpdate("StreamStarted");
} }
void WSEvents::OnStreamStopping() { void WSEvents::OnStreamStopping()
{
// Implements an existing update type from bilhamil's OBS Remote // Implements an existing update type from bilhamil's OBS Remote
obs_data_t *data = obs_data_create(); obs_data_t *data = obs_data_create();
obs_data_set_bool(data, "preview-only", false); obs_data_set_bool(data, "preview-only", false);
@ -153,38 +339,47 @@ void WSEvents::OnStreamStopping() {
obs_data_release(data); obs_data_release(data);
} }
void WSEvents::OnStreamStopped() { void WSEvents::OnStreamStopped()
{
// New update type specific to OBS Studio // New update type specific to OBS Studio
_streamStartTime = 0; _stream_starttime = 0;
broadcastUpdate("StreamStopped"); broadcastUpdate("StreamStopped");
} }
void WSEvents::OnRecordingStarting() { void WSEvents::OnRecordingStarting()
{
// New update type specific to OBS Studio // New update type specific to OBS Studio
broadcastUpdate("RecordingStarting"); broadcastUpdate("RecordingStarting");
} }
void WSEvents::OnRecordingStarted() { void WSEvents::OnRecordingStarted()
{
// New update type specific to OBS Studio // New update type specific to OBS Studio
_rec_starttime = os_gettime_ns();
broadcastUpdate("RecordingStarted"); broadcastUpdate("RecordingStarted");
} }
void WSEvents::OnRecordingStopping() { void WSEvents::OnRecordingStopping()
{
// New update type specific to OBS Studio // New update type specific to OBS Studio
broadcastUpdate("RecordingStopping"); broadcastUpdate("RecordingStopping");
} }
void WSEvents::OnRecordingStopped() { void WSEvents::OnRecordingStopped()
{
// New update type specific to OBS Studio // New update type specific to OBS Studio
_rec_starttime = 0;
broadcastUpdate("RecordingStopped"); broadcastUpdate("RecordingStopped");
} }
void WSEvents::OnExit() { void WSEvents::OnExit()
{
// New update type specific to OBS Studio // New update type specific to OBS Studio
broadcastUpdate("Exiting"); broadcastUpdate("Exiting");
} }
void WSEvents::StreamStatus() { void WSEvents::StreamStatus()
{
bool streaming_active = obs_frontend_streaming_active(); bool streaming_active = obs_frontend_streaming_active();
bool recording_active = obs_frontend_recording_active(); bool recording_active = obs_frontend_recording_active();
@ -215,15 +410,12 @@ void WSEvents::StreamStatus() {
_lastBytesSent = bytes_sent; _lastBytesSent = bytes_sent;
_lastBytesSentTime = bytes_sent_time; _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 total_frames = obs_output_get_total_frames(stream_output);
int dropped_frames = obs_output_get_frames_dropped(stream_output); int dropped_frames = obs_output_get_frames_dropped(stream_output);
float strain = 0.0; float strain = obs_output_get_congestion(stream_output);
if (total_frames > 0) {
strain = (dropped_frames / total_frames) * 100.0;
}
obs_data_t *data = obs_data_create(); obs_data_t *data = obs_data_create();
obs_data_set_bool(data, "streaming", streaming_active); obs_data_set_bool(data, "streaming", streaming_active);
@ -242,3 +434,108 @@ void WSEvents::StreamStatus() {
obs_data_release(data); obs_data_release(data);
obs_output_release(stream_output); 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);
}

View File

@ -1,6 +1,7 @@
/* /*
obs-websocket 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 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 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 #ifndef WSEVENTS_H
#define WSEVENTS_H #define WSEVENTS_H
#include <QtWebSockets/QWebSocket>
#include <QTimer>
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <util/platform.h>
#include "WSServer.h" #include "WSServer.h"
class WSEvents : public QObject class WSEvents : public QObject
@ -30,22 +28,42 @@ class WSEvents : public QObject
Q_OBJECT Q_OBJECT
public: public:
explicit WSEvents(WSServer *server); explicit WSEvents(WSServer *srv);
~WSEvents(); ~WSEvents();
static void FrontendEventHandler(enum obs_frontend_event event, void *private_data); 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: private Q_SLOTS:
void StreamStatus(); void StreamStatus();
void TransitionDurationChanged(int ms);
private: private:
WSServer *_srv; 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 _lastBytesSent;
uint64_t _lastBytesSentTime; uint64_t _lastBytesSentTime;
void broadcastUpdate(const char *updateType, obs_data_t *additionalFields); void broadcastUpdate(const char *updateType, obs_data_t *additionalFields);
void OnSceneChange(); void OnSceneChange();
void OnSceneListChange(); void OnSceneListChange();
void OnSceneCollectionChange();
void OnSceneCollectionListChange();
void OnTransitionChange();
void OnTransitionListChange();
void OnProfileChange();
void OnProfileListChange();
void OnStreamStarting(); void OnStreamStarting();
void OnStreamStarted(); void OnStreamStarted();
@ -58,6 +76,13 @@ class WSEvents : public QObject
void OnRecordingStopped(); void OnRecordingStopped();
void OnExit(); 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 #endif // WSEVENTS_H

View File

@ -1,6 +1,7 @@
/* /*
obs-websocket 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 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 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" #include "Utils.h"
WSRequestHandler::WSRequestHandler(QWebSocket *client) : WSRequestHandler::WSRequestHandler(QWebSocket *client) :
_authenticated(false),
_messageId(0), _messageId(0),
_requestType(""), _requestType(""),
_requestData(nullptr) _requestData(nullptr)
@ -36,37 +36,53 @@ WSRequestHandler::WSRequestHandler(QWebSocket *client) :
messageMap["SetCurrentScene"] = WSRequestHandler::HandleSetCurrentScene; messageMap["SetCurrentScene"] = WSRequestHandler::HandleSetCurrentScene;
messageMap["GetCurrentScene"] = WSRequestHandler::HandleGetCurrentScene; messageMap["GetCurrentScene"] = WSRequestHandler::HandleGetCurrentScene;
messageMap["GetSceneList"] = WSRequestHandler::HandleGetSceneList; messageMap["GetSceneList"] = WSRequestHandler::HandleGetSceneList;
messageMap["SetSourceOrder"] = WSRequestHandler::ErrNotImplemented;
messageMap["SetSourceRender"] = WSRequestHandler::HandleSetSourceRender; messageMap["SetSourceRender"] = WSRequestHandler::HandleSetSourceRender; // Retrocompat
messageMap["SetSceneItemPositionAndSize"] = WSRequestHandler::ErrNotImplemented; messageMap["SetSceneItemRender"] = WSRequestHandler::HandleSetSourceRender;
messageMap["SetSceneItemPosition"] = WSRequestHandler::HandleSetSceneItemPosition;
messageMap["SetSceneItemTransform"] = WSRequestHandler::HandleSetSceneItemTransform;
messageMap["GetStreamingStatus"] = WSRequestHandler::HandleGetStreamingStatus; messageMap["GetStreamingStatus"] = WSRequestHandler::HandleGetStreamingStatus;
messageMap["StartStopStreaming"] = WSRequestHandler::HandleStartStopStreaming; messageMap["StartStopStreaming"] = WSRequestHandler::HandleStartStopStreaming;
messageMap["StartStopRecording"] = WSRequestHandler::HandleStartStopRecording; messageMap["StartStopRecording"] = WSRequestHandler::HandleStartStopRecording;
messageMap["ToggleMute"] = WSRequestHandler::ErrNotImplemented;
messageMap["GetVolumes"] = WSRequestHandler::ErrNotImplemented;
messageMap["SetVolume"] = WSRequestHandler::ErrNotImplemented;
messageMap["GetTransitionList"] = WSRequestHandler::HandleGetTransitionList; messageMap["GetTransitionList"] = WSRequestHandler::HandleGetTransitionList;
messageMap["GetCurrentTransition"] = WSRequestHandler::HandleGetCurrentTransition; messageMap["GetCurrentTransition"] = WSRequestHandler::HandleGetCurrentTransition;
messageMap["SetCurrentTransition"] = WSRequestHandler::HandleSetCurrentTransition; 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("GetVersion");
authNotRequired.insert("GetAuthRequired"); authNotRequired.insert("GetAuthRequired");
authNotRequired.insert("Authenticate"); 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) { void WSRequestHandler::processIncomingMessage(QString textMessage)
QByteArray msgData = textMessage.toLocal8Bit(); {
QByteArray msgData = textMessage.toUtf8();
const char *msg = msgData; const char *msg = msgData;
_requestData = obs_data_create_from_json(msg); _requestData = obs_data_create_from_json(msg);
if (!_requestData) { if (!_requestData)
blog(LOG_ERROR, "[obs-websockets] invalid JSON payload for '%s'", msg); {
if (!msg)
{
msg = "<null pointer>";
}
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg);
SendErrorResponse("invalid JSON payload"); SendErrorResponse("invalid JSON payload");
return; return;
} }
@ -75,8 +91,8 @@ void WSRequestHandler::processTextMessage(QString textMessage) {
_messageId = obs_data_get_string(_requestData, "message-id"); _messageId = obs_data_get_string(_requestData, "message-id");
if (Config::Current()->AuthRequired if (Config::Current()->AuthRequired
&& !_authenticated && (_client->property(PROP_AUTHENTICATED).toBool() == false)
&& authNotRequired.find(_requestType) == authNotRequired.end()) && (authNotRequired.find(_requestType) == authNotRequired.end()))
{ {
SendErrorResponse("Not Authenticated"); SendErrorResponse("Not Authenticated");
return; return;
@ -84,44 +100,34 @@ void WSRequestHandler::processTextMessage(QString textMessage) {
void (*handlerFunc)(WSRequestHandler*) = (messageMap[_requestType]); void (*handlerFunc)(WSRequestHandler*) = (messageMap[_requestType]);
if (handlerFunc != NULL) { if (handlerFunc != NULL)
{
handlerFunc(this); handlerFunc(this);
} }
else { else
{
SendErrorResponse("invalid request type"); SendErrorResponse("invalid request type");
} }
obs_data_release(_requestData); obs_data_release(_requestData);
} }
void WSRequestHandler::socketDisconnected() { WSRequestHandler::~WSRequestHandler()
blog(LOG_INFO, "[obs-websockets] client %s:%d disconnected", _client->peerAddress().toString().toStdString(), _client->peerPort()); {
if (_requestData != NULL)
_authenticated = false; {
_client->deleteLater();
emit disconnected();
}
void WSRequestHandler::sendTextMessage(QString textMessage) {
_client->sendTextMessage(textMessage);
}
bool WSRequestHandler::isAuthenticated() {
return _authenticated;
}
WSRequestHandler::~WSRequestHandler() {
if (_requestData != NULL) {
obs_data_release(_requestData); 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_t *response = obs_data_create();
obs_data_set_string(response, "status", "ok"); obs_data_set_string(response, "status", "ok");
obs_data_set_string(response, "message-id", _messageId); obs_data_set_string(response, "message-id", _messageId);
if (additionalFields != NULL) { if (additionalFields != NULL)
{
obs_data_apply(response, additionalFields); obs_data_apply(response, additionalFields);
} }
@ -130,7 +136,8 @@ void WSRequestHandler::SendOKResponse(obs_data_t *additionalFields) {
obs_data_release(response); 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_t *response = obs_data_create();
obs_data_set_string(response, "status", "error"); obs_data_set_string(response, "status", "error");
obs_data_set_string(response, "error", errorMessage); obs_data_set_string(response, "error", errorMessage);
@ -141,24 +148,30 @@ void WSRequestHandler::SendErrorResponse(const char *errorMessage) {
obs_data_release(response); 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_t *data = obs_data_create();
obs_data_set_double(data, "version", 1.1); 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-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); owner->SendOKResponse(data);
obs_data_release(data); obs_data_release(data);
bfree((void*)obs_version);
} }
void WSRequestHandler::HandleGetAuthRequired(WSRequestHandler *owner) { void WSRequestHandler::HandleGetAuthRequired(WSRequestHandler *owner)
{
bool authRequired = Config::Current()->AuthRequired; bool authRequired = Config::Current()->AuthRequired;
obs_data_t *data = obs_data_create(); obs_data_t *data = obs_data_create();
obs_data_set_bool(data, "authRequired", authRequired); 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, "challenge", Config::Current()->SessionChallenge);
obs_data_set_string(data, "salt", Config::Current()->Salt); obs_data_set_string(data, "salt", Config::Current()->Salt);
} }
@ -168,43 +181,50 @@ void WSRequestHandler::HandleGetAuthRequired(WSRequestHandler *owner) {
obs_data_release(data); obs_data_release(data);
} }
void WSRequestHandler::HandleAuthenticate(WSRequestHandler *owner) { void WSRequestHandler::HandleAuthenticate(WSRequestHandler *owner)
{
const char *auth = obs_data_get_string(owner->_requestData, "auth"); 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!"); owner->SendErrorResponse("auth not specified!");
return; return;
} }
if (!(owner->_authenticated) && Config::Current()->CheckAuth(auth)) { if ((owner->_client->property(PROP_AUTHENTICATED).toBool() == false) && Config::Current()->CheckAuth(auth))
owner->_authenticated = true; {
owner->_client->setProperty(PROP_AUTHENTICATED, true);
owner->SendOKResponse(); owner->SendOKResponse();
} }
else { else
{
owner->SendErrorResponse("Authentication Failed."); 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"); const char *sceneName = obs_data_get_string(owner->_requestData, "scene-name");
obs_source_t *source = obs_get_source_by_name(sceneName); obs_source_t *source = obs_get_source_by_name(sceneName);
if (source) { if (source)
{
obs_frontend_set_current_scene(source); obs_frontend_set_current_scene(source);
owner->SendOKResponse(); owner->SendOKResponse();
} }
else { else
{
owner->SendErrorResponse("requested scene does not exist"); owner->SendErrorResponse("requested scene does not exist");
} }
obs_source_release(source); 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(); obs_source_t *current_scene = obs_frontend_get_current_scene();
const char *name = obs_source_get_name(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_t *data = obs_data_create();
obs_data_set_string(data, "name", name); obs_data_set_string(data, "name", name);
@ -217,7 +237,8 @@ void WSRequestHandler::HandleGetCurrentScene(WSRequestHandler *owner) {
obs_source_release(current_scene); 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_source_t *current_scene = obs_frontend_get_current_scene();
obs_data_array_t *scenes = Utils::GetScenes(); obs_data_array_t *scenes = Utils::GetScenes();
@ -232,30 +253,40 @@ void WSRequestHandler::HandleGetSceneList(WSRequestHandler *owner) {
obs_source_release(current_scene); 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"); const char *itemName = obs_data_get_string(owner->_requestData, "source");
bool isVisible = obs_data_get_bool(owner->_requestData, "render"); bool isVisible = obs_data_get_bool(owner->_requestData, "render");
if (itemName == NULL) { if (itemName == NULL)
{
owner->SendErrorResponse("invalid request parameters"); owner->SendErrorResponse("invalid request parameters");
return; 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); obs_sceneitem_t *sceneItem = Utils::GetSceneItemFromName(scene, itemName);
if (sceneItem != NULL) { if (sceneItem != NULL)
{
obs_sceneitem_set_visible(sceneItem, isVisible); obs_sceneitem_set_visible(sceneItem, isVisible);
obs_sceneitem_release(sceneItem); obs_sceneitem_release(sceneItem);
owner->SendOKResponse(); owner->SendOKResponse();
} }
else { else
{
owner->SendErrorResponse("specified scene item doesn't exist"); 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_t *data = obs_data_create();
obs_data_set_bool(data, "streaming", obs_frontend_streaming_active()); obs_data_set_bool(data, "streaming", obs_frontend_streaming_active());
obs_data_set_bool(data, "recording", obs_frontend_recording_active()); obs_data_set_bool(data, "recording", obs_frontend_recording_active());
@ -265,35 +296,43 @@ void WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler *owner) {
obs_data_release(data); obs_data_release(data);
} }
void WSRequestHandler::HandleStartStopStreaming(WSRequestHandler *owner) { void WSRequestHandler::HandleStartStopStreaming(WSRequestHandler *owner)
if (obs_frontend_streaming_active()) { {
if (obs_frontend_streaming_active())
{
obs_frontend_streaming_stop(); obs_frontend_streaming_stop();
} }
else { else
{
obs_frontend_streaming_start(); obs_frontend_streaming_start();
} }
owner->SendOKResponse(); owner->SendOKResponse();
} }
void WSRequestHandler::HandleStartStopRecording(WSRequestHandler *owner) { void WSRequestHandler::HandleStartStopRecording(WSRequestHandler *owner)
if (obs_frontend_recording_active()) { {
if (obs_frontend_recording_active())
{
obs_frontend_recording_stop(); obs_frontend_recording_stop();
} }
else { else
{
obs_frontend_recording_start(); obs_frontend_recording_start();
} }
owner->SendOKResponse(); owner->SendOKResponse();
} }
void WSRequestHandler::HandleGetTransitionList(WSRequestHandler *owner) { void WSRequestHandler::HandleGetTransitionList(WSRequestHandler *owner)
{
obs_source_t *current_transition = obs_frontend_get_current_transition(); obs_source_t *current_transition = obs_frontend_get_current_transition();
obs_frontend_source_list transitionList = {}; obs_frontend_source_list transitionList = {};
obs_frontend_get_transitions(&transitionList); obs_frontend_get_transitions(&transitionList);
obs_data_array_t* transitions = obs_data_array_create(); 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_source_t* transition = transitionList.sources.array[i];
obs_data_t *obj = obs_data_create(); obs_data_t *obj = obs_data_create();
@ -315,33 +354,297 @@ void WSRequestHandler::HandleGetTransitionList(WSRequestHandler *owner) {
obs_source_release(current_transition); 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_source_t *current_transition = obs_frontend_get_current_transition();
obs_data_t *response = obs_data_create(); obs_data_t *response = obs_data_create();
obs_data_set_string(response, "name", obs_source_get_name(current_transition)); 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); owner->SendOKResponse(response);
obs_data_release(response); obs_data_release(response);
obs_source_release(current_transition); 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"); const char *name = obs_data_get_string(owner->_requestData, "transition-name");
obs_source_t *transition = Utils::GetTransitionFromName(name); obs_source_t *transition = Utils::GetTransitionFromName(name);
if (transition) { if (transition)
{
obs_frontend_set_current_transition(transition); obs_frontend_set_current_transition(transition);
owner->SendOKResponse(); owner->SendOKResponse();
obs_source_release(transition); obs_source_release(transition);
} }
else { else
{
owner->SendErrorResponse("requested transition does not exist"); 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"); owner->SendErrorResponse("not implemented");
} }

View File

@ -1,6 +1,7 @@
/* /*
obs-websocket 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 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 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 #ifndef WSREQUESTHANDLER_H
#define WSREQUESTHANDLER_H #define WSREQUESTHANDLER_H
#include <map>
#include <set>
#include <QtWebSockets/QWebSocket>
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <QtWebSockets/QWebSocket>
#include <QtWebSockets/QWebSocketServer>
class WSRequestHandler : public QObject class WSRequestHandler : public QObject
{ {
@ -31,25 +31,16 @@ class WSRequestHandler : public QObject
public: public:
explicit WSRequestHandler(QWebSocket *client); explicit WSRequestHandler(QWebSocket *client);
~WSRequestHandler(); ~WSRequestHandler();
void sendTextMessage(QString textMessage); void processIncomingMessage(QString textMessage);
bool isAuthenticated();
private Q_SLOTS:
void processTextMessage(QString textMessage);
void socketDisconnected();
Q_SIGNALS:
void disconnected();
private: private:
QWebSocket *_client; QWebSocket *_client;
bool _authenticated;
const char *_messageId; const char *_messageId;
const char *_requestType; const char *_requestType;
obs_data_t *_requestData; obs_data_t *_requestData;
std::map<std::string, void(*)(WSRequestHandler*)> messageMap; QMap<QString, void(*)(WSRequestHandler*)> messageMap;
std::set<std::string> authNotRequired; QSet<QString> authNotRequired;
void SendOKResponse(obs_data_t *additionalFields = NULL); void SendOKResponse(obs_data_t *additionalFields = NULL);
void SendErrorResponse(const char *errorMessage); void SendErrorResponse(const char *errorMessage);
@ -62,7 +53,10 @@ class WSRequestHandler : public QObject
static void HandleSetCurrentScene(WSRequestHandler *owner); static void HandleSetCurrentScene(WSRequestHandler *owner);
static void HandleGetCurrentScene(WSRequestHandler *owner); static void HandleGetCurrentScene(WSRequestHandler *owner);
static void HandleGetSceneList(WSRequestHandler *owner); static void HandleGetSceneList(WSRequestHandler *owner);
static void HandleSetSourceRender(WSRequestHandler *owner); static void HandleSetSourceRender(WSRequestHandler *owner);
static void HandleSetSceneItemPosition(WSRequestHandler *owner);
static void HandleSetSceneItemTransform(WSRequestHandler *owner);
static void HandleGetStreamingStatus(WSRequestHandler *owner); static void HandleGetStreamingStatus(WSRequestHandler *owner);
static void HandleStartStopStreaming(WSRequestHandler *owner); static void HandleStartStopStreaming(WSRequestHandler *owner);
@ -71,6 +65,21 @@ class WSRequestHandler : public QObject
static void HandleGetTransitionList(WSRequestHandler *owner); static void HandleGetTransitionList(WSRequestHandler *owner);
static void HandleGetCurrentTransition(WSRequestHandler *owner); static void HandleGetCurrentTransition(WSRequestHandler *owner);
static void HandleSetCurrentTransition(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 #endif // WSPROTOCOL_H

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket 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 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 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/> 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 <QtWebSockets/QWebSocket>
#include <QtCore/QDebug>
#include <QtCore/QThread> #include <QtCore/QThread>
#include <QtCore/QByteArray>
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include "WSServer.h"
#include "obs-websocket.h"
#include "Config.h"
QT_USE_NAMESPACE QT_USE_NAMESPACE
WSServer::WSServer(quint16 port, QObject *parent) : WSServer* WSServer::Instance = new WSServer();
WSServer::WSServer(QObject *parent) :
QObject(parent), QObject(parent),
_wsServer(Q_NULLPTR), _wsServer(Q_NULLPTR),
_clients() _clients(),
_clMutex(QMutex::Recursive)
{ {
_serverThread = new QThread(); _serverThread = new QThread();
_wsServer = new QWebSocketServer( _wsServer = new QWebSocketServer(
QStringLiteral("OBS Websocket API"), QStringLiteral("obs-websocket"),
QWebSocketServer::NonSecureMode, QWebSocketServer::NonSecureMode,
this); this);
_wsServer->moveToThread(_serverThread); _wsServer->moveToThread(_serverThread);
_serverThread->start(); _serverThread->start();
bool serverStarted = _wsServer->listen(QHostAddress::Any, port);
if (serverStarted) {
connect(_wsServer, &QWebSocketServer::newConnection, this, &WSServer::onNewConnection);
}
} }
WSServer::~WSServer() 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(); _wsServer->close();
qDeleteAll(_clients.begin(), _clients.end());
} }
void WSServer::broadcast(QString message) void WSServer::broadcast(QString message)
{ {
Q_FOREACH(WSRequestHandler *pClient, _clients) { _clMutex.lock();
if (Config::Current()->AuthRequired == true
&& pClient->isAuthenticated() == false) { Q_FOREACH(QWebSocket *pClient, _clients)
{
if (Config::Current()->AuthRequired
&& (pClient->property(PROP_AUTHENTICATED).toBool() == false))
{
// Skip this client if unauthenticated // Skip this client if unauthenticated
continue; continue;
} }
pClient->sendTextMessage(message); pClient->sendTextMessage(message);
} }
_clMutex.unlock();
} }
void WSServer::onNewConnection() void WSServer::onNewConnection()
{ {
QWebSocket *pSocket = _wsServer->nextPendingConnection(); QWebSocket *pSocket = _wsServer->nextPendingConnection();
if (pSocket) { if (pSocket)
WSRequestHandler *pHandler = new WSRequestHandler(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); _clMutex.lock();
_clients << pHandler; _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() void WSServer::socketDisconnected()
{ {
WSRequestHandler *pClient = qobject_cast<WSRequestHandler *>(sender()); QWebSocket *pSocket = qobject_cast<QWebSocket *>(sender());
if (pClient) { if (pSocket)
_clients.removeAll(pClient); {
pClient->deleteLater(); 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());
} }
} }

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket 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 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 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/QObject>
#include <QtCore/QList> #include <QtCore/QList>
#include <QtCore/QByteArray> #include <QtCore/QMutex>
#include "WSRequestHandler.h"
QT_FORWARD_DECLARE_CLASS(QWebSocketServer) QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
QT_FORWARD_DECLARE_CLASS(QWebSocket) QT_FORWARD_DECLARE_CLASS(QWebSocket)
#include "WSRequestHandler.h"
class WSServer : public QObject class WSServer : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit WSServer(quint16 port, QObject *parent = Q_NULLPTR); explicit WSServer(QObject *parent = Q_NULLPTR);
virtual ~WSServer(); virtual ~WSServer();
void Start(quint16 port);
void Stop();
void broadcast(QString message); void broadcast(QString message);
static WSServer* Instance;
private Q_SLOTS: private Q_SLOTS:
void onNewConnection(); void onNewConnection();
void textMessageReceived(QString message);
void socketDisconnected(); void socketDisconnected();
private: private:
QWebSocketServer *_wsServer; QWebSocketServer *_wsServer;
QList<WSRequestHandler *> _clients; QList<QWebSocket *> _clients;
QMutex _clMutex;
QThread *_serverThread; QThread *_serverThread;
}; };

50
appveyor.yml Normal file
View 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
View 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"

View File

@ -1,4 +1,6 @@
Menu.SettingsItem="Websocket server settings" OBSWebsocket.Menu.SettingsItem="Websocket server settings"
Settings.DialogTitle="obs-websocket" OBSWebsocket.Settings.DialogTitle="obs-websocket"
Settings.AuthRequired="Enable authentication" OBSWebsocket.Settings.ServerEnable="Enable Websocket server"
Settings.Password="Password" OBSWebsocket.Settings.ServerPort="Server Port"
OBSWebsocket.Settings.AuthRequired="Enable authentication"
OBSWebsocket.Settings.Password="Password"

View File

@ -1,4 +1,6 @@
Menu.SettingsItem="Paramètres du serveur Websocket" OBSWebsocket.Menu.SettingsItem="Paramètres du serveur Websocket"
Settings.DialogTitle="obs-websocket" OBSWebsocket.Settings.DialogTitle="obs-websocket"
Settings.AuthRequired="Activer l'authentification" OBSWebsocket.Settings.ServerEnable="Activer le serveur Websockets"
Settings.Password="Mot de passe" 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
View 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
View 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="密碼"

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket 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 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 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-frontend-api.h>
#include "obs-websocket.h"
#include "Config.h"
#include "WSServer.h"
#include "settings-dialog.h" #include "settings-dialog.h"
#include "ui_settings-dialog.h" #include "ui_settings-dialog.h"
#include "Config.h"
#define CHANGE_ME "changeme" #define CHANGE_ME "changeme"
@ -35,12 +38,19 @@ SettingsDialog::SettingsDialog(QWidget *parent) :
AuthCheckboxChanged(); AuthCheckboxChanged();
} }
void SettingsDialog::showEvent(QShowEvent *event) { void SettingsDialog::showEvent(QShowEvent *event)
ui->authRequired->setChecked(Config::Current()->AuthRequired); {
Config* conf = Config::Current();
ui->serverEnabled->setChecked(conf->ServerEnabled);
ui->serverPort->setValue(conf->ServerPort);
ui->authRequired->setChecked(conf->AuthRequired);
ui->password->setText(CHANGE_ME); ui->password->setText(CHANGE_ME);
} }
void SettingsDialog::ToggleShowHide() { void SettingsDialog::ToggleShowHide()
{
if (!isVisible()) { if (!isVisible()) {
setVisible(true); setVisible(true);
} }
@ -49,7 +59,8 @@ void SettingsDialog::ToggleShowHide() {
} }
} }
void SettingsDialog::AuthCheckboxChanged() { void SettingsDialog::AuthCheckboxChanged()
{
if (ui->authRequired->isChecked()) { if (ui->authRequired->isChecked()) {
ui->password->setEnabled(true); ui->password->setEnabled(true);
} }
@ -58,28 +69,47 @@ void SettingsDialog::AuthCheckboxChanged() {
} }
} }
void SettingsDialog::FormAccepted() { void SettingsDialog::FormAccepted()
if (ui->authRequired->isChecked()) { {
if (ui->password->text() != CHANGE_ME) { Config* conf = Config::Current();
QByteArray pwd = ui->password->text().toLocal8Bit();
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; const char *new_password = pwd;
blog(LOG_INFO, "new password : %s", new_password); conf->SetPassword(new_password);
Config::Current()->SetPassword(new_password);
} }
if (strcmp(Config::Current()->Secret, "") != 0) { if (strcmp(Config::Current()->Secret, "") != 0)
Config::Current()->AuthRequired = true; {
conf->AuthRequired = true;
} }
else { else
Config::Current()->AuthRequired = false; {
conf->AuthRequired = false;
} }
} }
else { else
Config::Current()->AuthRequired = false; {
conf->AuthRequired = false;
} }
obs_frontend_save(); conf->Save();
if (conf->ServerEnabled)
{
WSServer::Instance->Start(conf->ServerPort);
}
else
{
WSServer::Instance->Stop();
}
} }
SettingsDialog::~SettingsDialog() SettingsDialog::~SettingsDialog()

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket 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 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 it under the terms of the GNU General Public License as published by

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>354</width> <width>407</width>
<height>110</height> <height>155</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -17,7 +17,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Settings.DialogTitle</string> <string>OBSWebsocket.Settings.DialogTitle</string>
</property> </property>
<property name="sizeGripEnabled"> <property name="sizeGripEnabled">
<bool>false</bool> <bool>false</bool>
@ -28,24 +28,54 @@
</property> </property>
<item> <item>
<layout class="QFormLayout" name="formLayout"> <layout class="QFormLayout" name="formLayout">
<item row="3" column="0"> <item row="3" column="1">
<widget class="QLabel" name="lbl_password"> <widget class="QCheckBox" name="authRequired">
<property name="text"> <property name="text">
<string>Settings.Password</string> <string>OBSWebsocket.Settings.AuthRequired</string>
</property> </property>
</widget> </widget>
</item> </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"> <widget class="QLineEdit" name="password">
<property name="echoMode"> <property name="echoMode">
<enum>QLineEdit::Password</enum> <enum>QLineEdit::Password</enum>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="1" column="1">
<widget class="QCheckBox" name="authRequired"> <widget class="QCheckBox" name="serverEnabled">
<property name="text"> <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> </property>
</widget> </widget>
</item> </item>

View File

@ -2,7 +2,7 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
#define MyAppName "obs-websocket" #define MyAppName "obs-websocket"
#define MyAppVersion "0.3.1" #define MyAppVersion "4.0.0"
#define MyAppPublisher "St<53>phane Lepin" #define MyAppPublisher "St<53>phane Lepin"
#define MyAppURL "http://github.com/Palakis/obs-websocket" #define MyAppURL "http://github.com/Palakis/obs-websocket"

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket 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 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 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-module.h>
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <QAction> #include <QAction>
#include <QMainWindow>
#include <QTimer>
#include "obs-websocket.h" #include "obs-websocket.h"
#include "WSEvents.h"
#include "WSServer.h" #include "WSServer.h"
#include "WSEvents.h"
#include "Config.h" #include "Config.h"
#include "forms/settings-dialog.h" #include "forms/settings-dialog.h"
@ -30,22 +32,27 @@ OBS_DECLARE_MODULE()
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US") OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
WSEvents *eventHandler; WSEvents *eventHandler;
WSServer *server;
SettingsDialog *settings_dialog; SettingsDialog *settings_dialog;
bool obs_module_load(void) 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); // Core setup
eventHandler = new WSEvents(server); 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); 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(); obs_frontend_pop_ui_translation();
auto menu_cb = [] { auto menu_cb = [] {
@ -53,11 +60,14 @@ bool obs_module_load(void)
}; };
menu_action->connect(menu_action, &QAction::triggered, menu_cb); menu_action->connect(menu_action, &QAction::triggered, menu_cb);
// Loading finished
blog(LOG_INFO, "module loaded!");
return true; return true;
} }
void obs_module_unload() void obs_module_unload()
{ {
blog(LOG_INFO, "[obs-websockets] goodbye !"); blog(LOG_INFO, "goodbye!");
} }

View File

@ -1,6 +1,6 @@
/* /*
obs-websocket 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 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 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 #ifndef OBSWEBSOCKET_H
#define 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 #endif // OBSWEBSOCKET_H