mirror of
https://github.com/Palakis/obs-websocket.git
synced 2024-08-30 18:12:16 +00:00
Compare commits
112 Commits
Author | SHA1 | Date | |
---|---|---|---|
0672b6a157 | |||
1e71bfa151 | |||
d4c2c8197a | |||
c9baed2df9 | |||
1e2065c84a | |||
537d683dfb | |||
e6f1b9f8c8 | |||
5815d2bfce | |||
7692e93306 | |||
8768b83251 | |||
35d18810fc | |||
ccd40a1834 | |||
5c1f0c3541 | |||
e237e52ae4 | |||
effec90528 | |||
de4a2247c0 | |||
b20b7cbc98 | |||
3c347b9b77 | |||
794c4066d8 | |||
8c8e3072a7 | |||
228708eec4 | |||
dcafaedaa8 | |||
a85297f2c8 | |||
91f7450cbd | |||
e7b074991d | |||
5f83ce2a28 | |||
44af896dee | |||
9eaa9a98ee | |||
629880cd58 | |||
42266ed14f | |||
9dd7a197e4 | |||
6f39da20a9 | |||
1ebb6f9257 | |||
e30e982ef0 | |||
42a80c6185 | |||
0d495f4d65 | |||
71f5e66bd1 | |||
a6f71b68f3 | |||
a527f343cd | |||
f8e1c454d9 | |||
da6e55f09f | |||
d277e2788a | |||
53ba747b78 | |||
b9862acd1d | |||
d1c19382a1 | |||
4141983ccd | |||
2b8b5001e0 | |||
532126561e | |||
3c026c4eef | |||
ae6c15158f | |||
6aa57247f9 | |||
50862ac945 | |||
01ce6faa20 | |||
6e571aef95 | |||
d6091c83e2 | |||
5ca55fc13e | |||
2b60da02e8 | |||
f02297152c | |||
af7c0bbc72 | |||
40727d5a6c | |||
9bdd73dbc6 | |||
3e1ed09f12 | |||
3682c625d7 | |||
70cd52ac95 | |||
3264da4b2f | |||
06c1648f55 | |||
94c3e5d41d | |||
7ae20d8c3b | |||
0ff4411abf | |||
3d68b7c9e5 | |||
f8b1cae0c9 | |||
5bee0fc453 | |||
7162765824 | |||
99aa6be887 | |||
afaaff298f | |||
68cf9af6a3 | |||
8d5752d6b5 | |||
98dbcc4c69 | |||
687d8fd120 | |||
114ace23f7 | |||
3fbc221db0 | |||
f2e6e137a6 | |||
bb232f1b3e | |||
9dc153bc22 | |||
b9bbdf5978 | |||
e0db0e394d | |||
3e9001721e | |||
bbf3b0f86f | |||
7f3eb9f11b | |||
c783c51915 | |||
0816d222c6 | |||
af16c70143 | |||
748b6f6e2e | |||
3bd600ed52 | |||
78e6ad0f59 | |||
2d71dc68f1 | |||
7dc2a00d47 | |||
7ece78a05b | |||
ff2bace1bf | |||
e63ce01bdc | |||
4439ce71d0 | |||
27ec094775 | |||
1bed53e07b | |||
8ae06c0c4f | |||
372db7865b | |||
cfeffc551e | |||
d519815a7e | |||
32932eacf5 | |||
1a057cf5a3 | |||
e3936dad9b | |||
4ec9b85506 | |||
fbae081c33 |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,3 +1,5 @@
|
|||||||
/build/
|
/build/
|
||||||
/build32/
|
/build32/
|
||||||
/build64/
|
/build64/
|
||||||
|
/release/
|
||||||
|
/installer/Output/
|
||||||
|
11
.travis.yml
Normal file
11
.travis.yml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
language: cpp
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: linux
|
||||||
|
dist: trusty
|
||||||
|
sudo: required
|
||||||
|
before_install: "./CI/install-dependencies-linux.sh"
|
||||||
|
before_script: "./CI/before-script-linux.sh"
|
||||||
|
|
||||||
|
script: cd ./build && make -j4 && cd -
|
6
CI/before-script-linux.sh
Executable file
6
CI/before-script-linux.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake -DLIBOBS_INCLUDE_DIR="../../obs-studio/libobs" -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||||
|
make -j4
|
61
CI/install-dependencies-linux.sh
Executable file
61
CI/install-dependencies-linux.sh
Executable file
@ -0,0 +1,61 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
# OBS Studio deps
|
||||||
|
sudo add-apt-repository ppa:kirillshkrogalev/ffmpeg-next -y
|
||||||
|
sudo apt-get -qq update
|
||||||
|
sudo apt-get install -y \
|
||||||
|
build-essential \
|
||||||
|
checkinstall \
|
||||||
|
cmake \
|
||||||
|
libasound2-dev \
|
||||||
|
libavcodec-ffmpeg-dev \
|
||||||
|
libavdevice-ffmpeg-dev \
|
||||||
|
libavfilter-ffmpeg-dev \
|
||||||
|
libavformat-ffmpeg-dev \
|
||||||
|
libavutil-ffmpeg-dev \
|
||||||
|
libcurl4-openssl-dev \
|
||||||
|
libfontconfig-dev \
|
||||||
|
libfreetype6-dev \
|
||||||
|
libgl1-mesa-dev \
|
||||||
|
libjack-jackd2-dev \
|
||||||
|
libjansson-dev \
|
||||||
|
libpulse-dev \
|
||||||
|
libqt5x11extras5-dev \
|
||||||
|
libspeexdsp-dev \
|
||||||
|
libswresample-ffmpeg-dev \
|
||||||
|
libswscale-ffmpeg-dev \
|
||||||
|
libudev-dev \
|
||||||
|
libv4l-dev \
|
||||||
|
libvlc-dev \
|
||||||
|
libx11-dev \
|
||||||
|
libx264-dev \
|
||||||
|
libxcb-shm0-dev \
|
||||||
|
libxcb-xinerama0-dev \
|
||||||
|
libxcomposite-dev \
|
||||||
|
libxinerama-dev \
|
||||||
|
pkg-config \
|
||||||
|
qtbase5-dev
|
||||||
|
|
||||||
|
# qtwebsockets deps
|
||||||
|
sudo apt-get install -y qt5-qmake
|
||||||
|
|
||||||
|
# obs-websocket deps
|
||||||
|
cd ..
|
||||||
|
git clone https://github.com/qt/qtwebsockets/ ./qtwebsockets
|
||||||
|
cd qtwebsockets
|
||||||
|
git checkout v5.3.0
|
||||||
|
qmake
|
||||||
|
make -j4
|
||||||
|
sudo make install
|
||||||
|
|
||||||
|
# Build obs-studio
|
||||||
|
cd ..
|
||||||
|
git clone https://github.com/jp9000/obs-studio ./obs-studio
|
||||||
|
cd obs-studio
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake -DUNIX_STRUCTURE=1 -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||||
|
make -j4
|
||||||
|
sudo make install
|
||||||
|
|
||||||
|
sudo ldconfig
|
@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.5)
|
cmake_minimum_required(VERSION 3.2)
|
||||||
project(obs-websocket)
|
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
|
||||||
|
99
Config.cpp
99
Config.cpp
@ -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;
|
||||||
}
|
}
|
||||||
|
13
Config.h
13
Config.h
@ -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;
|
||||||
|
349
PROTOCOL.md
349
PROTOCOL.md
@ -6,58 +6,206 @@ Messages exchanged between the client and the server are JSON objects.
|
|||||||
The protocol in general is based on the OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio.
|
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
|
||||||
|
27
README.md
27
README.md
@ -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 are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section. Linux and OS X releases coming soon.
|
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 : [](https://ci.appveyor.com/project/Palakis/obs-websocket/history)
|
||||||
|
- Linux & OS X : coming soon
|
||||||
|
|
||||||
|
117
Utils.cpp
117
Utils.cpp
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
obs-websocket
|
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
15
Utils.h
@ -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
|
414
WSEvents.cpp
414
WSEvents.cpp
@ -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,33 +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
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
obs_data_t *data = obs_data_create();
|
obs_data_t *data = obs_data_create();
|
||||||
obs_data_set_string(data, "scene-name", obs_source_get_name(new_scene));
|
|
||||||
|
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);
|
||||||
@ -123,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);
|
||||||
@ -140,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();
|
||||||
|
|
||||||
@ -202,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);
|
||||||
@ -229,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);
|
||||||
|
}
|
37
WSEvents.h
37
WSEvents.h
@ -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
|
@ -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");
|
||||||
}
|
}
|
@ -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
|
||||||
|
113
WSServer.cpp
113
WSServer.cpp
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
16
WSServer.h
16
WSServer.h
@ -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
50
appveyor.yml
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
environment:
|
||||||
|
CURL_VERSION: 7.39.0
|
||||||
|
|
||||||
|
install:
|
||||||
|
- git submodule update --init --recursive
|
||||||
|
- cd C:\projects\
|
||||||
|
- if not exist dependencies2013.zip curl -kLO https://obsproject.com/downloads/dependencies2013.zip -f --retry 5 -C -
|
||||||
|
- 7z x dependencies2013.zip -odependencies2013
|
||||||
|
- if not exist qt570.zip curl -kLO https://www.slepin.fr/obs-websocket/ci/qt570.zip -f --retry 5 -C -
|
||||||
|
- 7z x qt570.zip -o"Qt5.7.0"
|
||||||
|
- set DepsPath32=%CD%\dependencies2013\win32
|
||||||
|
- set DepsPath64=%CD%\dependencies2013\win64
|
||||||
|
- set QTDIR32=%CD%\Qt5.7.0\msvc2013
|
||||||
|
- set QTDIR64=%CD%\Qt5.7.0\msvc2013_64
|
||||||
|
- set build_config=Release
|
||||||
|
- git clone --recursive https://github.com/jp9000/obs-studio
|
||||||
|
- cd C:\projects\obs-studio\
|
||||||
|
- git checkout 18.0.0
|
||||||
|
- mkdir build
|
||||||
|
- mkdir build32
|
||||||
|
- mkdir build64
|
||||||
|
- cd ./build32
|
||||||
|
- cmake -G "Visual Studio 12 2013" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
||||||
|
- cd ../build64
|
||||||
|
- cmake -G "Visual Studio 12 2013 Win64" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
||||||
|
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build32\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||||
|
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build64\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||||
|
- cd C:\projects\obs-websocket\
|
||||||
|
- mkdir build32
|
||||||
|
- mkdir build64
|
||||||
|
- cd ./build32
|
||||||
|
- cmake -G "Visual Studio 12 2013" -DQTDIR="%QTDIR32%" -DLibObs_DIR="C:\projects\obs-studio\build32\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
|
||||||
|
- cd ../build64
|
||||||
|
- cmake -G "Visual Studio 12 2013 Win64" -DQTDIR="%QTDIR64%" -DLibObs_DIR="C:\projects\obs-studio\build64\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
|
||||||
|
|
||||||
|
build_script:
|
||||||
|
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build32\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||||
|
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build64\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||||
|
|
||||||
|
before_deploy:
|
||||||
|
- 7z a "C:\projects\obs-websocket\build.zip" C:\projects\obs-websocket\release\*
|
||||||
|
|
||||||
|
deploy_script:
|
||||||
|
- ps: Push-AppveyorArtifact "C:\projects\obs-websocket\build.zip" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1).zip"
|
||||||
|
|
||||||
|
test: off
|
||||||
|
|
||||||
|
cache:
|
||||||
|
- C:\projects\dependencies2013.zip
|
||||||
|
- C:\projects\qt570.zip
|
6
data/locale/de-DE.ini
Normal file
6
data/locale/de-DE.ini
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
OBSWebsocket.Menu.SettingsItem="Websocket-Server Einstellungen"
|
||||||
|
OBSWebsocket.Settings.DialogTitle="Websocket-Server Einstellungen"
|
||||||
|
OBSWebsocket.Settings.ServerEnable="Websocket-Server aktivieren"
|
||||||
|
OBSWebsocket.Settings.ServerPort="Server Port"
|
||||||
|
OBSWebsocket.Settings.AuthRequired="Authentifizierung erforderlich"
|
||||||
|
OBSWebsocket.Settings.Password="Passwort"
|
@ -1,4 +1,6 @@
|
|||||||
Menu.SettingsItem="Websocket server settings"
|
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"
|
@ -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
6
data/locale/zh-CN.ini
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
OBSWebsocket.Menu.SettingsItem="Websocket 服务器设置"
|
||||||
|
OBSWebsocket.Settings.DialogTitle="obs-websocket 设置"
|
||||||
|
OBSWebsocket.Settings.ServerEnable="启用 Websocket 服务器"
|
||||||
|
OBSWebsocket.Settings.ServerPort="服务器端口"
|
||||||
|
OBSWebsocket.Settings.AuthRequired="启用密码认证"
|
||||||
|
OBSWebsocket.Settings.Password="密码"
|
6
data/locale/zh-TW.ini
Normal file
6
data/locale/zh-TW.ini
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
OBSWebsocket.Menu.SettingsItem="Websocket 伺服器設定"
|
||||||
|
OBSWebsocket.Settings.DialogTitle="obs-websocket 設定"
|
||||||
|
OBSWebsocket.Settings.ServerEnable="啟用 Websocket 伺服器"
|
||||||
|
OBSWebsocket.Settings.ServerPort="伺服器端口"
|
||||||
|
OBSWebsocket.Settings.AuthRequired="啟用密碼認證"
|
||||||
|
OBSWebsocket.Settings.Password="密碼"
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
obs-websocket
|
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()
|
||||||
|
@ -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
|
||||||
|
@ -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>
|
||||||
|
54
installer/installer.iss
Normal file
54
installer/installer.iss
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
; Script generated by the Inno Setup Script Wizard.
|
||||||
|
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||||
|
|
||||||
|
#define MyAppName "obs-websocket"
|
||||||
|
#define MyAppVersion "4.0.0"
|
||||||
|
#define MyAppPublisher "St<53>phane Lepin"
|
||||||
|
#define MyAppURL "http://github.com/Palakis/obs-websocket"
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
; NOTE: The value of AppId uniquely identifies this application.
|
||||||
|
; Do not use the same AppId value in installers for other applications.
|
||||||
|
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
|
||||||
|
AppId={{117EE44F-48E1-49E5-A381-CC8D9195CF35}
|
||||||
|
AppName={#MyAppName}
|
||||||
|
AppVersion={#MyAppVersion}
|
||||||
|
;AppVerName={#MyAppName} {#MyAppVersion}
|
||||||
|
AppPublisher={#MyAppPublisher}
|
||||||
|
AppPublisherURL={#MyAppURL}
|
||||||
|
AppSupportURL={#MyAppURL}
|
||||||
|
AppUpdatesURL={#MyAppURL}
|
||||||
|
DefaultDirName={code:GetDirName}
|
||||||
|
DefaultGroupName={#MyAppName}
|
||||||
|
OutputBaseFilename=obs-websocket-{#MyAppVersion}-Windows-Installer
|
||||||
|
Compression=lzma
|
||||||
|
SolidCompression=yes
|
||||||
|
LicenseFile=..\LICENSE
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
Name: "french"; MessagesFile: "compiler:Languages\French.isl"
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: "..\release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}"
|
||||||
|
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
|
||||||
|
|
||||||
|
[Code]
|
||||||
|
// credit where it's due :
|
||||||
|
// following function come from https://github.com/Xaymar/obs-studio_amf-encoder-plugin/blob/master/%23Resources/Installer.in.iss#L45
|
||||||
|
function GetDirName(Value: string): string;
|
||||||
|
var
|
||||||
|
InstallPath: string;
|
||||||
|
begin
|
||||||
|
// initialize default path, which will be returned when the following registry
|
||||||
|
// key queries fail due to missing keys or for some different reason
|
||||||
|
Result := '{pf}\obs-studio';
|
||||||
|
// query the first registry value; if this succeeds, return the obtained value
|
||||||
|
if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then
|
||||||
|
Result := InstallPath
|
||||||
|
end;
|
||||||
|
|
@ -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!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.1"
|
#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
|
Reference in New Issue
Block a user