mirror of
https://github.com/Palakis/obs-websocket.git
synced 2024-08-30 18:12:16 +00:00
Compare commits
51 Commits
Author | SHA1 | Date | |
---|---|---|---|
cc0b110d61 | |||
82a1e7d253 | |||
87b47689eb | |||
c3346f9192 | |||
3b0cf02574 | |||
4404889019 | |||
4e642d97fc | |||
44bd25646d | |||
09b04037a0 | |||
3c50e1e4d8 | |||
179ba9a9e6 | |||
9ce2b1983a | |||
37dde278cb | |||
1cc57e7d1d | |||
60a5cdb154 | |||
f290cbc148 | |||
2e2e9b1332 | |||
7915e3815b | |||
beb34deed8 | |||
a263d8a364 | |||
1eccf4c899 | |||
d8d19d839a | |||
17e573d67f | |||
59de83b45d | |||
712bd6e8f3 | |||
75b21d070a | |||
e91c9e7bf4 | |||
2562272775 | |||
819621e307 | |||
4b9229311c | |||
4c4c1de190 | |||
6b6b7feff2 | |||
9e3ce26ba0 | |||
eac19a7c83 | |||
ee1486274d | |||
8692e2afda | |||
f995268444 | |||
bd2e974718 | |||
d1c64c7509 | |||
30a19cfe8a | |||
cc3097b09a | |||
c00681b52d | |||
61931c179f | |||
c7190cb94a | |||
a1bd27dfde | |||
6ac3a3de57 | |||
e198ed7a9c | |||
4b89464349 | |||
dba599c127 | |||
586f9076f0 | |||
add39cfc5f |
16
.github/ISSUE_TEMPLATE.md
vendored
16
.github/ISSUE_TEMPLATE.md
vendored
@ -1,18 +1,16 @@
|
|||||||
#### Issue type
|
##### Issue type
|
||||||
- [ ] Bug
|
Bug report? Feature request? Other?
|
||||||
- [ ] Feature request
|
|
||||||
- [ ] Other
|
|
||||||
|
|
||||||
#### Description
|
##### Description
|
||||||
*Replace this with a description of the bug encountered or feature requested.*
|
*Replace this with a description of the bug encountered or feature requested.*
|
||||||
|
|
||||||
#### Steps to reproduce
|
##### Steps to reproduce and other useful info
|
||||||
*If it's a bug, please describe the steps to reproduce it. Otherwise, remove this section.*
|
*If it's a bug, please describe the steps to reproduce it and PLEASE include an OBS log file. Otherwise, remove this section.*
|
||||||
|
|
||||||
#### Technical information
|
##### Technical information
|
||||||
- **Operating System** :
|
- **Operating System** :
|
||||||
- **OBS Studio version** :
|
- **OBS Studio version** :
|
||||||
|
|
||||||
#### Development Environment
|
##### Development Environment
|
||||||
*If you're trying to compile obs-websocket, please describe your compiler type and version (e.g: GCC 4.7, VC2013, ...), and the CMake settings used.
|
*If you're trying to compile obs-websocket, please describe your compiler type and version (e.g: GCC 4.7, VC2013, ...), and the CMake settings used.
|
||||||
Remove this section if it doesn't apply to your case.*
|
Remove this section if it doesn't apply to your case.*
|
||||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,3 +5,5 @@
|
|||||||
/build64/
|
/build64/
|
||||||
/release/
|
/release/
|
||||||
/installer/Output/
|
/installer/Output/
|
||||||
|
|
||||||
|
.vscode
|
18
.travis.yml
18
.travis.yml
@ -1,4 +1,5 @@
|
|||||||
language: cpp
|
language: cpp
|
||||||
|
|
||||||
env:
|
env:
|
||||||
global:
|
global:
|
||||||
# AWS key ID
|
# AWS key ID
|
||||||
@ -6,9 +7,18 @@ env:
|
|||||||
# AWS key secret
|
# AWS key secret
|
||||||
- secure: bGwljoP3E1OVBXLXox0O6p8kwQXLcNQ8YDKVa4H8u9Y+Ic7uqE4iV3rYS3ynNWSBMVRWY3ZbyClnhrCNwRhBAlcd8qWSJdpjVzs6HdQyzhuKa1P3V4FJPb7upGP/5R/DECGwex8Mun9dmXpYDak75LxfKIJUidPis5VDCYqul7k/xVVCou6Ctjpj7vQhWXDj2G/py+mdB8DERhymnQCtyK1Ziu8c4QlFKByZmnD72GFm/h3JPI1Pq1V2mz3x6x6GaYjb9Rdbd0UNwqjGQX4q2M/c3GEJa6B2JBCoTncawNZBNnPUF9qtv+zh0TNaNHMRWX13AJ/qYB+nVDub0C9b/6Mc48mt0Tv4ze15MproVrylZdV6qHYEG8yGPBqpTVbRP6gv6Y2TXIHWoTzqA+F/Gv2IDChyHXsld/MQQS2MSo5iaYktIrZKtX8Z0qAmTzPwIVBromaSI3vrE7UH0fRSQ6fAM8+Tn+MRthOBdqu23kS1dnG+X2CPbUhBfsJp0OSwVQD5jQtA51/sREVeGFiJvzQIkvwQDjb5MYilsRnwmoBXemkLmqaviXVY4rz1o5AIvz2pgZS2YggK1xHZCuI5tSjcNEkb77VwZTfsqrdDo9EJh6VgfdnGlHQhR2/A5hUJ4ANpJ/LgZlgfVp71Xg2GWQW6M4Znc5uj6A6xLBkO6FA=
|
- secure: bGwljoP3E1OVBXLXox0O6p8kwQXLcNQ8YDKVa4H8u9Y+Ic7uqE4iV3rYS3ynNWSBMVRWY3ZbyClnhrCNwRhBAlcd8qWSJdpjVzs6HdQyzhuKa1P3V4FJPb7upGP/5R/DECGwex8Mun9dmXpYDak75LxfKIJUidPis5VDCYqul7k/xVVCou6Ctjpj7vQhWXDj2G/py+mdB8DERhymnQCtyK1Ziu8c4QlFKByZmnD72GFm/h3JPI1Pq1V2mz3x6x6GaYjb9Rdbd0UNwqjGQX4q2M/c3GEJa6B2JBCoTncawNZBNnPUF9qtv+zh0TNaNHMRWX13AJ/qYB+nVDub0C9b/6Mc48mt0Tv4ze15MproVrylZdV6qHYEG8yGPBqpTVbRP6gv6Y2TXIHWoTzqA+F/Gv2IDChyHXsld/MQQS2MSo5iaYktIrZKtX8Z0qAmTzPwIVBromaSI3vrE7UH0fRSQ6fAM8+Tn+MRthOBdqu23kS1dnG+X2CPbUhBfsJp0OSwVQD5jQtA51/sREVeGFiJvzQIkvwQDjb5MYilsRnwmoBXemkLmqaviXVY4rz1o5AIvz2pgZS2YggK1xHZCuI5tSjcNEkb77VwZTfsqrdDo9EJh6VgfdnGlHQhR2/A5hUJ4ANpJ/LgZlgfVp71Xg2GWQW6M4Znc5uj6A6xLBkO6FA=
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- node_modules
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: linux
|
- os: linux
|
||||||
|
env: _generate_docs
|
||||||
|
script: "./CI/generate-docs.sh"
|
||||||
|
|
||||||
|
- os: linux
|
||||||
|
env: _linux_build
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
sudo: required
|
||||||
services:
|
services:
|
||||||
@ -23,10 +33,12 @@ matrix:
|
|||||||
- docker exec -it xenial /root/obs-websocket/CI/package-xenial.sh
|
- docker exec -it xenial /root/obs-websocket/CI/package-xenial.sh
|
||||||
|
|
||||||
- os: osx
|
- os: osx
|
||||||
|
env: _macos_build
|
||||||
osx_image: xcode8.3
|
osx_image: xcode8.3
|
||||||
before_install: "./CI/install-dependencies-osx.sh"
|
before_install: "./CI/install-dependencies-osx.sh"
|
||||||
script: "./CI/build-osx.sh"
|
script: "./CI/build-osx.sh"
|
||||||
after_success: "./CI/package-osx.sh"
|
after_success:
|
||||||
|
- ./CI/package-osx.sh
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
- provider: s3
|
- provider: s3
|
||||||
@ -39,7 +51,9 @@ deploy:
|
|||||||
acl: public_read
|
acl: public_read
|
||||||
on:
|
on:
|
||||||
repo: Palakis/obs-websocket
|
repo: Palakis/obs-websocket
|
||||||
condition: "$TRAVIS_OS_NAME = linux"
|
condition:
|
||||||
|
- "$TRAVIS_OS_NAME = linux"
|
||||||
|
- "-d /home/travis/package"
|
||||||
all_branches: true
|
all_branches: true
|
||||||
- provider: s3
|
- provider: s3
|
||||||
region: eu-central-1
|
region: eu-central-1
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
mkdir build && cd build
|
#export QT_PREFIX="$(find /usr/local/Cellar/qt5 -d 1 | tail -n 1)"
|
||||||
|
|
||||||
|
mkdir build && cd build
|
||||||
cmake .. \
|
cmake .. \
|
||||||
-DQTDIR=/usr/local/opt/qt5 \
|
-DQTDIR=/usr/local/opt/qt \
|
||||||
-DLIBOBS_INCLUDE_DIR=../../obs-studio/libobs \
|
-DLIBOBS_INCLUDE_DIR=../../obs-studio/libobs \
|
||||||
-DOBS_FRONTEND_LIB="$(pwd)/../../obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \
|
-DOBS_FRONTEND_LIB="$(pwd)/../../obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \
|
||||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||||
|
32
CI/generate-docs.sh
Executable file
32
CI/generate-docs.sh
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
echo "-- Generating documentation."
|
||||||
|
echo "-- Node version: $(node -v)"
|
||||||
|
echo "-- NPM version: $(npm -v)"
|
||||||
|
|
||||||
|
cd docs
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
echo "-- Documentation successfully generated."
|
||||||
|
|
||||||
|
if git diff --quiet; then
|
||||||
|
echo "-- No documentation changes to commit."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "master" ]; then
|
||||||
|
echo "-- Skipping documentation deployment because this is either a pull request or a non-master branch."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
REMOTE_URL="$(git config remote.origin.url)"
|
||||||
|
TARGET_REPO=${REMOTE_URL/https:\/\/github.com\//github.com/}
|
||||||
|
GITHUB_REPO=https://${GH_TOKEN:-git}@${TARGET_REPO}
|
||||||
|
|
||||||
|
git config user.name "Travis CI"
|
||||||
|
git config user.email "$COMMIT_AUTHOR_EMAIL"
|
||||||
|
|
||||||
|
git add ./generated
|
||||||
|
git pull
|
||||||
|
git commit -m "docs(travis): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]"
|
||||||
|
git push -q $GITHUB_REPO HEAD:$TRAVIS_BRANCH
|
@ -7,16 +7,17 @@ brew install ffmpeg
|
|||||||
brew install libav
|
brew install libav
|
||||||
|
|
||||||
# qtwebsockets deps
|
# qtwebsockets deps
|
||||||
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/fdb7c6e960e830b3bf630850c0002c5df9f68ed8/Formula/qt5.rb
|
brew install qt5
|
||||||
|
#echo "Qt path: $(find /usr/local/Cellar/qt5 -d 1 | tail -n 1)"
|
||||||
|
|
||||||
# Build obs-studio
|
# Build obs-studio
|
||||||
cd ..
|
cd ..
|
||||||
git clone --recursive https://github.com/jp9000/obs-studio
|
git clone --recursive https://github.com/jp9000/obs-studio
|
||||||
cd obs-studio
|
cd obs-studio
|
||||||
git checkout 19.0.2
|
git checkout 19.0.3
|
||||||
mkdir build && cd build
|
mkdir build && cd build
|
||||||
cmake .. \
|
cmake .. \
|
||||||
-DCMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake \
|
-DCMAKE_PREFIX_PATH=/usr/local/opt/qt/lib/cmake \
|
||||||
&& make -j4
|
&& make -j4
|
||||||
|
|
||||||
sudo make install
|
sudo make install
|
||||||
|
@ -48,7 +48,7 @@ apt-get install -y libqt5websockets5-dev
|
|||||||
cd /root
|
cd /root
|
||||||
git clone https://github.com/jp9000/obs-studio ./obs-studio
|
git clone https://github.com/jp9000/obs-studio ./obs-studio
|
||||||
cd obs-studio
|
cd obs-studio
|
||||||
git checkout 19.0.2
|
git checkout 19.0.3
|
||||||
mkdir build && cd build
|
mkdir build && cd build
|
||||||
cmake -DUNIX_STRUCTURE=1 -DCMAKE_INSTALL_PREFIX=/usr ..
|
cmake -DUNIX_STRUCTURE=1 -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||||
make -j4
|
make -j4
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
set -e
|
set -e
|
||||||
|
|
||||||
echo "-- Preparing package build"
|
echo "-- Preparing package build"
|
||||||
export QT_PREFIX="/usr/local/opt/qt5"
|
export QT_CELLAR_PREFIX="$(find /usr/local/Cellar/qt -d 1 | tail -n 1)"
|
||||||
|
|
||||||
export WS_LIB="$QT_PREFIX/lib/QtWebSockets.framework/QtWebSockets"
|
export WS_LIB="/usr/local/opt/qt/lib/QtWebSockets.framework/QtWebSockets"
|
||||||
export NET_LIB="$QT_PREFIX/lib/QtNetwork.framework/QtNetwork"
|
export NET_LIB="/usr/local/opt/qt/lib/QtNetwork.framework/QtNetwork"
|
||||||
|
|
||||||
export GIT_HASH=$(git rev-parse --short HEAD)
|
export GIT_HASH=$(git rev-parse --short HEAD)
|
||||||
|
|
||||||
@ -27,28 +27,35 @@ cp $NET_LIB ./build
|
|||||||
chmod +rw ./build/QtWebSockets ./build/QtNetwork
|
chmod +rw ./build/QtWebSockets ./build/QtNetwork
|
||||||
|
|
||||||
echo "-- Modifying QtNetwork"
|
echo "-- Modifying QtNetwork"
|
||||||
# TODO : put a loop in there
|
|
||||||
install_name_tool \
|
install_name_tool \
|
||||||
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
|
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
|
||||||
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
|
-change $QT_CELLAR_PREFIX/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
|
||||||
./build/QtNetwork
|
./build/QtNetwork
|
||||||
|
|
||||||
echo "-- Modifying QtWebSockets"
|
echo "-- Modifying QtWebSockets"
|
||||||
install_name_tool \
|
install_name_tool \
|
||||||
-change /usr/local/opt/qt/lib/QtWebSockets.framework/Versions/5/QtWebSockets @rpath/QtWebSockets \
|
-change /usr/local/opt/qt/lib/QtWebSockets.framework/Versions/5/QtWebSockets @rpath/QtWebSockets \
|
||||||
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
|
-change $QT_CELLAR_PREFIX/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
|
||||||
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
|
-change $QT_CELLAR_PREFIX/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
|
||||||
./build/QtWebSockets
|
./build/QtWebSockets
|
||||||
|
|
||||||
echo "-- Modifying obs-websocket.so"
|
echo "-- Modifying obs-websocket.so"
|
||||||
install_name_tool \
|
install_name_tool \
|
||||||
-change "$QT_PREFIX/lib/QtWebSockets.framework/Versions/5/QtWebSockets" @rpath/QtWebSockets \
|
-change /usr/local/opt/qt/lib/QtWebSockets.framework/Versions/5/QtWebSockets @rpath/QtWebSockets \
|
||||||
-change "$QT_PREFIX/lib/QtWidgets.framework/Versions/5/QtWidgets" @rpath/QtWidgets \
|
-change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets \
|
||||||
-change "$QT_PREFIX/lib/QtNetwork.framework/Versions/5/QtNetwork" @rpath/QtNetwork \
|
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
|
||||||
-change "$QT_PREFIX/lib/QtGui.framework/Versions/5/QtGui" @rpath/QtGui \
|
-change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @rpath/QtGui \
|
||||||
-change "$QT_PREFIX/lib/QtCore.framework/Versions/5/QtCore" @rpath/QtCore \
|
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
|
||||||
./build/obs-websocket.so
|
./build/obs-websocket.so
|
||||||
|
|
||||||
|
# Check if replacement worked
|
||||||
|
echo "-- Dependencies for QtNetwork"
|
||||||
|
otool -L ./build/QtNetwork
|
||||||
|
echo "-- Dependencies for QtWebSockets"
|
||||||
|
otool -L ./build/QtWebSockets
|
||||||
|
echo "-- Dependencies for obs-websocket"
|
||||||
|
otool -L ./build/obs-websocket.so
|
||||||
|
|
||||||
chmod -w ./build/QtWebSockets ./build/QtNetwork
|
chmod -w ./build/QtWebSockets ./build/QtNetwork
|
||||||
|
|
||||||
echo "-- Actual package build"
|
echo "-- Actual package build"
|
||||||
|
@ -65,6 +65,18 @@ if(WIN32)
|
|||||||
message(FATAL_ERROR "Could not find OBS Frontend API\'s library !")
|
message(FATAL_ERROR "Could not find OBS Frontend API\'s library !")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||||
|
set(ARCH_NAME "64bit")
|
||||||
|
set(OBS_BUILDDIR_ARCH "build64")
|
||||||
|
else()
|
||||||
|
set(ARCH_NAME "32bit")
|
||||||
|
set(OBS_BUILDDIR_ARCH "build32")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
include_directories(
|
||||||
|
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/UI"
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(obs-websocket
|
target_link_libraries(obs-websocket
|
||||||
"${OBS_FRONTEND_LIB}")
|
"${OBS_FRONTEND_LIB}")
|
||||||
|
|
||||||
@ -84,12 +96,6 @@ if(WIN32)
|
|||||||
# The "release" folder has a structure similar OBS' one on Windows
|
# The "release" folder has a structure similar OBS' one on Windows
|
||||||
set(RELEASE_DIR "${PROJECT_SOURCE_DIR}/release")
|
set(RELEASE_DIR "${PROJECT_SOURCE_DIR}/release")
|
||||||
|
|
||||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
|
||||||
set(ARCH_NAME "64bit")
|
|
||||||
else()
|
|
||||||
set(ARCH_NAME "32bit")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_custom_command(TARGET obs-websocket POST_BUILD
|
add_custom_command(TARGET obs-websocket POST_BUILD
|
||||||
COMMAND if $<CONFIG:Release>==1 (
|
COMMAND if $<CONFIG:Release>==1 (
|
||||||
"${CMAKE_COMMAND}" -E make_directory
|
"${CMAKE_COMMAND}" -E make_directory
|
||||||
@ -105,6 +111,23 @@ if(WIN32)
|
|||||||
"${QTDIR}/bin/Qt5WebSockets.dll"
|
"${QTDIR}/bin/Qt5WebSockets.dll"
|
||||||
"${QTDIR}/bin/Qt5Network.dll"
|
"${QTDIR}/bin/Qt5Network.dll"
|
||||||
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
|
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
|
||||||
|
|
||||||
|
# Copy to obs-studio dev environment for immediate testing
|
||||||
|
COMMAND if $<CONFIG:Debug>==1 (
|
||||||
|
"${CMAKE_COMMAND}" -E copy
|
||||||
|
"$<TARGET_FILE:obs-websocket>"
|
||||||
|
"${QTDIR}/bin/Qt5WebSocketsd.dll"
|
||||||
|
"${QTDIR}/bin/Qt5Networkd.dll"
|
||||||
|
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/obs-plugins/${ARCH_NAME}")
|
||||||
|
|
||||||
|
COMMAND if $<CONFIG:Debug>==1 (
|
||||||
|
"${CMAKE_COMMAND}" -E make_directory
|
||||||
|
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/data/obs-plugins/obs-websocket")
|
||||||
|
|
||||||
|
COMMAND if $<CONFIG:Debug>==1 (
|
||||||
|
"${CMAKE_COMMAND}" -E copy_directory
|
||||||
|
"${PROJECT_SOURCE_DIR}/data"
|
||||||
|
"${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$<CONFIG>/data/obs-plugins/obs-websocket")
|
||||||
)
|
)
|
||||||
# --- End of sub-section ---
|
# --- End of sub-section ---
|
||||||
|
|
||||||
|
57
Config.cpp
57
Config.cpp
@ -22,8 +22,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
|||||||
#include <util/config-file.h>
|
#include <util/config-file.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "Config.h"
|
|
||||||
|
|
||||||
#define SECTION_NAME "WebsocketAPI"
|
#define SECTION_NAME "WebsocketAPI"
|
||||||
#define PARAM_ENABLE "ServerEnabled"
|
#define PARAM_ENABLE "ServerEnabled"
|
||||||
#define PARAM_PORT "ServerPort"
|
#define PARAM_PORT "ServerPort"
|
||||||
@ -32,25 +30,21 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
|||||||
#define PARAM_SECRET "AuthSecret"
|
#define PARAM_SECRET "AuthSecret"
|
||||||
#define PARAM_SALT "AuthSalt"
|
#define PARAM_SALT "AuthSalt"
|
||||||
|
|
||||||
Config *Config::_instance = new Config();
|
#include "Config.h"
|
||||||
|
|
||||||
Config::Config()
|
Config* Config::_instance = new Config();
|
||||||
{
|
|
||||||
// Default settings
|
|
||||||
ServerEnabled = true;
|
|
||||||
ServerPort = 4444;
|
|
||||||
|
|
||||||
DebugEnabled = false;
|
|
||||||
|
|
||||||
AuthRequired = false;
|
|
||||||
Secret = "";
|
|
||||||
Salt = "";
|
|
||||||
SettingsLoaded = false;
|
|
||||||
|
|
||||||
|
Config::Config() :
|
||||||
|
ServerEnabled(true),
|
||||||
|
ServerPort(4444),
|
||||||
|
DebugEnabled(false),
|
||||||
|
AuthRequired(false),
|
||||||
|
Secret(""),
|
||||||
|
Salt(""),
|
||||||
|
SettingsLoaded(false) {
|
||||||
// OBS Config defaults
|
// OBS Config defaults
|
||||||
config_t* obs_config = obs_frontend_get_global_config();
|
config_t* obs_config = obs_frontend_get_global_config();
|
||||||
if (obs_config)
|
if (obs_config) {
|
||||||
{
|
|
||||||
config_set_default_bool(obs_config,
|
config_set_default_bool(obs_config,
|
||||||
SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||||
config_set_default_uint(obs_config,
|
config_set_default_uint(obs_config,
|
||||||
@ -70,19 +64,16 @@ Config::Config()
|
|||||||
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);
|
||||||
//mbedtls_ctr_drbg_set_prediction_resistance(&rng, MBEDTLS_CTR_DRBG_PR_ON);
|
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::Load()
|
void Config::Load() {
|
||||||
{
|
|
||||||
config_t* obs_config = obs_frontend_get_global_config();
|
config_t* obs_config = obs_frontend_get_global_config();
|
||||||
|
|
||||||
ServerEnabled = config_get_bool(obs_config, SECTION_NAME, PARAM_ENABLE);
|
ServerEnabled = config_get_bool(obs_config, SECTION_NAME, PARAM_ENABLE);
|
||||||
@ -95,8 +86,7 @@ void Config::Load()
|
|||||||
Salt = config_get_string(obs_config, SECTION_NAME, PARAM_SALT);
|
Salt = config_get_string(obs_config, SECTION_NAME, PARAM_SALT);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Config::Save()
|
void Config::Save() {
|
||||||
{
|
|
||||||
config_t* obs_config = obs_frontend_get_global_config();
|
config_t* obs_config = obs_frontend_get_global_config();
|
||||||
|
|
||||||
config_set_bool(obs_config, SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
config_set_bool(obs_config, SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||||
@ -111,8 +101,7 @@ void Config::Save()
|
|||||||
config_save(obs_config);
|
config_save(obs_config);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* Config::GenerateSalt()
|
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);
|
||||||
@ -128,8 +117,7 @@ const char* Config::GenerateSalt()
|
|||||||
return salt;
|
return salt;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* Config::GenerateSecret(const char *password, const char *salt)
|
const char* Config::GenerateSecret(const char* password, const char* salt) {
|
||||||
{
|
|
||||||
// Concatenate the password and the salt
|
// Concatenate the password and the salt
|
||||||
std::string passAndSalt = "";
|
std::string passAndSalt = "";
|
||||||
passAndSalt += password;
|
passAndSalt += password;
|
||||||
@ -152,17 +140,15 @@ const char* Config::GenerateSecret(const char *password, const char *salt)
|
|||||||
return challenge;
|
return 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);
|
|
||||||
|
|
||||||
this->Salt = new_salt;
|
this->Salt = new_salt;
|
||||||
this->Secret = new_challenge;
|
this->Secret = new_challenge;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Config::CheckAuth(const char *response)
|
bool Config::CheckAuth(const char* response) {
|
||||||
{
|
|
||||||
// Concatenate auth secret with the challenge sent to the user
|
// Concatenate auth secret with the challenge sent to the user
|
||||||
std::string challengeAndResponse = "";
|
std::string challengeAndResponse = "";
|
||||||
challengeAndResponse += this->Secret;
|
challengeAndResponse += this->Secret;
|
||||||
@ -193,7 +179,6 @@ bool Config::CheckAuth(const char *response)
|
|||||||
return authSuccess;
|
return authSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
Config* Config::Current()
|
Config* Config::Current() {
|
||||||
{
|
|
||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
|
17
Config.h
17
Config.h
@ -22,19 +22,18 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
|||||||
#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 Load();
|
||||||
void Save();
|
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(
|
static const char* GenerateSecret(
|
||||||
const char *password, const char *salt);
|
const char* password, const char* salt);
|
||||||
|
|
||||||
bool ServerEnabled;
|
bool ServerEnabled;
|
||||||
uint64_t ServerPort;
|
uint64_t ServerPort;
|
||||||
@ -42,15 +41,15 @@ class Config
|
|||||||
bool DebugEnabled;
|
bool DebugEnabled;
|
||||||
|
|
||||||
bool AuthRequired;
|
bool AuthRequired;
|
||||||
const char *Secret;
|
const char* Secret;
|
||||||
const char *Salt;
|
const char* Salt;
|
||||||
const char *SessionChallenge;
|
const char* SessionChallenge;
|
||||||
bool SettingsLoaded;
|
bool SettingsLoaded;
|
||||||
|
|
||||||
static Config* Current();
|
static Config* Current();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static Config *_instance;
|
static Config* _instance;
|
||||||
mbedtls_entropy_context entropy;
|
mbedtls_entropy_context entropy;
|
||||||
mbedtls_ctr_drbg_context rng;
|
mbedtls_ctr_drbg_context rng;
|
||||||
};
|
};
|
||||||
|
967
PROTOCOL.md
967
PROTOCOL.md
@ -1,967 +0,0 @@
|
|||||||
obs-websocket 4.1 protocol reference
|
|
||||||
================================
|
|
||||||
**This is the reference for the latest 4.1 developement build. [See here for obs-websocket 4.0.0!](https://github.com/Palakis/obs-websocket/blob/4.0.0/PROTOCOL.md)**
|
|
||||||
|
|
||||||
## General Introduction
|
|
||||||
Messages exchanged between the client and the server are JSON objects.
|
|
||||||
The protocol in general is based on the OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio.
|
|
||||||
|
|
||||||
### Table of contents
|
|
||||||
* [Authentication](#authentication)
|
|
||||||
* [Events](#events)
|
|
||||||
- [Description](#description)
|
|
||||||
- [Event Types](#event-types)
|
|
||||||
- **Scenes**
|
|
||||||
- ["SwitchScenes"](#switchscenes)
|
|
||||||
- ["ScenesChanged"](#sceneschanged)
|
|
||||||
- **Scene Items**
|
|
||||||
- ["SourceOrderChanged"](#sourceorderchanged)
|
|
||||||
- ["SceneItemAdded"](#sceneitemadded)
|
|
||||||
- ["SceneItemRemoved"](#sceneitemremoved)
|
|
||||||
- ["SceneItemVisibilityChanged"](#sceneitemvisibilitychanged)
|
|
||||||
- **Scene Collections**
|
|
||||||
- ["SceneCollectionChanged"](#scenecollectionchanged)
|
|
||||||
- ["SceneCollectionListChanged"](#scenecollectionlistchanged)
|
|
||||||
- **Transitions**
|
|
||||||
- ["SwitchTransition"](#switchtransition)
|
|
||||||
- ["TransitionDurationChanged"](#transitiondurationchanged)
|
|
||||||
- ["TransitionListChanged"](#transitionlistchanged)
|
|
||||||
- ["TransitionBegin"](#transitionbegin)
|
|
||||||
- **Studio Mode**
|
|
||||||
- ["PreviewSceneChanged"](#previewscenechanged)
|
|
||||||
- ["StudioModeSwitched"](#studiomodeswitched)
|
|
||||||
- **Profiles**
|
|
||||||
- ["ProfileChanged"](#profilechanged)
|
|
||||||
- ["ProfileListChanged"](#profilelistchanged)
|
|
||||||
- **Streaming**
|
|
||||||
- ["StreamStarting"](#streamstarting)
|
|
||||||
- ["StreamStarted"](#streamstarted)
|
|
||||||
- ["StreamStopping"](#streamstopping)
|
|
||||||
- ["StreamStopped"](#streamstopped)
|
|
||||||
- ["StreamStatus"](#streamstatus)
|
|
||||||
- **Recording**
|
|
||||||
- ["RecordingStarting"](#recordingstarting)
|
|
||||||
- ["RecordingStarted"](#recordingstarted)
|
|
||||||
- ["RecordingStopping"](#recordingstopping)
|
|
||||||
- ["RecordingStopped"](#recordingstopped)
|
|
||||||
- **Other**
|
|
||||||
- ["Exiting"](#exiting)
|
|
||||||
* [Requests](#requests)
|
|
||||||
- [Description](#description-1)
|
|
||||||
- [Request Types](#request-types)
|
|
||||||
- **General**
|
|
||||||
- ["GetVersion"](#getversion)
|
|
||||||
- ["GetAuthRequired"](#getauthrequired)
|
|
||||||
- ["Authenticate"](#authenticate)
|
|
||||||
- **Scenes**
|
|
||||||
- ["GetCurrentScene"](#getcurrentscene)
|
|
||||||
- ["SetCurrentScene"](#setcurrentscene)
|
|
||||||
- ["GetSceneList"](#getscenelist)
|
|
||||||
- **Studio Mode**
|
|
||||||
- ["GetStudioModeStatus"](#getstudiomodestatus)
|
|
||||||
- ["SetPreviewScene"](#setpreviewscene)
|
|
||||||
- ["TransitionToProgram"](#transitiontoprogram)
|
|
||||||
- ["EnableStudioMode"](#enablestudiomode)
|
|
||||||
- ["DisableStudioMode"](#disablestudiomode)
|
|
||||||
- ["ToggleStudioMode"](#togglestudiomode)
|
|
||||||
- **Streaming**
|
|
||||||
- ["StartStopStreaming"](#startstopstreaming)
|
|
||||||
- ["StartStreaming"](#startstreaming)
|
|
||||||
- ["StopStreaming"](#stopstreaming)
|
|
||||||
- ["GetStreamingStatus"](#getstreamingstatus)
|
|
||||||
- **Recording**
|
|
||||||
- ["StartStopRecording"](#startstoprecording)
|
|
||||||
- ["StartRecording"](#startrecording)
|
|
||||||
- ["StopRecording"](#stoprecording)
|
|
||||||
- ["GetStreamingStatus"](#getstreamingstatus)
|
|
||||||
- ["SetRecordingFolder"](#setrecordingfolder)
|
|
||||||
- ["GetRecordingFolder"](#getrecordingfolder)
|
|
||||||
- **Transitions**
|
|
||||||
- ["GetTransitionList"](#gettransitionlist)
|
|
||||||
- ["GetCurrentTransition"](#getcurrenttransition)
|
|
||||||
- ["SetCurrentTransition"](#setcurrenttransition)
|
|
||||||
- ["GetTransitionDuration"](#gettransitionduration)
|
|
||||||
- ["SetTransitionDuration"](#settransitionduration)
|
|
||||||
- **Sources**
|
|
||||||
- ["GetCurrentScene"](#getcurrentscene)
|
|
||||||
- ["GetSceneList"](#getscenelist)
|
|
||||||
- ["GetSpecialSources"](#getspecialsources)
|
|
||||||
- ["GetTextGDIPlusProperties"](#gettextgdiplusproperties)
|
|
||||||
- ["SetTextGDIPlusProperties"](#settextgdiplusproperties)
|
|
||||||
- ["GetBrowserSourceProperties"](#getbrowsersourceproperties)
|
|
||||||
- ["SetBrowserSourceProperties"](#setbrowsersourceproperties)
|
|
||||||
- ["SetVolume"](#setvolume)
|
|
||||||
- ["GetVolume"](#getvolume)
|
|
||||||
- ["SetMute"](#setmute)
|
|
||||||
- ["GetMute"](#getmute)
|
|
||||||
- ["ToggleMute"](#togglemute)
|
|
||||||
- **Scene Items**
|
|
||||||
- ["SetSceneItemRender"](#setsourcerender) (a.k.a `SetSourceRender`)
|
|
||||||
- ["SetSceneItemPosition"](#setsceneitemposition)
|
|
||||||
- ["SetSceneItemTransform"](#setsceneitemtransform)
|
|
||||||
- ["SetSceneItemCrop"](#setsceneitemcrop)
|
|
||||||
- **Scene Collections**
|
|
||||||
- ["ListSceneCollections"](#listscenecollections)
|
|
||||||
- ["SetCurrentSceneCollection"](#setcurrentscenecollection)
|
|
||||||
- ["GetCurrentSceneCollection"](#getcurrentscenecollection)
|
|
||||||
- **Streaming Server Settings**
|
|
||||||
- ["GetStreamSettings"](#getstreamsettings)
|
|
||||||
- ["SetStreamSettings"](#setstreamsettings)
|
|
||||||
- ["SaveStreamSettings"](#savestreamsettings)
|
|
||||||
- **Profiles**
|
|
||||||
- ["ListProfiles"](#listprofiles)
|
|
||||||
- ["SetCurrentProfile"](#setcurrentprofile)
|
|
||||||
- ["GetCurrentProfile"](#getcurrentprofile)
|
|
||||||
|
|
||||||
## Authentication
|
|
||||||
A call to [`GetAuthRequired`](#getauthrequired) gives the client two elements :
|
|
||||||
- A challenge : a random string that will be used to generate the auth response
|
|
||||||
- A salt : applied to the password when generating the auth response
|
|
||||||
|
|
||||||
The client knows a password and must it to authenticate itself to the server.
|
|
||||||
However, it must keep this password secret, and it is the purpose of the authentication mecanism used by obs-websocket.
|
|
||||||
|
|
||||||
After a call to [`GetAuthRequired`](#getauthrequired), the client knows a password (kept secret), a challenge and a salt (sent by the server).
|
|
||||||
To generate the answer to the auth challenge, follow this procedure :
|
|
||||||
- Concatenate the password with the salt sent by the server (in this order : password + server salt), then generate a binary SHA256 hash of the result and encode the resulting SHA256 binary hash to base64.
|
|
||||||
- Concatenate the base64 secret with the challenge sent by the server (in this order : base64 secret + server challenge), then generate a binary SHA256 hash of the result and encode it to base64.
|
|
||||||
- Voilà, this last base64 string is the auth response. You may now use it to authenticate to the server with the `Authenticate` request.
|
|
||||||
|
|
||||||
Here's how it looks in pseudocode :
|
|
||||||
```
|
|
||||||
password = "supersecretpassword"
|
|
||||||
challenge = "ztTBnnuqrqaKDzRM3xcVdbYm"
|
|
||||||
salt = "PZVbYpvAnZut2SS6JNJytDm9"
|
|
||||||
|
|
||||||
secret_string = password + salt
|
|
||||||
secret_hash = binary_sha256(secret_string)
|
|
||||||
secret = base64_encode(secret_hash)
|
|
||||||
|
|
||||||
auth_response_string = secret + challenge
|
|
||||||
auth_response_hash = binary_sha256(auth_response_string)
|
|
||||||
auth_response = base64_encode(auth_response_hash)
|
|
||||||
```
|
|
||||||
|
|
||||||
A client can then authenticate to the server by calling [`Authenticate`](#authenticate) with the computed challenge response.
|
|
||||||
|
|
||||||
## Events
|
|
||||||
### Description
|
|
||||||
Events are sent exclusively by the server and broadcast to each connected client.
|
|
||||||
An event message will contain at least one field :
|
|
||||||
- **update-type** (string) : the type of event
|
|
||||||
- **stream-timecode** (string, optional) : time elapsed between now and stream start (only present if OBS Studio is streaming)
|
|
||||||
- **rec-timecode** (string, optional) : time elapsed between now and recording start (only present if OBS Studio is recording)
|
|
||||||
|
|
||||||
Timecodes are in the following format : HH:MM:SS.mmm
|
|
||||||
|
|
||||||
Additional fields will be present in the event message depending on the event type.
|
|
||||||
|
|
||||||
### Event Types
|
|
||||||
#### "SwitchScenes"
|
|
||||||
OBS is switching to another scene (called at the end of the transition).
|
|
||||||
- **scene-name** (string) : The name of the scene being switched to.
|
|
||||||
- **sources** (array of objects) : List of sources composing the scene. Same specification as [`GetCurrentScene`](#getcurrentscene).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "ScenesChanged"
|
|
||||||
The scene list has been modified (Scenes have been added, removed, or renamed).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "SourceOrderChanged"
|
|
||||||
Scene items have been reordered.
|
|
||||||
- **"scene-name"** (string) : name of the scene where items have been reordered
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "SceneItemAdded"
|
|
||||||
An item has been added to the current scene.
|
|
||||||
- **"scene-name"** (string) : name of the scene
|
|
||||||
- **"item-name"** (string) : name of the item added to **scene-name**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "SceneItemRemoved"
|
|
||||||
An item has been removed from the current scene.
|
|
||||||
- **"scene-name"** (string) : name of the scene
|
|
||||||
- **"item-name"** (string) : name of the item removed from **scene-name**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "SceneItemVisibilityChanged"
|
|
||||||
An item's visibility has been toggled.
|
|
||||||
- **"scene-name"** (string) : name of the scene
|
|
||||||
- **"item-name"** (string) : name of the item in **scene-name**
|
|
||||||
- **"item-visible"** (bool) : new visibility of item **item-name**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "SceneCollectionChanged"
|
|
||||||
Triggered when switching to another scene collection or when renaming the current scene collection.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "SceneCollectionListChanged"
|
|
||||||
Triggered when a scene collection is created, added, renamed or removed.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "SwitchTransition"
|
|
||||||
The active transition has been changed.
|
|
||||||
- **transition-name** (string) : The name of the active transition.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "TransitionDurationChanged"
|
|
||||||
Triggered when the transition duration has changed.
|
|
||||||
- **"new-duration"** (integer) : new transition duration
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "TransitionListChanged"
|
|
||||||
The list of available transitions has been modified (Transitions have been added, removed, or renamed).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "TransitionBegin"
|
|
||||||
A transition other than "Cut" has begun.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "PreviewSceneChanged"
|
|
||||||
The selected Preview scene changed (only in Studio Mode).
|
|
||||||
- **scene-name** (string) : Name of the scene being previewed.
|
|
||||||
- **sources** (array of objects) : List of sources composing the scene. Same specification as [`GetCurrentScene`](#getcurrentscene).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "StudioModeSwitched"
|
|
||||||
Studio Mode has been switched on or off.
|
|
||||||
- **"new-state"** (bool) : new state of Studio Mode: true if enabled, false if disabled.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "ProfileChanged"
|
|
||||||
Triggered when switching to another profile or when renaming the current profile.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "ProfileListChanged"
|
|
||||||
Triggered when a profile is created, added, renamed or removed.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "StreamStarting"
|
|
||||||
A request to start streaming has been issued.
|
|
||||||
- **preview-only** (bool) : Always false.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "StreamStarted"
|
|
||||||
Streaming started successfully.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "StreamStopping"
|
|
||||||
A request to stop streaming has been issued.
|
|
||||||
- **preview-only** (bool) : Always false.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "StreamStopped"
|
|
||||||
Streaming stopped successfully.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "RecordingStarting"
|
|
||||||
A request to start recording has been issued.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "RecordingStarted"
|
|
||||||
Recording started successfully.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "RecordingStopping"
|
|
||||||
A request to stop streaming has been issued.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "RecordingStopped"
|
|
||||||
Recording stopped successfully.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "StreamStatus"
|
|
||||||
Sent every 2 seconds with the following information :
|
|
||||||
- **streaming** (bool) : Current Streaming state.
|
|
||||||
- **recording** (bool) : Current Recording state.
|
|
||||||
- **preview-only** (bool) : Always false.
|
|
||||||
- **bytes-per-sec** (integer) : Amount of data per second (in bytes) transmitted by the stream encoder.
|
|
||||||
- **kbits-per-sec** (integer) : "bytes-per-sec" converted to kilobits per second
|
|
||||||
- **strain** (double) : Percentage of dropped frames
|
|
||||||
- **total-stream-time** (integer) : Total time (in seconds) since the stream started.
|
|
||||||
- **num-total-frames** (integer) : Total number of frames transmitted since the stream started.
|
|
||||||
- **num-dropped-frames** (integer) : Number of frames dropped by the encoder since the stream started.
|
|
||||||
- **fps** (double) : Current framerate.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "Exiting"
|
|
||||||
OBS is exiting.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Requests
|
|
||||||
|
|
||||||
### Description
|
|
||||||
Requests are sent by the client and must have at least the following two fields :
|
|
||||||
- **"request-type"** (string) : One of the request types listed in the sub-section "[Requests Types](#request-types)".
|
|
||||||
- **"message-id"** (string) : An identifier defined by the client which will be embedded in the server response.
|
|
||||||
|
|
||||||
Depending on the request type additional fields may be required (see the "[Request Types](#request-types)" section below for more information).
|
|
||||||
|
|
||||||
Once a request is sent, the server will return a JSON response with the following fields :
|
|
||||||
- **"message-id"** (string) : The identifier specified in the request.
|
|
||||||
- **"status"** (string) : Response status, will be one of the following : "ok", "error"
|
|
||||||
- **"error"** (string) : The error message associated with an "error" status.
|
|
||||||
|
|
||||||
Depending on the request type additional fields may be present (see the "[Request Types](#request-types)" section below for more information).
|
|
||||||
|
|
||||||
### Request Types
|
|
||||||
#### "GetVersion"
|
|
||||||
Returns the latest version of the plugin and the API.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
__Response__ : always OK, with these additional fields :
|
|
||||||
- **"version"** (double) : OBSRemote API version. Fixed to 1.1 for retrocompatibility.
|
|
||||||
- **"obs-websocket-version"** (string) : obs-websocket version string
|
|
||||||
- **"obs-studio-version"** (string) : OBS Studio version string
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "GetAuthRequired"
|
|
||||||
Tells the client if authentication is required. If it is, authentication parameters "challenge" and "salt" are passed in the response fields (see "Authentication").
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
__Response__ : always OK, with these additional fields :
|
|
||||||
- **"authRequired"** (bool)
|
|
||||||
- **"challenge"** (string)
|
|
||||||
- **"salt"** (string)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "Authenticate"
|
|
||||||
Try to authenticate the client on the server.
|
|
||||||
|
|
||||||
__Request fields__ :
|
|
||||||
- **"auth"** (string) : response to the auth challenge (see "Authentication").
|
|
||||||
|
|
||||||
__Response__ : OK if auth succeeded, error if invalid credentials. No additional fields.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "GetCurrentScene"
|
|
||||||
Get the current scene's name and items.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
__Response__ : always OK, with these additional fields :
|
|
||||||
- **"name"** (string) : name of the current scene
|
|
||||||
- **"sources"** (array of objects) : ordered list of the current scene's items descriptions
|
|
||||||
|
|
||||||
Objects in the "sources" array have the following fields :
|
|
||||||
- **"name"** (string) : name of the source associated with the scene item
|
|
||||||
- **"type"** (string) : internal source type name
|
|
||||||
- **"volume"** (double) : audio volume of the source, ranging from 0.0 to 1.0
|
|
||||||
- **"x"** (double) : X coordinate of the top-left corner of the item in the scene
|
|
||||||
- **"y"** (double) : Y coordinate of the top-left corner of the item in the scene
|
|
||||||
- **"source_cx"** (integer) : width of the item (without scale applied)
|
|
||||||
- **"source_cy"** (integer) : height of the item (without scale applied)
|
|
||||||
- **"cx"** (double) : width of the item (with scale applied)
|
|
||||||
- **"cy"** (double) : height of the item (with scale applied)
|
|
||||||
- **"render"** (bool) : visibility of the source in the scene
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "SetCurrentScene"
|
|
||||||
Switch to the scene specified in "scene-name".
|
|
||||||
|
|
||||||
__Request fields__ :
|
|
||||||
- **"scene-name"** (string) : name of the scene to switch to.
|
|
||||||
|
|
||||||
__Response__ : always OK if scene exists, error if it doesn't. No additional fields
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "GetSceneList"
|
|
||||||
List OBS' scenes.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
__Response__ : always OK, with these additional fields :
|
|
||||||
- **"current-scene"** (string) : name of the currently active scene
|
|
||||||
- **"scenes"** (array of objects) : ordered list of scene descriptions (see `GetCurrentScene` for reference)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "SetSourceRender"
|
|
||||||
Show or hide a specific source in the current scene.
|
|
||||||
|
|
||||||
__Request fields__ :
|
|
||||||
- **"source"** (string) : name of the source in the currently active scene.
|
|
||||||
- **"render"** (bool) : desired visibility
|
|
||||||
- **"scene-name"** (string; optional) : name of the scene the source belongs to. defaults to current scene.
|
|
||||||
|
|
||||||
__Response__ : OK if source exists in the current scene, error otherwise.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "GetStudioModeStatus"
|
|
||||||
Tells if Studio Mode is currently enabled or disabled.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
__Response__ : always OK, with these additional fields :
|
|
||||||
- **"studio-mode"** (bool) : true if OBS is in Studio Mode, false otherwise.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "GetPreviewScene"
|
|
||||||
Studio Mode only. Gets the name of the currently Previewed scene, along with a list of its sources.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
__Response__ : OK if Studio Mode is enabled, with the same fields as [`GetCurrentScene`](#getcurrentscene), error otherwise.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "SetPreviewScene"
|
|
||||||
Studio Mode only. Sets the specified scene as the Previewed scene in Studio Mode.
|
|
||||||
|
|
||||||
__Request fields__ :
|
|
||||||
- **"scene-name"** (string) : name of the scene to selected as the preview of Studio Mode
|
|
||||||
|
|
||||||
__Response__ : OK if Studio Mode is enabled and specified scene exists, error otherwise.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "TransitionToProgram"
|
|
||||||
Studio Mode only. Transitions the currently previewed scene to Program (main output).
|
|
||||||
|
|
||||||
__Request fields__ :
|
|
||||||
- **"with-transition"** (object, optional) : if specified, use this transition when switching from preview to program. This will change the current transition in the frontend to this one.
|
|
||||||
|
|
||||||
__Response__ : OK if studio mode is enabled and optional transition exists, error otherwise.
|
|
||||||
|
|
||||||
An object passed as `"with-transition"` in a request must have the following fields :
|
|
||||||
- **"name"** (string, optional) : transition name
|
|
||||||
- **"duration"** (integer, optional) : transition duration in milliseconds
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "EnableStudioMode"
|
|
||||||
Enables Studio Mode.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
__Response__ : always OK. No additional fields.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "DisableStudioMode"
|
|
||||||
Disables Studio Mode.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
__Response__ : always OK. No additional fields.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "ToggleStudioMode"
|
|
||||||
Toggles Studio Mode on or off.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
__Response__ : always OK. No additional fields.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "StartStopStreaming"
|
|
||||||
Toggles streaming on or off.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
__Response__ : always OK. No additional fields.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "StartStopRecording"
|
|
||||||
Toggles recording on or off.
|
|
||||||
|
|
||||||
__Request fields__ :
|
|
||||||
- **"stream"** (object; optional) : See 'stream' parameter in 'StartStreaming'. Ignored if stream is already started.
|
|
||||||
|
|
||||||
__Response__ : always OK. No additional fields.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "StartStreaming"
|
|
||||||
Start streaming.
|
|
||||||
|
|
||||||
__Request fields__ :
|
|
||||||
- **"stream"** (object; optional) : If specified allows for special configuration of the stream
|
|
||||||
|
|
||||||
The 'stream' object has the following fields:
|
|
||||||
- **"settings"** (object; optional) : The settings for the stream
|
|
||||||
- **"type"** (string; optional) : If specified ensures the type of the stream matches the given type (usually 'rtmp\_custom' or 'rtmp\_common'). If the currently configured stream type does not match the given stream type, all settings must be specified in the 'settings' object or an error will occur starting the stream.
|
|
||||||
- **"metadata"** (object; optional) : Adds the given object parameters as encoded query string parameters to the 'key' of the RTMP stream. Used to pass data to the RTMP service about the stream.
|
|
||||||
|
|
||||||
The 'settings' object has the following fields:
|
|
||||||
- **"server"** (string; optional) : The publish URL
|
|
||||||
- **"key"** (string; optional) : The publish key of the stream
|
|
||||||
- **"use-auth"** (bool; optional) : should authentication be used when connecting to the streaming server
|
|
||||||
- **"username"** (string; optional) : if authentication is enabled, the username for access to the streaming server. Ignored if 'use-auth' is not specified as 'true'.
|
|
||||||
- **"password"** (string; optional) : if authentication is enabled, the password for access to the streaming server. Ignored if 'use-auth' is not specified as 'true'.
|
|
||||||
|
|
||||||
The 'metadata' object supports passing any string, numeric or boolean field.
|
|
||||||
|
|
||||||
__Response__ : Error if streaming is already active, OK otherwise. No additional fields.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "StopStreaming"
|
|
||||||
Stop streaming.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
__Response__ : Error if streaming is already inactive, OK otherwise. No additional fields.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "StartRecording"
|
|
||||||
Start recording.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
__Response__ : Error if recording is already active, OK otherwise. No additional fields.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "StopRecording"
|
|
||||||
Stop recording.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
__Response__ : Error if recording is already inactive, OK otherwise. No additional fields.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "SetRecordingFolder"
|
|
||||||
Change the current recording folder.
|
|
||||||
|
|
||||||
__Request fields__ :
|
|
||||||
- **"rec-folder"** (string) : path of the desired recording folder
|
|
||||||
|
|
||||||
__Response__ : OK if path is valid, error otherwise.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "GetRecordingFolder"
|
|
||||||
Get the path of the current recording folder.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
|
|
||||||
__Response__ : OK with these additional fields :
|
|
||||||
- **"rec-folder"** (string) : path of the current recording folder
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "GetStreamingStatus"
|
|
||||||
Get current streaming and recording status.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
__Response__ : always OK, with these additional fields :
|
|
||||||
- **"streaming"** (bool) : streaming status (active or not)
|
|
||||||
- **"recording"** (bool) : recording status (active or not)
|
|
||||||
- **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)
|
|
||||||
- **"preview-only"** (bool) : always false. Retrocompat with OBSRemote.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "GetTransitionList"
|
|
||||||
List all transitions available in the frontend's dropdown menu.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
__Response__ : always OK, with these additional fields :
|
|
||||||
- **"current-transition"** (string) : name of the current transition
|
|
||||||
- **"transitions"** (array of objects) : list of transition descriptions
|
|
||||||
|
|
||||||
Objects in the "transitions" array have only one field :
|
|
||||||
- **"name"** (string) : name of the transition
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "GetCurrentTransition"
|
|
||||||
Get the name of the currently selected transition in the frontend's dropdown menu.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
__Response__ : always OK, with these additional fields :
|
|
||||||
- **"name"** (string) : name of the selected transition
|
|
||||||
- **"duration"** (integer, only if transition supports this) : transition duration
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "SetCurrentTransition"
|
|
||||||
__Request fields__ :
|
|
||||||
- **"transition-name"** (string) : The name of the transition.
|
|
||||||
|
|
||||||
__Response__ : OK if specified transition exists, error otherwise.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "SetTransitionDuration"
|
|
||||||
Set the duration of the currently selected transition.
|
|
||||||
|
|
||||||
__Request fields__ :
|
|
||||||
- **"duration"** (integer) : desired transition duration in milliseconds
|
|
||||||
|
|
||||||
__Response__ : always OK.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "GetTransitionDuration"
|
|
||||||
Set the duration of the currently selected transition.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
__Response__ : always OK, with these additional fields :
|
|
||||||
- **"transition-duration"** (integer) : current transition duration, in milliseconds
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "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) : source name
|
|
||||||
- **"volume"** (double) : source volume, on a linear scale (0.0 to 1.0)
|
|
||||||
- **"muted"** (bool) : source mute status
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "GetMute"
|
|
||||||
Get mute status of a specific source.
|
|
||||||
|
|
||||||
__Request fields__ :
|
|
||||||
- **"source"** (string) : the name of the source
|
|
||||||
|
|
||||||
__Response__ : OK if source exists, with these additional fields :
|
|
||||||
- **"name"** (string) : source name
|
|
||||||
- **"muted"** (bool) : source mute status
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "GetSpecialSources"
|
|
||||||
Get configured special sources like Desktop Audio and Mic/Aux sources.
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
|
|
||||||
__Response__ : always OK, with these additional fields :
|
|
||||||
- **"desktop-1"** (string, optional) : Name of the first Desktop Audio capture source
|
|
||||||
- **"desktop-1"** (string, optional) : Name of the second Desktop Audio capture source
|
|
||||||
- **"mic-1"** (string, optional) : Name of the first Mic/Aux input source
|
|
||||||
- **"mic-2"** (string, optional) : Name of the second Mic/Aux input source
|
|
||||||
- **"mic-3"** (string, optional) : Name of the third Mic/Aux input source
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "SetSceneItemCrop"
|
|
||||||
__Request fields__ :
|
|
||||||
- **"item"** (string) : Name of the scene item
|
|
||||||
- **"scene-name"** (string, optional) : Scene the item belongs to. Default : current scene.
|
|
||||||
- **"top"** (integer)
|
|
||||||
- **"bottom"** (integer)
|
|
||||||
- **"left"** (integer)
|
|
||||||
- **"right"** (integer)
|
|
||||||
|
|
||||||
__Response__ : OK if specified item exists, error otherwise.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "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
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "GetStreamSettings"
|
|
||||||
Gets the current streaming server settings
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
|
|
||||||
__Response__ : OK with these additional fields :
|
|
||||||
- **"type"** (string) : The type of streaming service configuration usually 'rtmp\_custom' or 'rtmp\_common'
|
|
||||||
- **"settings"** (object) : The actual settings of the stream (i.e. server, key, use-auth, username, password)
|
|
||||||
|
|
||||||
The 'settings' object has the following fields however they may vary by 'type':
|
|
||||||
- **"server"** (string) : The publish URL
|
|
||||||
- **"key"** (string) : The publish key of the stream
|
|
||||||
- **"use-auth"** (bool) : should authentication be used when connecting to the streaming server
|
|
||||||
- **"username"** (string) : if authentication is enabled, the username for access to the streaming server
|
|
||||||
- **"password"** (string) : if authentication is enabled, the password for access to the streaming server
|
|
||||||
|
|
||||||
--
|
|
||||||
|
|
||||||
#### "SetStreamSettings"
|
|
||||||
Sets one or more attributes of the current streaming server settings. Any options not passed will remain unchanged. Returns the updated settings in response.
|
|
||||||
If 'type' is different than the current streaming service type, all settings are required.
|
|
||||||
Returns the full settings of the stream (i.e. the same as GetStreamSettings)
|
|
||||||
|
|
||||||
__Request fields__ :
|
|
||||||
- **"type"** (string) : The type of streaming service configuration usually 'rtmp\_custom' or 'rtmp\_common'
|
|
||||||
- **"settings"** (object) : The actual settings of the stream (i.e. server, key, use-auth, username, password)
|
|
||||||
- **"save"** (bool) : If specified as true, saves the settings to disk
|
|
||||||
|
|
||||||
The 'settings' object has the following fields however they may vary by 'type':
|
|
||||||
- **"server"** (string; optional) : The publish URL
|
|
||||||
- **"key"** (string; optional) : The publish key of the stream
|
|
||||||
- **"use-auth"** (bool; optional) : should authentication be used when connecting to the streaming server
|
|
||||||
- **"username"** (string; optional) : if authentication is enabled, the username for access to the streaming server
|
|
||||||
- **"password"** (string; optional) : if authentication is enabled, the password for access to the streaming server
|
|
||||||
|
|
||||||
__Response__ : OK with the same fields as the request (except 'save')
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "SaveStreamSettings"
|
|
||||||
Saves the current streaming server settings to disk
|
|
||||||
|
|
||||||
__Request fields__ : none
|
|
||||||
|
|
||||||
__Response__ : OK
|
|
||||||
|
|
||||||
|
|
||||||
#### "SetCurrentProfile"
|
|
||||||
Change the current profile.
|
|
||||||
|
|
||||||
__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
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "GetTextGDIPlusProperties"
|
|
||||||
Gets current properties for Text GDI Plus source.
|
|
||||||
|
|
||||||
__Request fields__ :
|
|
||||||
- **"source"** (string) : name of the source in the currently active scene.
|
|
||||||
- **"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 with these additional fields when fields are set, error otherwise.
|
|
||||||
- **"align"** (string) : "left","center","right" : text alignment
|
|
||||||
- **"bk_color"** (integer) : background color
|
|
||||||
- **"bk_opacity"** (integer) : background opacity range 0 to 100
|
|
||||||
- **"chatlog"** (bool) : chat log
|
|
||||||
- **"chatlog_lines"** (integer) : chat log lines
|
|
||||||
- **"color"** (integer) : text color
|
|
||||||
- **"extents"** (bool) : extents
|
|
||||||
- **"extents_wrap"** (bool) : extents wrap
|
|
||||||
- **"extents_cx"** (integer) : extents cx
|
|
||||||
- **"extents_cy"** (integer) : extents cy
|
|
||||||
- **"file"** (string) : file path name
|
|
||||||
- **"read_from_file"** (bool) : read text from file specified
|
|
||||||
- **"font"** (object) : holds font data for face, flags, size and style
|
|
||||||
-- Example: "font": {"face": "Arial","flags": 0,"size": 150,"style": ""}
|
|
||||||
- **"face"** (string) : font face i.e. Arial
|
|
||||||
- **"flags"** (integer) : font text style flag i.e. Bold 1, Italic 2, Bold Italic 3, Underline 5, Strikeout 8
|
|
||||||
- **"size"** (integer) : font text size
|
|
||||||
- **"style"** (string) : font style (unknown function)
|
|
||||||
- **"gradient"** (bool) : gradient
|
|
||||||
- **"gradient_color"** (integer) : gradient color
|
|
||||||
- **"gradient_dir"** (float) : gradient direction
|
|
||||||
- **"gradient_opacity"** (integer) : gradient opacity range 0 to 100
|
|
||||||
- **"outline"** (bool) : outline
|
|
||||||
- **"outline_color"** (integer) : outline color
|
|
||||||
- **"outline_size"** (integer) : outline size
|
|
||||||
- **"outline_opacity"** (integer) : outline opacity range 0 to 100
|
|
||||||
- **"text"** (string) : text to be displayed
|
|
||||||
- **"valign"** (string) : "top","center","bottom" : text vertical alignment
|
|
||||||
- **"vertical"** (bool) : vertical text
|
|
||||||
- **"render"** (bool) : visibility of the scene item
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "SetTextGDIPlusProperties"
|
|
||||||
Sets current properties for Text GDI Plus source.
|
|
||||||
|
|
||||||
__Request fields__ :
|
|
||||||
- **"source"** (string) : name of the source in the currently active scene.
|
|
||||||
- **"scene-name"** (string; optional) : name of the scene the source belongs to. defaults to current scene.
|
|
||||||
- **"align"** (string; optional) : "left","center","right" : text alignment
|
|
||||||
- **"bk_color"** (integer; optional) : background color
|
|
||||||
- **"bk_opacity"** (integer; optional) : background opacity range 0 to 100
|
|
||||||
- **"chatlog"** (bool; optional) : chat log
|
|
||||||
- **"chatlog_lines"** (integer; optional) : chat log lines
|
|
||||||
- **"color"** (integer; optional) : text color
|
|
||||||
- **"extents"** (bool; optional) : extents
|
|
||||||
- **"extents_wrap"** (bool; optional) : extents wrap
|
|
||||||
- **"extents_cx"** (integer; optional) : extents cx
|
|
||||||
- **"extents_cy"** (integer; optional) : extents cy
|
|
||||||
- **"file"** (string; optional) : file path name
|
|
||||||
- **"read_from_file"** (bool; optional) : read text from file specified
|
|
||||||
- **"font"** (object; optional) : holds font data for face, flags, size and style
|
|
||||||
-- Example: "font":{"face": "Arial","flags": 0,"size": 150,"style": ""}
|
|
||||||
- **"face"** (string; optional) : font face i.e. Arial
|
|
||||||
-- Example: "font":{"face": "Arial"}
|
|
||||||
- **"flags"** (integer; optional) : font text style flag i.e. Bold 1, Italic 2, Bold Italic 3, Underline 5, Strikeout 8
|
|
||||||
- **"size"** (integer; optional) : font text size
|
|
||||||
-- Example: "font": {"size":125}
|
|
||||||
- **"style"** (string; optional) : font style (unknown function)
|
|
||||||
- **"gradient"** (bool; optional) : gradient
|
|
||||||
- **"gradient_color"** (integer; optional) : gradient color
|
|
||||||
- **"gradient_dir"** (float; optional) : gradient direction
|
|
||||||
- **"gradient_opacity"** (integer; optional) : gradient opacity range 0 to 100
|
|
||||||
- **"outline"** (bool; optional) : outline
|
|
||||||
- **"outline_color"** (integer; optional) : outline color
|
|
||||||
- **"outline_size"** (integer; optional) : outline size
|
|
||||||
- **"outline_opacity"** (integer; optional) : outline opacity range 0 to 100
|
|
||||||
- **"text"** (string; optional) : text to be displayed
|
|
||||||
- **"valign"** (string; optional) : "top","center","bottom" : text vertical alignment
|
|
||||||
- **"vertical"** (bool; optional) : vertical text
|
|
||||||
- **"render"** (bool; optional) : visibility of the scene item
|
|
||||||
|
|
||||||
__Response__ : OK if source exists in the current scene, error otherwise.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "GetBrowserSourceProperties"
|
|
||||||
Gets current properties for Browser Source.
|
|
||||||
|
|
||||||
__Request fields__ :
|
|
||||||
- **"source"** (string) : name of the source in the currently active scene.
|
|
||||||
- **"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 with these additional fields when fields are set, error otherwise.
|
|
||||||
|
|
||||||
- **"is_local_file"** (bool) : use local file
|
|
||||||
- **"url"** (string) : url or file path
|
|
||||||
- **"css"** (string) : cascading style sheet code
|
|
||||||
- **"width"** (integer) : width
|
|
||||||
- **"height"** (integer) : height
|
|
||||||
- **"fps"** (integer) : frames per second
|
|
||||||
- **"shutdown"** (bool) : shutdown when sorce is not visible
|
|
||||||
- **"render"** (bool; optional) : visibility of the scene item
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### "SetBrowserSourceProperties"
|
|
||||||
Sets current properties for Browser Source.
|
|
||||||
|
|
||||||
__Request fields__ :
|
|
||||||
- **"source"** (string) : name of the source in the currently active scene.
|
|
||||||
- **"scene-name"** (string; optional) : name of the scene the source belongs to. defaults to current scene.
|
|
||||||
- **"is_local_file"** (bool; optional) : use local file
|
|
||||||
- **"url"** (string; optional) : url or file path
|
|
||||||
- **"css"** (string; optional) : cascading style sheet code
|
|
||||||
- **"width"** (integer; optional) : width
|
|
||||||
- **"height"** (integer; optional) : height
|
|
||||||
- **"fps"** (integer; optional) : frames per second
|
|
||||||
- **"shutdown"** (bool; optional) : shutdown when sorce is not visible
|
|
||||||
- **"render"** (bool; optional) : visibility of the scene item
|
|
||||||
|
|
||||||
---
|
|
19
README.md
19
README.md
@ -21,12 +21,13 @@ It is **highly recommended** to protect obs-websocket with a password against un
|
|||||||
|
|
||||||
### For developers
|
### 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 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).
|
The protocol understood by the server is documented in [PROTOCOL.md](docs/generated/protocol.md).
|
||||||
|
|
||||||
Here's a list of available language APIs for obs-websocket :
|
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 Brendan Hagan
|
- Javascript (browser & nodejs): [obs-websocket-js](https://github.com/haganbmj/obs-websocket-js) by Brendan Hagan
|
||||||
- C#/VB.NET : [obs-websocket-dotnet](https://github.com/Palakis/obs-websocket-dotnet)
|
- C#/VB.NET: [obs-websocket-dotnet](https://github.com/Palakis/obs-websocket-dotnet)
|
||||||
- Python : [obs-websocket-py](https://github.com/Elektordi/obs-websocket-py) by Guillaume Genty a.k.a Elektordi
|
- Python 2 and 3: [obs-websocket-py](https://github.com/Elektordi/obs-websocket-py) by Guillaume Genty a.k.a Elektordi
|
||||||
|
- Python 3.5+ with asyncio: [obs-ws-rc](https://github.com/KirillMysnik/obs-ws-rc) by Kirill Mysnik
|
||||||
|
|
||||||
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` !
|
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` !
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ See the [build instructions](BUILDING.md).
|
|||||||
|
|
||||||
## Special thanks
|
## Special thanks
|
||||||
In order of appearance:
|
In order of appearance:
|
||||||
- [Brendan H.](https://github.com/haganbmj) : Code contributions and better English in the Protocol specification
|
- [Brendan H.](https://github.com/haganbmj) : Code contributions and gooder English in the Protocol specification
|
||||||
- [Mikhail Swift](https://github.com/mikhailswift) : Code contributions
|
- [Mikhail Swift](https://github.com/mikhailswift) : Code contributions
|
||||||
- [Tobias Frahmer](https://github.com/Frahmer) : German translation
|
- [Tobias Frahmer](https://github.com/Frahmer) : German translation
|
||||||
- [Genture](https://github.com/Genteure) : Simplified Chinese and Traditional Chinese translations
|
- [Genture](https://github.com/Genteure) : Simplified Chinese and Traditional Chinese translations
|
||||||
@ -46,6 +47,10 @@ In order of appearance:
|
|||||||
- [Andy Asquelt](https://github.com/asquelt) : Polish translation
|
- [Andy Asquelt](https://github.com/asquelt) : Polish translation
|
||||||
- [Marcel Haazen](https://github.com/inpothet) : Dutch translation
|
- [Marcel Haazen](https://github.com/inpothet) : Dutch translation
|
||||||
- [Peter Antonvich](https://github.com/pantonvich) : Code contributions
|
- [Peter Antonvich](https://github.com/pantonvich) : Code contributions
|
||||||
|
- [yinzara](https://github.com/yinzara) : Code contributions
|
||||||
|
- [Chris Angelico](https://github.com/Rosuav) : Code contributions
|
||||||
|
- [Guillaume "Elektordi" Genty](https://github.com/Elektordi) : Code contributions
|
||||||
|
- [Marwin M](https://github.com/dragonbane0) : Code contributions
|
||||||
|
|
||||||
And also: special thanks to supporters of the project!
|
And also: special thanks to supporters of the project!
|
||||||
|
|
||||||
@ -56,10 +61,10 @@ They have contributed financially to the project and made possible the addition
|
|||||||
|
|
||||||
[Support Class](http://supportclass.net) designs and develops professional livestreams, with services ranging from broadcast graphics design and integration to event organization, along many other skills.
|
[Support Class](http://supportclass.net) designs and develops professional livestreams, with services ranging from broadcast graphics design and integration to event organization, along many other skills.
|
||||||
|
|
||||||
[](http://supportclass.net)
|
[](http://supportclass.net)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
[MediaUnit](http://www.mediaunit.no) is a Norwegian media company developing products and services for the media industry, primarly focused on web and events.
|
[MediaUnit](http://www.mediaunit.no) is a Norwegian media company developing products and services for the media industry, primarly focused on web and events.
|
||||||
|
|
||||||
[](http://www.mediaunit.no/)
|
[](http://www.mediaunit.no/)
|
||||||
|
251
Utils.cpp
251
Utils.cpp
@ -16,26 +16,25 @@ 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 <obs-frontend-api.h>
|
|
||||||
#include <obs.hpp>
|
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include "Utils.h"
|
#include <obs-frontend-api.h>
|
||||||
|
#include <obs.hpp>
|
||||||
#include "obs-websocket.h"
|
#include "obs-websocket.h"
|
||||||
|
|
||||||
|
#include "Utils.h"
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(OBSScene);
|
Q_DECLARE_METATYPE(OBSScene);
|
||||||
|
|
||||||
obs_data_array_t* string_list_to_array(char** strings, char* key)
|
obs_data_array_t* string_list_to_array(char** strings, char* key) {
|
||||||
{
|
|
||||||
if (!strings)
|
if (!strings)
|
||||||
return obs_data_array_create();
|
return obs_data_array_create();
|
||||||
|
|
||||||
obs_data_array_t* list = obs_data_array_create();
|
obs_data_array_t* list = obs_data_array_create();
|
||||||
|
|
||||||
char* value = "";
|
char* value = "";
|
||||||
for (int i = 0; value != nullptr; i++)
|
for (int i = 0; value != nullptr; i++) {
|
||||||
{
|
|
||||||
value = strings[i];
|
value = strings[i];
|
||||||
|
|
||||||
obs_data_t* item = obs_data_create();
|
obs_data_t* item = obs_data_create();
|
||||||
@ -50,17 +49,19 @@ obs_data_array_t* string_list_to_array(char** strings, char* key)
|
|||||||
return list;
|
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();
|
||||||
obs_scene_t* scene = obs_scene_from_source(source);
|
obs_scene_t* scene = obs_scene_from_source(source);
|
||||||
|
|
||||||
if (!scene)
|
if (!scene)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
obs_scene_enum_items(scene, [](obs_scene_t* scene, obs_sceneitem_t* currentItem, void* param)
|
obs_scene_enum_items(scene, [](
|
||||||
|
obs_scene_t* scene,
|
||||||
|
obs_sceneitem_t* currentItem,
|
||||||
|
void* param)
|
||||||
{
|
{
|
||||||
obs_data_array_t* data = static_cast<obs_data_array_t* >(param);
|
obs_data_array_t* data = static_cast<obs_data_array_t*>(param);
|
||||||
|
|
||||||
obs_data_t* item_data = GetSceneItemData(currentItem);
|
obs_data_t* item_data = GetSceneItemData(currentItem);
|
||||||
obs_data_array_insert(data, 0, item_data);
|
obs_data_array_insert(data, 0, item_data);
|
||||||
@ -72,8 +73,7 @@ obs_data_array_t* Utils::GetSceneItems(obs_source_t* source)
|
|||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item)
|
obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) {
|
||||||
{
|
|
||||||
if (!item)
|
if (!item)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
@ -105,8 +105,7 @@ obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item)
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, const char* name)
|
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, const char* name) {
|
||||||
{
|
|
||||||
struct current_search {
|
struct current_search {
|
||||||
const char* query;
|
const char* query;
|
||||||
obs_sceneitem_t* result;
|
obs_sceneitem_t* result;
|
||||||
@ -120,15 +119,17 @@ obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, const char* n
|
|||||||
if (scene == nullptr)
|
if (scene == nullptr)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
obs_scene_enum_items(scene, [](obs_scene_t* scene, obs_sceneitem_t* currentItem, void* param)
|
obs_scene_enum_items(scene, [](
|
||||||
|
obs_scene_t* scene,
|
||||||
|
obs_sceneitem_t* currentItem,
|
||||||
|
void* param)
|
||||||
{
|
{
|
||||||
current_search* search = static_cast<current_search* >(param);
|
current_search* search = static_cast<current_search*>(param);
|
||||||
|
|
||||||
const char* currentItemName =
|
const char* currentItemName =
|
||||||
obs_source_get_name(obs_sceneitem_get_source(currentItem));
|
obs_source_get_name(obs_sceneitem_get_source(currentItem));
|
||||||
|
|
||||||
if (strcmp(currentItemName, search->query) == 0)
|
if (strcmp(currentItemName, search->query) == 0) {
|
||||||
{
|
|
||||||
search->result = currentItem;
|
search->result = currentItem;
|
||||||
obs_sceneitem_addref(search->result);
|
obs_sceneitem_addref(search->result);
|
||||||
return false;
|
return false;
|
||||||
@ -140,15 +141,13 @@ obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, const char* n
|
|||||||
return search.result;
|
return search.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_source_t* Utils::GetTransitionFromName(const char* search_name)
|
obs_source_t* Utils::GetTransitionFromName(const char* search_name) {
|
||||||
{
|
|
||||||
obs_source_t* found_transition = NULL;
|
obs_source_t* found_transition = NULL;
|
||||||
|
|
||||||
obs_frontend_source_list transition_list = {};
|
obs_frontend_source_list transition_list = {};
|
||||||
obs_frontend_get_transitions(&transition_list);
|
obs_frontend_get_transitions(&transition_list);
|
||||||
|
|
||||||
for (size_t i = 0; i < transition_list.sources.num; i++)
|
for (size_t i = 0; i < transition_list.sources.num; i++) {
|
||||||
{
|
|
||||||
obs_source_t* transition = transition_list.sources.array[i];
|
obs_source_t* transition = transition_list.sources.array[i];
|
||||||
|
|
||||||
const char* transition_name = obs_source_get_name(transition);
|
const char* transition_name = obs_source_get_name(transition);
|
||||||
@ -165,8 +164,7 @@ 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* Utils::GetSceneFromNameOrCurrent(const char* scene_name) {
|
||||||
{
|
|
||||||
obs_source_t* scene = nullptr;
|
obs_source_t* scene = nullptr;
|
||||||
|
|
||||||
if (!scene_name || !strlen(scene_name))
|
if (!scene_name || !strlen(scene_name))
|
||||||
@ -177,14 +175,12 @@ obs_source_t* Utils::GetSceneFromNameOrCurrent(const char* scene_name)
|
|||||||
return scene;
|
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);
|
||||||
|
|
||||||
obs_data_array_t* scenes = obs_data_array_create();
|
obs_data_array_t* scenes = obs_data_array_create();
|
||||||
for (size_t i = 0; i < sceneList.sources.num; i++)
|
for (size_t i = 0; i < sceneList.sources.num; i++) {
|
||||||
{
|
|
||||||
obs_source_t* scene = sceneList.sources.array[i];
|
obs_source_t* scene = sceneList.sources.array[i];
|
||||||
|
|
||||||
obs_data_t* scene_data = GetSceneData(scene);
|
obs_data_t* scene_data = GetSceneData(scene);
|
||||||
@ -198,8 +194,7 @@ obs_data_array_t* Utils::GetScenes()
|
|||||||
return scenes;
|
return scenes;
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_data_t* Utils::GetSceneData(obs_source* source)
|
obs_data_t* Utils::GetSceneData(obs_source* source) {
|
||||||
{
|
|
||||||
obs_data_array_t* scene_items = GetSceneItems(source);
|
obs_data_array_t* scene_items = GetSceneItems(source);
|
||||||
|
|
||||||
obs_data_t* sceneData = obs_data_create();
|
obs_data_t* sceneData = obs_data_create();
|
||||||
@ -210,8 +205,7 @@ obs_data_t* Utils::GetSceneData(obs_source* source)
|
|||||||
return sceneData;
|
return sceneData;
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_data_array_t* Utils::GetSceneCollections()
|
obs_data_array_t* Utils::GetSceneCollections() {
|
||||||
{
|
|
||||||
char** scene_collections = obs_frontend_get_scene_collections();
|
char** scene_collections = obs_frontend_get_scene_collections();
|
||||||
obs_data_array_t* list = string_list_to_array(scene_collections, "sc-name");
|
obs_data_array_t* list = string_list_to_array(scene_collections, "sc-name");
|
||||||
|
|
||||||
@ -219,8 +213,7 @@ obs_data_array_t* Utils::GetSceneCollections()
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_data_array_t* Utils::GetProfiles()
|
obs_data_array_t* Utils::GetProfiles() {
|
||||||
{
|
|
||||||
char** profiles = obs_frontend_get_profiles();
|
char** profiles = obs_frontend_get_profiles();
|
||||||
obs_data_array_t* list = string_list_to_array(profiles, "profile-name");
|
obs_data_array_t* list = string_list_to_array(profiles, "profile-name");
|
||||||
|
|
||||||
@ -228,14 +221,12 @@ obs_data_array_t* Utils::GetProfiles()
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSpinBox* Utils::GetTransitionDurationControl()
|
QSpinBox* Utils::GetTransitionDurationControl() {
|
||||||
{
|
|
||||||
QMainWindow* window = (QMainWindow*)obs_frontend_get_main_window();
|
QMainWindow* window = (QMainWindow*)obs_frontend_get_main_window();
|
||||||
return window->findChild<QSpinBox*>("transitionDuration");
|
return window->findChild<QSpinBox*>("transitionDuration");
|
||||||
}
|
}
|
||||||
|
|
||||||
int Utils::GetTransitionDuration()
|
int Utils::GetTransitionDuration() {
|
||||||
{
|
|
||||||
QSpinBox* control = GetTransitionDurationControl();
|
QSpinBox* control = GetTransitionDurationControl();
|
||||||
if (control)
|
if (control)
|
||||||
return control->value();
|
return control->value();
|
||||||
@ -243,45 +234,35 @@ int Utils::GetTransitionDuration()
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Utils::SetTransitionDuration(int ms)
|
void Utils::SetTransitionDuration(int ms) {
|
||||||
{
|
|
||||||
QSpinBox* control = GetTransitionDurationControl();
|
QSpinBox* control = GetTransitionDurationControl();
|
||||||
|
|
||||||
if (control && ms >= 0)
|
if (control && ms >= 0)
|
||||||
control->setValue(ms);
|
control->setValue(ms);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Utils::SetTransitionByName(const char* transition_name)
|
bool Utils::SetTransitionByName(const char* transition_name) {
|
||||||
{
|
|
||||||
obs_source_t* transition = GetTransitionFromName(transition_name);
|
obs_source_t* transition = GetTransitionFromName(transition_name);
|
||||||
|
|
||||||
if (transition)
|
if (transition) {
|
||||||
{
|
|
||||||
obs_frontend_set_current_transition(transition);
|
obs_frontend_set_current_transition(transition);
|
||||||
obs_source_release(transition);
|
obs_source_release(transition);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QPushButton* Utils::GetPreviewModeButtonControl()
|
QPushButton* Utils::GetPreviewModeButtonControl() {
|
||||||
{
|
|
||||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||||
return main->findChild<QPushButton*>("modeSwitch");
|
return main->findChild<QPushButton*>("modeSwitch");
|
||||||
}
|
}
|
||||||
|
|
||||||
QListWidget* Utils::GetSceneListControl()
|
QListWidget* Utils::GetSceneListControl() {
|
||||||
{
|
|
||||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||||
return main->findChild<QListWidget*>("scenes");
|
return main->findChild<QListWidget*>("scenes");
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_scene_t* Utils::SceneListItemToScene(QListWidgetItem* item)
|
obs_scene_t* Utils::SceneListItemToScene(QListWidgetItem* item) {
|
||||||
{
|
|
||||||
if (!item)
|
if (!item)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
@ -289,14 +270,12 @@ obs_scene_t* Utils::SceneListItemToScene(QListWidgetItem* item)
|
|||||||
return item_data.value<OBSScene>();
|
return item_data.value<OBSScene>();
|
||||||
}
|
}
|
||||||
|
|
||||||
QLayout* Utils::GetPreviewLayout()
|
QLayout* Utils::GetPreviewLayout() {
|
||||||
{
|
|
||||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||||
return main->findChild<QLayout*>("previewLayout");
|
return main->findChild<QLayout*>("previewLayout");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Utils::IsPreviewModeActive()
|
bool Utils::IsPreviewModeActive() {
|
||||||
{
|
|
||||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||||
|
|
||||||
// Clue 1 : "Studio Mode" button is toggled on
|
// Clue 1 : "Studio Mode" button is toggled on
|
||||||
@ -309,29 +288,23 @@ bool Utils::IsPreviewModeActive()
|
|||||||
return buttonToggledOn || (previewChildCount >= 2);
|
return buttonToggledOn || (previewChildCount >= 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Utils::EnablePreviewMode()
|
void Utils::EnablePreviewMode() {
|
||||||
{
|
|
||||||
if (!IsPreviewModeActive())
|
if (!IsPreviewModeActive())
|
||||||
GetPreviewModeButtonControl()->click();
|
GetPreviewModeButtonControl()->click();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Utils::DisablePreviewMode()
|
void Utils::DisablePreviewMode() {
|
||||||
{
|
|
||||||
if (IsPreviewModeActive())
|
if (IsPreviewModeActive())
|
||||||
GetPreviewModeButtonControl()->click();
|
GetPreviewModeButtonControl()->click();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Utils::TogglePreviewMode()
|
void Utils::TogglePreviewMode() {
|
||||||
{
|
|
||||||
GetPreviewModeButtonControl()->click();
|
GetPreviewModeButtonControl()->click();
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_scene_t* Utils::GetPreviewScene()
|
obs_scene_t* Utils::GetPreviewScene() {
|
||||||
{
|
if (IsPreviewModeActive()) {
|
||||||
if (IsPreviewModeActive())
|
|
||||||
{
|
|
||||||
QListWidget* sceneList = GetSceneListControl();
|
QListWidget* sceneList = GetSceneListControl();
|
||||||
|
|
||||||
QList<QListWidgetItem*> selected = sceneList->selectedItems();
|
QList<QListWidgetItem*> selected = sceneList->selectedItems();
|
||||||
|
|
||||||
// Qt::UserRole == QtUserRole::OBSRef
|
// Qt::UserRole == QtUserRole::OBSRef
|
||||||
@ -344,21 +317,16 @@ obs_scene_t* Utils::GetPreviewScene()
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Utils::SetPreviewScene(const char* name)
|
bool Utils::SetPreviewScene(const char* name) {
|
||||||
{
|
if (IsPreviewModeActive()) {
|
||||||
if (IsPreviewModeActive())
|
|
||||||
{
|
|
||||||
QListWidget* sceneList = GetSceneListControl();
|
QListWidget* sceneList = GetSceneListControl();
|
||||||
QList<QListWidgetItem*> matchingItems =
|
QList<QListWidgetItem*> matchingItems =
|
||||||
sceneList->findItems(name, Qt::MatchExactly);
|
sceneList->findItems(name, Qt::MatchExactly);
|
||||||
|
|
||||||
if (matchingItems.count() > 0)
|
if (matchingItems.count() > 0) {
|
||||||
{
|
|
||||||
sceneList->setCurrentItem(matchingItems.first());
|
sceneList->setCurrentItem(matchingItems.first());
|
||||||
return true;
|
return true;
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -366,8 +334,7 @@ bool Utils::SetPreviewScene(const char* name)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Utils::TransitionToProgram()
|
void Utils::TransitionToProgram() {
|
||||||
{
|
|
||||||
if (!IsPreviewModeActive())
|
if (!IsPreviewModeActive())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -405,14 +372,13 @@ const char* Utils::OBSVersionString() {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSystemTrayIcon* Utils::GetTrayIcon()
|
QSystemTrayIcon* Utils::GetTrayIcon() {
|
||||||
{
|
|
||||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||||
return main->findChildren<QSystemTrayIcon*>().first();
|
return main->findChildren<QSystemTrayIcon*>().first();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Utils::SysTrayNotify(QString &text, QSystemTrayIcon::MessageIcon icon, QString title)
|
void Utils::SysTrayNotify(QString &text,
|
||||||
{
|
QSystemTrayIcon::MessageIcon icon, QString title) {
|
||||||
if (!QSystemTrayIcon::supportsMessages())
|
if (!QSystemTrayIcon::supportsMessages())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -421,45 +387,38 @@ void Utils::SysTrayNotify(QString &text, QSystemTrayIcon::MessageIcon icon, QStr
|
|||||||
trayIcon->showMessage(title, text, icon);
|
trayIcon->showMessage(title, text, icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Utils::FormatIPAddress(QHostAddress &addr)
|
QString Utils::FormatIPAddress(QHostAddress &addr) {
|
||||||
{
|
QRegExp v4regex("(::ffff:)(((\\d).){3})", Qt::CaseInsensitive);
|
||||||
if (addr.protocol() == QAbstractSocket::IPv4Protocol)
|
QString addrString = addr.toString();
|
||||||
QString v4addr = addr.toString().replace("::fff:", "");
|
if (addrString.contains(v4regex)) {
|
||||||
|
addrString = QHostAddress(addr.toIPv4Address()).toString();
|
||||||
return addr.toString();
|
}
|
||||||
|
return addrString;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* Utils::GetRecordingFolder()
|
const char* Utils::GetRecordingFolder() {
|
||||||
{
|
|
||||||
config_t* profile = obs_frontend_get_profile_config();
|
config_t* profile = obs_frontend_get_profile_config();
|
||||||
const char* outputMode = config_get_string(profile, "Output", "Mode");
|
const char* outputMode = config_get_string(profile, "Output", "Mode");
|
||||||
|
|
||||||
if (strcmp(outputMode, "Advanced") == 0)
|
if (strcmp(outputMode, "Advanced") == 0) {
|
||||||
{
|
|
||||||
// Advanced mode
|
// Advanced mode
|
||||||
return config_get_string(profile, "AdvOut", "RecFilePath");
|
return config_get_string(profile, "AdvOut", "RecFilePath");
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
// Simple mode
|
// Simple mode
|
||||||
return config_get_string(profile, "SimpleOutput", "FilePath");
|
return config_get_string(profile, "SimpleOutput", "FilePath");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Utils::SetRecordingFolder(const char* path)
|
bool Utils::SetRecordingFolder(const char* path) {
|
||||||
{
|
|
||||||
if (!QDir(path).exists())
|
if (!QDir(path).exists())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
config_t* profile = obs_frontend_get_profile_config();
|
config_t* profile = obs_frontend_get_profile_config();
|
||||||
const char* outputMode = config_get_string(profile, "Output", "Mode");
|
const char* outputMode = config_get_string(profile, "Output", "Mode");
|
||||||
|
|
||||||
if (strcmp(outputMode, "Advanced") == 0)
|
if (strcmp(outputMode, "Advanced") == 0) {
|
||||||
{
|
|
||||||
config_set_string(profile, "AdvOut", "RecFilePath", path);
|
config_set_string(profile, "AdvOut", "RecFilePath", path);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
config_set_string(profile, "SimpleOutput", "FilePath", path);
|
config_set_string(profile, "SimpleOutput", "FilePath", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -467,18 +426,14 @@ bool Utils::SetRecordingFolder(const char* path)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString* Utils::ParseDataToQueryString(obs_data_t * data)
|
QString* Utils::ParseDataToQueryString(obs_data_t* data) {
|
||||||
{
|
|
||||||
QString* query = nullptr;
|
QString* query = nullptr;
|
||||||
if (data)
|
if (data) {
|
||||||
{
|
|
||||||
obs_data_item_t* item = obs_data_first(data);
|
obs_data_item_t* item = obs_data_first(data);
|
||||||
if (item)
|
if (item) {
|
||||||
{
|
|
||||||
query = new QString();
|
query = new QString();
|
||||||
bool isFirst = true;
|
bool isFirst = true;
|
||||||
do
|
do {
|
||||||
{
|
|
||||||
if (!obs_data_item_has_user_value(item))
|
if (!obs_data_item_has_user_value(item))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -489,8 +444,8 @@ QString* Utils::ParseDataToQueryString(obs_data_t * data)
|
|||||||
|
|
||||||
const char* attrName = obs_data_item_get_name(item);
|
const char* attrName = obs_data_item_get_name(item);
|
||||||
query->append(attrName).append("=");
|
query->append(attrName).append("=");
|
||||||
switch (obs_data_item_gettype(item))
|
|
||||||
{
|
switch (obs_data_item_gettype(item)) {
|
||||||
case OBS_DATA_BOOLEAN:
|
case OBS_DATA_BOOLEAN:
|
||||||
query->append(obs_data_item_get_bool(item)?"true":"false");
|
query->append(obs_data_item_get_bool(item)?"true":"false");
|
||||||
break;
|
break;
|
||||||
@ -498,25 +453,79 @@ QString* Utils::ParseDataToQueryString(obs_data_t * data)
|
|||||||
switch (obs_data_item_numtype(item))
|
switch (obs_data_item_numtype(item))
|
||||||
{
|
{
|
||||||
case OBS_DATA_NUM_DOUBLE:
|
case OBS_DATA_NUM_DOUBLE:
|
||||||
query->append(QString::number(obs_data_item_get_double(item)));
|
query->append(
|
||||||
|
QString::number(obs_data_item_get_double(item)));
|
||||||
break;
|
break;
|
||||||
case OBS_DATA_NUM_INT:
|
case OBS_DATA_NUM_INT:
|
||||||
query->append(QString::number(obs_data_item_get_int(item)));
|
query->append(
|
||||||
|
QString::number(obs_data_item_get_int(item)));
|
||||||
break;
|
break;
|
||||||
case OBS_DATA_NUM_INVALID:
|
case OBS_DATA_NUM_INVALID:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case OBS_DATA_STRING:
|
case OBS_DATA_STRING:
|
||||||
query->append(QUrl::toPercentEncoding(QString(obs_data_item_get_string(item))));
|
query->append(QUrl::toPercentEncoding(
|
||||||
|
QString(obs_data_item_get_string(item))));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
//other types are not supported
|
//other types are not supported
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} while ( obs_data_item_next( &item ) );
|
} while (obs_data_item_next(&item));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obs_hotkey_t* Utils::FindHotkeyByName(const char* name) {
|
||||||
|
struct current_search {
|
||||||
|
const char* query;
|
||||||
|
obs_hotkey_t* result;
|
||||||
|
};
|
||||||
|
|
||||||
|
current_search search;
|
||||||
|
search.query = name;
|
||||||
|
search.result = nullptr;
|
||||||
|
|
||||||
|
obs_enum_hotkeys([](void* data, obs_hotkey_id id, obs_hotkey_t* hotkey) {
|
||||||
|
current_search* search = static_cast<current_search*>(data);
|
||||||
|
|
||||||
|
const char* hk_name = obs_hotkey_get_name(hotkey);
|
||||||
|
if (strcmp(hk_name, search->query) == 0) {
|
||||||
|
search->result = hotkey;
|
||||||
|
blog(LOG_INFO, "Utils::FindHotkeyByName: found %s", hk_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}, &search);
|
||||||
|
|
||||||
|
return search.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Utils::ReplayBufferEnabled() {
|
||||||
|
config_t* profile = obs_frontend_get_profile_config();
|
||||||
|
const char* outputMode = config_get_string(profile, "Output", "Mode");
|
||||||
|
|
||||||
|
if (strcmp(outputMode, "Simple") == 0) {
|
||||||
|
return config_get_bool(profile, "SimpleOutput", "RecRB");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Utils::RPHotkeySet() {
|
||||||
|
obs_output_t* rp_output = obs_frontend_get_replay_buffer_output();
|
||||||
|
|
||||||
|
obs_data_t *hotkeys = obs_hotkeys_save_output(rp_output);
|
||||||
|
obs_data_array_t *bindings = obs_data_get_array(hotkeys,
|
||||||
|
"ReplayBuffer.Save");
|
||||||
|
|
||||||
|
size_t count = obs_data_array_count(bindings);
|
||||||
|
|
||||||
|
obs_data_array_release(bindings);
|
||||||
|
obs_data_release(hotkeys);
|
||||||
|
obs_output_release(rp_output);
|
||||||
|
|
||||||
|
return (count > 0);
|
||||||
|
}
|
||||||
|
7
Utils.h
7
Utils.h
@ -25,12 +25,12 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
|||||||
#include <QListWidget>
|
#include <QListWidget>
|
||||||
#include <QSystemTrayIcon>
|
#include <QSystemTrayIcon>
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <obs-module.h>
|
#include <obs-module.h>
|
||||||
#include <util/config-file.h>
|
#include <util/config-file.h>
|
||||||
|
|
||||||
class Utils
|
class Utils {
|
||||||
{
|
|
||||||
public:
|
public:
|
||||||
static obs_data_array_t* GetSceneItems(obs_source_t* source);
|
static obs_data_array_t* GetSceneItems(obs_source_t* source);
|
||||||
static obs_data_t* GetSceneItemData(obs_scene_item* item);
|
static obs_data_t* GetSceneItemData(obs_scene_item* item);
|
||||||
@ -78,6 +78,9 @@ class Utils
|
|||||||
static bool SetRecordingFolder(const char* path);
|
static bool SetRecordingFolder(const char* path);
|
||||||
|
|
||||||
static QString* ParseDataToQueryString(obs_data_t * data);
|
static QString* ParseDataToQueryString(obs_data_t * data);
|
||||||
|
static obs_hotkey_t* FindHotkeyByName(const char* name);
|
||||||
|
static bool ReplayBufferEnabled();
|
||||||
|
static bool RPHotkeySet();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // UTILS_H
|
#endif // UTILS_H
|
||||||
|
586
WSEvents.cpp
586
WSEvents.cpp
@ -1,46 +1,45 @@
|
|||||||
/*
|
/**
|
||||||
obs-websocket
|
* obs-websocket
|
||||||
Copyright (C) 2016-2017 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>
|
* 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
|
||||||
the Free Software Foundation; either version 2 of the License, or
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
(at your option) any later version.
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
This program is distributed in the hope that it will be useful,
|
* This program is distributed in the hope that it will be useful,
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
GNU General Public License for more details.
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
You should have received a copy of the GNU General Public License along
|
* 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 <util/platform.h>
|
||||||
|
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "WSEvents.h"
|
#include "WSEvents.h"
|
||||||
|
|
||||||
#include "obs-websocket.h"
|
#include "obs-websocket.h"
|
||||||
|
|
||||||
bool transition_is_cut(obs_source_t* transition)
|
bool transition_is_cut(obs_source_t* transition) {
|
||||||
{
|
|
||||||
if (!transition)
|
if (!transition)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (obs_source_get_type(transition) == OBS_SOURCE_TYPE_TRANSITION
|
if (obs_source_get_type(transition) == OBS_SOURCE_TYPE_TRANSITION
|
||||||
&& strcmp(obs_source_get_id(transition), "cut_transition") == 0)
|
&& strcmp(obs_source_get_id(transition), "cut_transition") == 0) {
|
||||||
{
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* ns_to_timestamp(uint64_t ns)
|
const char* ns_to_timestamp(uint64_t ns) {
|
||||||
{
|
|
||||||
uint64_t ms = ns / (1000 * 1000);
|
uint64_t ms = ns / (1000 * 1000);
|
||||||
uint64_t secs = ms / 1000;
|
uint64_t secs = ms / 1000;
|
||||||
uint64_t minutes = secs / 60;
|
uint64_t minutes = secs / 60;
|
||||||
@ -59,8 +58,7 @@ const char* ns_to_timestamp(uint64_t ns)
|
|||||||
|
|
||||||
WSEvents* WSEvents::Instance = nullptr;
|
WSEvents* WSEvents::Instance = nullptr;
|
||||||
|
|
||||||
WSEvents::WSEvents(WSServer* srv)
|
WSEvents::WSEvents(WSServer* srv) {
|
||||||
{
|
|
||||||
_srv = srv;
|
_srv = srv;
|
||||||
obs_frontend_add_event_callback(WSEvents::FrontendEventHandler, this);
|
obs_frontend_add_event_callback(WSEvents::FrontendEventHandler, this);
|
||||||
|
|
||||||
@ -92,13 +90,11 @@ WSEvents::WSEvents(WSServer* srv)
|
|||||||
_rec_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);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::deferredInitOperations()
|
void WSEvents::deferredInitOperations() {
|
||||||
{
|
|
||||||
obs_source_t* transition = obs_frontend_get_current_transition();
|
obs_source_t* transition = obs_frontend_get_current_transition();
|
||||||
connectTransitionSignals(transition);
|
connectTransitionSignals(transition);
|
||||||
obs_source_release(transition);
|
obs_source_release(transition);
|
||||||
@ -108,8 +104,7 @@ void WSEvents::deferredInitOperations()
|
|||||||
obs_source_release(scene);
|
obs_source_release(scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private_data)
|
void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private_data) {
|
||||||
{
|
|
||||||
WSEvents* owner = static_cast<WSEvents*>(private_data);
|
WSEvents* owner = static_cast<WSEvents*>(private_data);
|
||||||
|
|
||||||
if (!owner->_srv)
|
if (!owner->_srv)
|
||||||
@ -117,95 +112,88 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private
|
|||||||
|
|
||||||
// TODO : implement SourceOrderChanged and RepopulateSources
|
// TODO : implement SourceOrderChanged and RepopulateSources
|
||||||
|
|
||||||
if (event == OBS_FRONTEND_EVENT_SCENE_CHANGED)
|
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_SCENE_COLLECTION_CHANGED)
|
else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED) {
|
||||||
{
|
|
||||||
owner->OnSceneCollectionChange();
|
owner->OnSceneCollectionChange();
|
||||||
}
|
}
|
||||||
else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED)
|
else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED) {
|
||||||
{
|
|
||||||
owner->OnSceneCollectionListChange();
|
owner->OnSceneCollectionListChange();
|
||||||
}
|
}
|
||||||
else if (event == OBS_FRONTEND_EVENT_TRANSITION_CHANGED)
|
else if (event == OBS_FRONTEND_EVENT_TRANSITION_CHANGED) {
|
||||||
{
|
|
||||||
owner->OnTransitionChange();
|
owner->OnTransitionChange();
|
||||||
}
|
}
|
||||||
else if (event == OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED)
|
else if (event == OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED) {
|
||||||
{
|
|
||||||
owner->OnTransitionListChange();
|
owner->OnTransitionListChange();
|
||||||
}
|
}
|
||||||
else if (event == OBS_FRONTEND_EVENT_PROFILE_CHANGED)
|
else if (event == OBS_FRONTEND_EVENT_PROFILE_CHANGED) {
|
||||||
{
|
|
||||||
owner->OnProfileChange();
|
owner->OnProfileChange();
|
||||||
}
|
}
|
||||||
else if (event == OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED)
|
else if (event == OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED) {
|
||||||
{
|
|
||||||
owner->OnProfileListChange();
|
owner->OnProfileListChange();
|
||||||
}
|
}
|
||||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTING)
|
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->_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->_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->_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->_recording_active = false;
|
||||||
owner->OnRecordingStopped();
|
owner->OnRecordingStopped();
|
||||||
}
|
}
|
||||||
else if (event == OBS_FRONTEND_EVENT_EXIT)
|
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING) {
|
||||||
{
|
owner->OnReplayStarting();
|
||||||
|
}
|
||||||
|
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED) {
|
||||||
|
owner->OnReplayStarted();
|
||||||
|
}
|
||||||
|
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING) {
|
||||||
|
owner->OnReplayStopping();
|
||||||
|
}
|
||||||
|
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED) {
|
||||||
|
owner->OnReplayStopped();
|
||||||
|
}
|
||||||
|
else if (event == OBS_FRONTEND_EVENT_EXIT) {
|
||||||
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_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;
|
const char* ts = nullptr;
|
||||||
if (_streaming_active)
|
if (_streaming_active) {
|
||||||
{
|
|
||||||
ts = ns_to_timestamp(os_gettime_ns() - _stream_starttime);
|
ts = ns_to_timestamp(os_gettime_ns() - _stream_starttime);
|
||||||
obs_data_set_string(update, "stream-timecode", ts);
|
obs_data_set_string(update, "stream-timecode", ts);
|
||||||
bfree((void*)ts);
|
bfree((void*)ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_recording_active)
|
if (_recording_active) {
|
||||||
{
|
|
||||||
ts = ns_to_timestamp(os_gettime_ns() - _rec_starttime);
|
ts = ns_to_timestamp(os_gettime_ns() - _rec_starttime);
|
||||||
obs_data_set_string(update, "rec-timecode", ts);
|
obs_data_set_string(update, "rec-timecode", ts);
|
||||||
bfree((void*)ts);
|
bfree((void*)ts);
|
||||||
@ -222,30 +210,23 @@ void WSEvents::broadcastUpdate(const char* updateType, obs_data_t* additionalFie
|
|||||||
obs_data_release(update);
|
obs_data_release(update);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::connectTransitionSignals(obs_source_t* transition)
|
void WSEvents::connectTransitionSignals(obs_source_t* transition) {
|
||||||
{
|
if (transition_handler) {
|
||||||
if (transition_handler)
|
|
||||||
{
|
|
||||||
signal_handler_disconnect(transition_handler,
|
signal_handler_disconnect(transition_handler,
|
||||||
"transition_start", OnTransitionBegin, this);
|
"transition_start", OnTransitionBegin, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!transition_is_cut(transition))
|
if (!transition_is_cut(transition)) {
|
||||||
{
|
|
||||||
transition_handler = obs_source_get_signal_handler(transition);
|
transition_handler = obs_source_get_signal_handler(transition);
|
||||||
signal_handler_connect(transition_handler,
|
signal_handler_connect(transition_handler,
|
||||||
"transition_start", OnTransitionBegin, this);
|
"transition_start", OnTransitionBegin, this);
|
||||||
}
|
} else {
|
||||||
else
|
|
||||||
{
|
|
||||||
transition_handler = nullptr;
|
transition_handler = nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::connectSceneSignals(obs_source_t* scene)
|
void WSEvents::connectSceneSignals(obs_source_t* scene) {
|
||||||
{
|
if (scene_handler) {
|
||||||
if (scene_handler)
|
|
||||||
{
|
|
||||||
signal_handler_disconnect(scene_handler,
|
signal_handler_disconnect(scene_handler,
|
||||||
"reorder", OnSceneReordered, this);
|
"reorder", OnSceneReordered, this);
|
||||||
signal_handler_disconnect(scene_handler,
|
signal_handler_disconnect(scene_handler,
|
||||||
@ -268,35 +249,40 @@ void WSEvents::connectSceneSignals(obs_source_t* scene)
|
|||||||
"item_visible", OnSceneItemVisibilityChanged, this);
|
"item_visible", OnSceneItemVisibilityChanged, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t WSEvents::GetStreamingTime()
|
uint64_t WSEvents::GetStreamingTime() {
|
||||||
{
|
|
||||||
if (_streaming_active)
|
if (_streaming_active)
|
||||||
return (os_gettime_ns() - _stream_starttime);
|
return (os_gettime_ns() - _stream_starttime);
|
||||||
else
|
else
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* WSEvents::GetStreamingTimecode()
|
const char* WSEvents::GetStreamingTimecode() {
|
||||||
{
|
|
||||||
return ns_to_timestamp(GetStreamingTime());
|
return ns_to_timestamp(GetStreamingTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t WSEvents::GetRecordingTime()
|
uint64_t WSEvents::GetRecordingTime() {
|
||||||
{
|
|
||||||
if (_recording_active)
|
if (_recording_active)
|
||||||
return (os_gettime_ns() - _rec_starttime);
|
return (os_gettime_ns() - _rec_starttime);
|
||||||
else
|
else
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* WSEvents::GetRecordingTimecode()
|
const char* WSEvents::GetRecordingTimecode() {
|
||||||
{
|
|
||||||
return ns_to_timestamp(GetRecordingTime());
|
return ns_to_timestamp(GetRecordingTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnSceneChange()
|
/**
|
||||||
{
|
* Indicates a scene change.
|
||||||
// Implements an existing update type from bilhamil's OBS Remote
|
*
|
||||||
|
* @return {String} `scene-name` The new scene.
|
||||||
|
* @return {Array} `sources` List of sources in the new scene.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name SwitchScenes
|
||||||
|
* @category scenes
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
void WSEvents::OnSceneChange() {
|
||||||
obs_data_t* data = obs_data_create();
|
obs_data_t* data = obs_data_create();
|
||||||
|
|
||||||
obs_source_t* current_scene = obs_frontend_get_current_scene();
|
obs_source_t* current_scene = obs_frontend_get_current_scene();
|
||||||
@ -314,20 +300,34 @@ void WSEvents::OnSceneChange()
|
|||||||
|
|
||||||
// Dirty fix : OBS blocks signals when swapping scenes in Studio Mode
|
// Dirty fix : OBS blocks signals when swapping scenes in Studio Mode
|
||||||
// after transition end, so SelectedSceneChanged is never called...
|
// after transition end, so SelectedSceneChanged is never called...
|
||||||
if (Utils::IsPreviewModeActive())
|
if (Utils::IsPreviewModeActive()) {
|
||||||
{
|
|
||||||
QListWidget* list = Utils::GetSceneListControl();
|
QListWidget* list = Utils::GetSceneListControl();
|
||||||
SelectedSceneChanged(list->currentItem(), nullptr);
|
SelectedSceneChanged(list->currentItem(), nullptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnSceneListChange()
|
/**
|
||||||
{
|
* The scene list has been modified.
|
||||||
|
* Scenes have been added, removed, or renamed.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name ScenesChanged
|
||||||
|
* @category scenes
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
void WSEvents::OnSceneListChange() {
|
||||||
broadcastUpdate("ScenesChanged");
|
broadcastUpdate("ScenesChanged");
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnSceneCollectionChange()
|
/**
|
||||||
{
|
* Triggered when switching to another scene collection or when renaming the current scene collection.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name SceneCollectionChanged
|
||||||
|
* @category scenes
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnSceneCollectionChange() {
|
||||||
broadcastUpdate("SceneCollectionChanged");
|
broadcastUpdate("SceneCollectionChanged");
|
||||||
|
|
||||||
scene_handler = nullptr;
|
scene_handler = nullptr;
|
||||||
@ -340,13 +340,29 @@ void WSEvents::OnSceneCollectionChange()
|
|||||||
OnSceneChange();
|
OnSceneChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnSceneCollectionListChange()
|
/**
|
||||||
{
|
* Triggered when a scene collection is created, added, renamed, or removed.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name SceneCollectionListChanged
|
||||||
|
* @category scenes
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnSceneCollectionListChange() {
|
||||||
broadcastUpdate("SceneCollectionListChanged");
|
broadcastUpdate("SceneCollectionListChanged");
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnTransitionChange()
|
/**
|
||||||
{
|
* The active transition has been changed.
|
||||||
|
*
|
||||||
|
* @return {String} `transition-name` The name of the new active transition.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name SwitchTransition
|
||||||
|
* @category transitions
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnTransitionChange() {
|
||||||
obs_source_t* current_transition = obs_frontend_get_current_transition();
|
obs_source_t* current_transition = obs_frontend_get_current_transition();
|
||||||
connectTransitionSignals(current_transition);
|
connectTransitionSignals(current_transition);
|
||||||
|
|
||||||
@ -360,24 +376,54 @@ void WSEvents::OnTransitionChange()
|
|||||||
obs_source_release(current_transition);
|
obs_source_release(current_transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnTransitionListChange()
|
/**
|
||||||
{
|
* The list of available transitions has been modified.
|
||||||
|
* Transitions have been added, removed, or renamed.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name TransitionListChanged
|
||||||
|
* @category transitions
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnTransitionListChange() {
|
||||||
broadcastUpdate("TransitionListChanged");
|
broadcastUpdate("TransitionListChanged");
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnProfileChange()
|
/**
|
||||||
{
|
* Triggered when switching to another profile or when renaming the current profile.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name ProfileChanged
|
||||||
|
* @category profiles
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnProfileChange() {
|
||||||
broadcastUpdate("ProfileChanged");
|
broadcastUpdate("ProfileChanged");
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnProfileListChange()
|
/**
|
||||||
{
|
* Triggered when a profile is created, added, renamed, or removed.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name ProfileListChanged
|
||||||
|
* @category profiles
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnProfileListChange() {
|
||||||
broadcastUpdate("ProfileListChanged");
|
broadcastUpdate("ProfileListChanged");
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnStreamStarting()
|
/**
|
||||||
{
|
* A request to start streaming has been issued.
|
||||||
// Implements an existing update type from bilhamil's OBS Remote
|
*
|
||||||
|
* @return {boolean} `preview-only` Always false (retrocompatibility).
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name StreamStarting
|
||||||
|
* @category streaming
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
void WSEvents::OnStreamStarting() {
|
||||||
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);
|
||||||
|
|
||||||
@ -386,17 +432,31 @@ void WSEvents::OnStreamStarting()
|
|||||||
obs_data_release(data);
|
obs_data_release(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnStreamStarted()
|
/**
|
||||||
{
|
* Streaming started successfully.
|
||||||
// New update type specific to OBS Studio
|
*
|
||||||
|
* @api events
|
||||||
|
* @name StreamStarted
|
||||||
|
* @category streaming
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
void WSEvents::OnStreamStarted() {
|
||||||
_stream_starttime = os_gettime_ns();
|
_stream_starttime = os_gettime_ns();
|
||||||
_lastBytesSent = 0;
|
_lastBytesSent = 0;
|
||||||
broadcastUpdate("StreamStarted");
|
broadcastUpdate("StreamStarted");
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnStreamStopping()
|
/**
|
||||||
{
|
* A request to stop streaming has been issued.
|
||||||
// Implements an existing update type from bilhamil's OBS Remote
|
*
|
||||||
|
* @return {boolean} `preview-only` Always false (retrocompatibility).
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name StreamStopping
|
||||||
|
* @category streaming
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
void WSEvents::OnStreamStopping() {
|
||||||
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);
|
||||||
|
|
||||||
@ -405,57 +465,158 @@ void WSEvents::OnStreamStopping()
|
|||||||
obs_data_release(data);
|
obs_data_release(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnStreamStopped()
|
/**
|
||||||
{
|
* Streaming stopped successfully.
|
||||||
// New update type specific to OBS Studio
|
*
|
||||||
|
* @api events
|
||||||
|
* @name StreamStopped
|
||||||
|
* @category streaming
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
void WSEvents::OnStreamStopped() {
|
||||||
_stream_starttime = 0;
|
_stream_starttime = 0;
|
||||||
broadcastUpdate("StreamStopped");
|
broadcastUpdate("StreamStopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnRecordingStarting()
|
/**
|
||||||
{
|
* A request to start recording has been issued.
|
||||||
// New update type specific to OBS Studio
|
*
|
||||||
|
* @api events
|
||||||
|
* @name RecordingStarting
|
||||||
|
* @category recording
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
void WSEvents::OnRecordingStarting() {
|
||||||
broadcastUpdate("RecordingStarting");
|
broadcastUpdate("RecordingStarting");
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnRecordingStarted()
|
/**
|
||||||
{
|
* Recording started successfully.
|
||||||
// New update type specific to OBS Studio
|
*
|
||||||
|
* @api events
|
||||||
|
* @name RecordingStarted
|
||||||
|
* @category recording
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
void WSEvents::OnRecordingStarted() {
|
||||||
_rec_starttime = os_gettime_ns();
|
_rec_starttime = os_gettime_ns();
|
||||||
broadcastUpdate("RecordingStarted");
|
broadcastUpdate("RecordingStarted");
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnRecordingStopping()
|
/**
|
||||||
{
|
* A request to stop recording has been issued.
|
||||||
// New update type specific to OBS Studio
|
*
|
||||||
|
* @api events
|
||||||
|
* @name RecordingStopping
|
||||||
|
* @category recording
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
void WSEvents::OnRecordingStopping() {
|
||||||
broadcastUpdate("RecordingStopping");
|
broadcastUpdate("RecordingStopping");
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnRecordingStopped()
|
/**
|
||||||
{
|
* Recording stopped successfully.
|
||||||
// New update type specific to OBS Studio
|
*
|
||||||
|
* @api events
|
||||||
|
* @name RecordingStopped
|
||||||
|
* @category recording
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
void WSEvents::OnRecordingStopped() {
|
||||||
_rec_starttime = 0;
|
_rec_starttime = 0;
|
||||||
broadcastUpdate("RecordingStopped");
|
broadcastUpdate("RecordingStopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnExit()
|
/**
|
||||||
{
|
* A request to start the replay buffer has been issued.
|
||||||
// New update type specific to OBS Studio
|
*
|
||||||
|
* @api events
|
||||||
|
* @name ReplayStarting
|
||||||
|
* @category replay buffer
|
||||||
|
* @since 4.2.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnReplayStarting() {
|
||||||
|
broadcastUpdate("ReplayStarting");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replay Buffer started successfully
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name ReplayStarted
|
||||||
|
* @category replay buffer
|
||||||
|
* @since 4.2.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnReplayStarted() {
|
||||||
|
broadcastUpdate("ReplayStarted");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A request to start the replay buffer has been issued.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name ReplayStopping
|
||||||
|
* @category replay buffer
|
||||||
|
* @since 4.2.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnReplayStopping() {
|
||||||
|
broadcastUpdate("ReplayStopping");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replay Buffer stopped successfully
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name ReplayStopped
|
||||||
|
* @category replay buffer
|
||||||
|
* @since 4.2.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnReplayStopped() {
|
||||||
|
broadcastUpdate("ReplayStopped");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OBS is exiting.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name Exiting
|
||||||
|
* @category other
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
void WSEvents::OnExit() {
|
||||||
broadcastUpdate("Exiting");
|
broadcastUpdate("Exiting");
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::StreamStatus()
|
/**
|
||||||
{
|
* Emit every 2 seconds.
|
||||||
|
*
|
||||||
|
* @return {boolean} `streaming` Current streaming state.
|
||||||
|
* @return {boolean} `recording` Current recording state.
|
||||||
|
* @return {boolean} `preview-only` Always false (retrocompatibility).
|
||||||
|
* @return {int} `bytes-per-sec` Amount of data per second (in bytes) transmitted by the stream encoder.
|
||||||
|
* @return {int} `kbits-per-sec` Amount of data per second (in kilobits) transmitted by the stream encoder.
|
||||||
|
* @return {double} `strain` Percentage of dropped frames.
|
||||||
|
* @return {int} `total-stream-time` Total time (in seconds) since the stream started.
|
||||||
|
* @return {int} `num-total-frames` Total number of frames transmitted since the stream started.
|
||||||
|
* @return {int} `num-dropped-frames` Number of frames dropped by the encoder since the stream started.
|
||||||
|
* @return {double} `fps` Current framerate.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name StreamStatus
|
||||||
|
* @category streaming
|
||||||
|
* @since 0.3
|
||||||
|
*/
|
||||||
|
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();
|
||||||
|
|
||||||
obs_output_t* stream_output = obs_frontend_get_streaming_output();
|
obs_output_t* stream_output = obs_frontend_get_streaming_output();
|
||||||
|
|
||||||
if (!stream_output || !streaming_active)
|
if (!stream_output || !streaming_active) {
|
||||||
{
|
if (stream_output) {
|
||||||
if (stream_output)
|
|
||||||
obs_output_release(stream_output);
|
obs_output_release(stream_output);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -503,28 +664,63 @@ void WSEvents::StreamStatus()
|
|||||||
obs_output_release(stream_output);
|
obs_output_release(stream_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::TransitionDurationChanged(int ms)
|
/**
|
||||||
{
|
* The active transition duration has been changed.
|
||||||
|
*
|
||||||
|
* @return {int} `new-duration` New transition duration.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name TransitionDurationChanged
|
||||||
|
* @category transitions
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
void WSEvents::TransitionDurationChanged(int ms) {
|
||||||
obs_data_t* fields = obs_data_create();
|
obs_data_t* fields = obs_data_create();
|
||||||
obs_data_set_int(fields, "new-duration", ms);
|
obs_data_set_int(fields, "new-duration", ms);
|
||||||
|
|
||||||
broadcastUpdate("TransitionDurationChanged", fields);
|
broadcastUpdate("TransitionDurationChanged", fields);
|
||||||
|
|
||||||
obs_data_release(fields);
|
obs_data_release(fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnTransitionBegin(void* param, calldata_t* data)
|
/**
|
||||||
{
|
* A transition (other than "cut") has begun.
|
||||||
|
*
|
||||||
|
* @return {String} `name` Transition name.
|
||||||
|
* @return {int} `duration` Transition duration (in milliseconds).
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name TransitionBegin
|
||||||
|
* @category transitions
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnTransitionBegin(void* param, calldata_t* data) {
|
||||||
UNUSED_PARAMETER(data);
|
UNUSED_PARAMETER(data);
|
||||||
|
|
||||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||||
instance->broadcastUpdate("TransitionBegin");
|
|
||||||
|
|
||||||
blog(LOG_INFO, "transition begin");
|
obs_source_t* current_transition = obs_frontend_get_current_transition();
|
||||||
|
const char* name = obs_source_get_name(current_transition);
|
||||||
|
int duration = Utils::GetTransitionDuration();
|
||||||
|
|
||||||
|
obs_data_t* fields = obs_data_create();
|
||||||
|
obs_data_set_string(fields, "name", name);
|
||||||
|
obs_data_set_int(fields, "duration", duration);
|
||||||
|
|
||||||
|
instance->broadcastUpdate("TransitionBegin", fields);
|
||||||
|
obs_data_release(fields);
|
||||||
|
obs_source_release(current_transition);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnSceneReordered(void* param, calldata_t* data)
|
/**
|
||||||
{
|
* Scene items have been reordered.
|
||||||
|
*
|
||||||
|
* @return {String} `scene-name` Name of the scene where items have been reordered.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name SourceOrderChanged
|
||||||
|
* @category sources
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnSceneReordered(void* param, calldata_t* data) {
|
||||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||||
|
|
||||||
obs_scene_t* scene = nullptr;
|
obs_scene_t* scene = nullptr;
|
||||||
@ -535,12 +731,21 @@ void WSEvents::OnSceneReordered(void* param, calldata_t* data)
|
|||||||
obs_source_get_name(obs_scene_get_source(scene)));
|
obs_source_get_name(obs_scene_get_source(scene)));
|
||||||
|
|
||||||
instance->broadcastUpdate("SourceOrderChanged", fields);
|
instance->broadcastUpdate("SourceOrderChanged", fields);
|
||||||
|
|
||||||
obs_data_release(fields);
|
obs_data_release(fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnSceneItemAdd(void* param, calldata_t* data)
|
/**
|
||||||
{
|
* An item has been added to the current scene.
|
||||||
|
*
|
||||||
|
* @return {String} `scene-name` Name of the scene.
|
||||||
|
* @return {String} `item-name` Name of the item added to the scene.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name SceneItemAdded
|
||||||
|
* @category sources
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnSceneItemAdd(void* param, calldata_t* data) {
|
||||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||||
|
|
||||||
obs_scene_t* scene = nullptr;
|
obs_scene_t* scene = nullptr;
|
||||||
@ -559,12 +764,21 @@ void WSEvents::OnSceneItemAdd(void* param, calldata_t* data)
|
|||||||
obs_data_set_string(fields, "item-name", sceneitem_name);
|
obs_data_set_string(fields, "item-name", sceneitem_name);
|
||||||
|
|
||||||
instance->broadcastUpdate("SceneItemAdded", fields);
|
instance->broadcastUpdate("SceneItemAdded", fields);
|
||||||
|
|
||||||
obs_data_release(fields);
|
obs_data_release(fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnSceneItemDelete(void* param, calldata_t* data)
|
/**
|
||||||
{
|
* An item has been removed from the current scene.
|
||||||
|
*
|
||||||
|
* @return {String} `scene-name` Name of the scene.
|
||||||
|
* @return {String} `item-name` Name of the item removed from the scene.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name SceneItemRemoved
|
||||||
|
* @category sources
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnSceneItemDelete(void* param, calldata_t* data) {
|
||||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||||
|
|
||||||
obs_scene_t* scene = nullptr;
|
obs_scene_t* scene = nullptr;
|
||||||
@ -583,12 +797,22 @@ void WSEvents::OnSceneItemDelete(void* param, calldata_t* data)
|
|||||||
obs_data_set_string(fields, "item-name", sceneitem_name);
|
obs_data_set_string(fields, "item-name", sceneitem_name);
|
||||||
|
|
||||||
instance->broadcastUpdate("SceneItemRemoved", fields);
|
instance->broadcastUpdate("SceneItemRemoved", fields);
|
||||||
|
|
||||||
obs_data_release(fields);
|
obs_data_release(fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data)
|
/**
|
||||||
{
|
* An item's visibility has been toggled.
|
||||||
|
*
|
||||||
|
* @return {String} `scene-name` Name of the scene.
|
||||||
|
* @return {String} `item-name` Name of the item in the scene.
|
||||||
|
* @return {boolean} `item-visible` New visibility state of the item.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name SceneItemVisibilityChanged
|
||||||
|
* @category sources
|
||||||
|
* @since 4.0.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) {
|
||||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||||
|
|
||||||
obs_scene_t* scene = nullptr;
|
obs_scene_t* scene = nullptr;
|
||||||
@ -611,14 +835,22 @@ void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data)
|
|||||||
obs_data_set_bool(fields, "item-visible", visible);
|
obs_data_set_bool(fields, "item-visible", visible);
|
||||||
|
|
||||||
instance->broadcastUpdate("SceneItemVisibilityChanged", fields);
|
instance->broadcastUpdate("SceneItemVisibilityChanged", fields);
|
||||||
|
|
||||||
obs_data_release(fields);
|
obs_data_release(fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::SelectedSceneChanged(QListWidgetItem* current, QListWidgetItem* prev)
|
/**
|
||||||
{
|
* The selected preview scene has changed (only available in Studio Mode).
|
||||||
if (Utils::IsPreviewModeActive())
|
*
|
||||||
{
|
* @return {String} `scene-name` Name of the scene being previewed.
|
||||||
|
* @return {Source|Array} `sources` List of sources composing the scene. Same specification as [`GetCurrentScene`](#getcurrentscene).
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name PreviewSceneChanged
|
||||||
|
* @category studio mode
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
void WSEvents::SelectedSceneChanged(QListWidgetItem* current, QListWidgetItem* prev) {
|
||||||
|
if (Utils::IsPreviewModeActive()) {
|
||||||
obs_scene_t* scene = Utils::SceneListItemToScene(current);
|
obs_scene_t* scene = Utils::SceneListItemToScene(current);
|
||||||
if (!scene) return;
|
if (!scene) return;
|
||||||
|
|
||||||
@ -636,12 +868,20 @@ void WSEvents::SelectedSceneChanged(QListWidgetItem* current, QListWidgetItem* p
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::ModeSwitchClicked(bool checked)
|
/**
|
||||||
{
|
* Studio Mode has been enabled or disabled.
|
||||||
|
*
|
||||||
|
* @return {boolean} `new-state` The new enabled state of Studio Mode.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name StudioModeSwitched
|
||||||
|
* @category studio mode
|
||||||
|
* @since 4.1.0
|
||||||
|
*/
|
||||||
|
void WSEvents::ModeSwitchClicked(bool checked) {
|
||||||
obs_data_t* data = obs_data_create();
|
obs_data_t* data = obs_data_create();
|
||||||
obs_data_set_bool(data, "new-state", checked);
|
obs_data_set_bool(data, "new-state", checked);
|
||||||
|
|
||||||
broadcastUpdate("StudioModeSwitched", data);
|
broadcastUpdate("StudioModeSwitched", data);
|
||||||
|
|
||||||
obs_data_release(data);
|
obs_data_release(data);
|
||||||
}
|
}
|
||||||
|
12
WSEvents.h
12
WSEvents.h
@ -22,12 +22,11 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
|||||||
|
|
||||||
#include <obs-frontend-api.h>
|
#include <obs-frontend-api.h>
|
||||||
#include <QListWidgetItem>
|
#include <QListWidgetItem>
|
||||||
|
|
||||||
#include "WSServer.h"
|
#include "WSServer.h"
|
||||||
|
|
||||||
class WSEvents : public QObject
|
class WSEvents : public QObject {
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit WSEvents(WSServer* srv);
|
explicit WSEvents(WSServer* srv);
|
||||||
~WSEvents();
|
~WSEvents();
|
||||||
@ -42,7 +41,7 @@ class WSEvents : public QObject
|
|||||||
uint64_t GetRecordingTime();
|
uint64_t GetRecordingTime();
|
||||||
const char* GetRecordingTimecode();
|
const char* GetRecordingTimecode();
|
||||||
|
|
||||||
private Q_SLOTS:
|
private slots:
|
||||||
void deferredInitOperations();
|
void deferredInitOperations();
|
||||||
void StreamStatus();
|
void StreamStatus();
|
||||||
void TransitionDurationChanged(int ms);
|
void TransitionDurationChanged(int ms);
|
||||||
@ -88,6 +87,11 @@ class WSEvents : public QObject
|
|||||||
void OnRecordingStopping();
|
void OnRecordingStopping();
|
||||||
void OnRecordingStopped();
|
void OnRecordingStopped();
|
||||||
|
|
||||||
|
void OnReplayStarting();
|
||||||
|
void OnReplayStarted();
|
||||||
|
void OnReplayStopping();
|
||||||
|
void OnReplayStopped();
|
||||||
|
|
||||||
void OnExit();
|
void OnExit();
|
||||||
|
|
||||||
static void OnTransitionBegin(void* param, calldata_t* data);
|
static void OnTransitionBegin(void* param, calldata_t* data);
|
||||||
|
1575
WSRequestHandler.cpp
1575
WSRequestHandler.cpp
File diff suppressed because it is too large
Load Diff
@ -20,13 +20,13 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
|||||||
#ifndef WSREQUESTHANDLER_H
|
#ifndef WSREQUESTHANDLER_H
|
||||||
#define WSREQUESTHANDLER_H
|
#define WSREQUESTHANDLER_H
|
||||||
|
|
||||||
|
#include <QMap>
|
||||||
|
#include <QWebSocket>
|
||||||
|
#include <QWebSocketServer>
|
||||||
|
|
||||||
#include <obs-frontend-api.h>
|
#include <obs-frontend-api.h>
|
||||||
|
|
||||||
#include <QtWebSockets/QWebSocket>
|
class WSRequestHandler : public QObject {
|
||||||
#include <QtWebSockets/QWebSocketServer>
|
|
||||||
|
|
||||||
class WSRequestHandler : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@ -61,6 +61,7 @@ class WSRequestHandler : public QObject
|
|||||||
static void HandleSetSceneItemPosition(WSRequestHandler* req);
|
static void HandleSetSceneItemPosition(WSRequestHandler* req);
|
||||||
static void HandleSetSceneItemTransform(WSRequestHandler* req);
|
static void HandleSetSceneItemTransform(WSRequestHandler* req);
|
||||||
static void HandleSetSceneItemCrop(WSRequestHandler* req);
|
static void HandleSetSceneItemCrop(WSRequestHandler* req);
|
||||||
|
static void HandleResetSceneItem(WSRequestHandler* req);
|
||||||
|
|
||||||
static void HandleGetStreamingStatus(WSRequestHandler* req);
|
static void HandleGetStreamingStatus(WSRequestHandler* req);
|
||||||
static void HandleStartStopStreaming(WSRequestHandler* req);
|
static void HandleStartStopStreaming(WSRequestHandler* req);
|
||||||
@ -70,6 +71,11 @@ class WSRequestHandler : public QObject
|
|||||||
static void HandleStartRecording(WSRequestHandler* req);
|
static void HandleStartRecording(WSRequestHandler* req);
|
||||||
static void HandleStopRecording(WSRequestHandler* req);
|
static void HandleStopRecording(WSRequestHandler* req);
|
||||||
|
|
||||||
|
static void HandleStartStopReplayBuffer(WSRequestHandler* req);
|
||||||
|
static void HandleStartReplayBuffer(WSRequestHandler* req);
|
||||||
|
static void HandleStopReplayBuffer(WSRequestHandler* req);
|
||||||
|
static void HandleSaveReplayBuffer(WSRequestHandler* req);
|
||||||
|
|
||||||
static void HandleSetRecordingFolder(WSRequestHandler* req);
|
static void HandleSetRecordingFolder(WSRequestHandler* req);
|
||||||
static void HandleGetRecordingFolder(WSRequestHandler* req);
|
static void HandleGetRecordingFolder(WSRequestHandler* req);
|
||||||
|
|
||||||
@ -82,6 +88,8 @@ class WSRequestHandler : public QObject
|
|||||||
static void HandleToggleMute(WSRequestHandler* req);
|
static void HandleToggleMute(WSRequestHandler* req);
|
||||||
static void HandleSetMute(WSRequestHandler* req);
|
static void HandleSetMute(WSRequestHandler* req);
|
||||||
static void HandleGetMute(WSRequestHandler* req);
|
static void HandleGetMute(WSRequestHandler* req);
|
||||||
|
static void HandleSetSyncOffset(WSRequestHandler* req);
|
||||||
|
static void HandleGetSyncOffset(WSRequestHandler* req);
|
||||||
static void HandleGetSpecialSources(WSRequestHandler* req);
|
static void HandleGetSpecialSources(WSRequestHandler* req);
|
||||||
|
|
||||||
static void HandleSetCurrentSceneCollection(WSRequestHandler* req);
|
static void HandleSetCurrentSceneCollection(WSRequestHandler* req);
|
||||||
|
72
WSServer.cpp
72
WSServer.cpp
@ -30,30 +30,21 @@ QT_USE_NAMESPACE
|
|||||||
|
|
||||||
WSServer* WSServer::Instance = nullptr;
|
WSServer* WSServer::Instance = nullptr;
|
||||||
|
|
||||||
WSServer::WSServer(QObject* parent) :
|
WSServer::WSServer(QObject* parent)
|
||||||
QObject(parent),
|
: QObject(parent),
|
||||||
_wsServer(Q_NULLPTR),
|
_wsServer(Q_NULLPTR),
|
||||||
_clients(),
|
_clients(),
|
||||||
_clMutex(QMutex::Recursive)
|
_clMutex(QMutex::Recursive) {
|
||||||
{
|
|
||||||
_serverThread = new QThread();
|
|
||||||
|
|
||||||
_wsServer = new QWebSocketServer(
|
_wsServer = new QWebSocketServer(
|
||||||
QStringLiteral("obs-websocket"),
|
QStringLiteral("obs-websocket"),
|
||||||
QWebSocketServer::NonSecureMode,
|
QWebSocketServer::NonSecureMode);
|
||||||
_serverThread);
|
|
||||||
|
|
||||||
_serverThread->start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WSServer::~WSServer()
|
WSServer::~WSServer() {
|
||||||
{
|
|
||||||
Stop();
|
Stop();
|
||||||
delete _serverThread;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSServer::Start(quint16 port)
|
void WSServer::Start(quint16 port) {
|
||||||
{
|
|
||||||
if (port == _wsServer->serverPort())
|
if (port == _wsServer->serverPort())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -61,15 +52,13 @@ void WSServer::Start(quint16 port)
|
|||||||
Stop();
|
Stop();
|
||||||
|
|
||||||
bool serverStarted = _wsServer->listen(QHostAddress::Any, port);
|
bool serverStarted = _wsServer->listen(QHostAddress::Any, port);
|
||||||
if (serverStarted)
|
if (serverStarted) {
|
||||||
{
|
connect(_wsServer, SIGNAL(newConnection()),
|
||||||
connect(_wsServer, &QWebSocketServer::newConnection,
|
this, SLOT(onNewConnection()));
|
||||||
this, &WSServer::onNewConnection);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSServer::Stop()
|
void WSServer::Stop() {
|
||||||
{
|
|
||||||
_clMutex.lock();
|
_clMutex.lock();
|
||||||
for(QWebSocket* pClient : _clients) {
|
for(QWebSocket* pClient : _clients) {
|
||||||
pClient->close();
|
pClient->close();
|
||||||
@ -79,34 +68,27 @@ void WSServer::Stop()
|
|||||||
_wsServer->close();
|
_wsServer->close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSServer::broadcast(QString message)
|
void WSServer::broadcast(QString message) {
|
||||||
{
|
|
||||||
_clMutex.lock();
|
_clMutex.lock();
|
||||||
|
|
||||||
for(QWebSocket* pClient : _clients) {
|
for(QWebSocket* pClient : _clients) {
|
||||||
if (Config::Current()->AuthRequired
|
if (Config::Current()->AuthRequired
|
||||||
&& (pClient->property(PROP_AUTHENTICATED).toBool() == false))
|
&& (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();
|
_clMutex.unlock();
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSServer::onNewConnection()
|
void WSServer::onNewConnection() {
|
||||||
{
|
|
||||||
QWebSocket* pSocket = _wsServer->nextPendingConnection();
|
QWebSocket* pSocket = _wsServer->nextPendingConnection();
|
||||||
|
if (pSocket) {
|
||||||
|
connect(pSocket, SIGNAL(textMessageReceived(const QString&)),
|
||||||
|
this, SLOT(onTextMessageReceived(QString)));
|
||||||
|
connect(pSocket, SIGNAL(disconnected()),
|
||||||
|
this, SLOT(onSocketDisconnected()));
|
||||||
|
|
||||||
if (pSocket)
|
|
||||||
{
|
|
||||||
connect(pSocket, &QWebSocket::textMessageReceived,
|
|
||||||
this, &WSServer::textMessageReceived);
|
|
||||||
connect(pSocket, &QWebSocket::disconnected,
|
|
||||||
this, &WSServer::socketDisconnected);
|
|
||||||
pSocket->setProperty(PROP_AUTHENTICATED, false);
|
pSocket->setProperty(PROP_AUTHENTICATED, false);
|
||||||
|
|
||||||
_clMutex.lock();
|
_clMutex.lock();
|
||||||
@ -121,7 +103,7 @@ void WSServer::onNewConnection()
|
|||||||
|
|
||||||
QString msg = QString(obs_module_text("OBSWebsocket.ConnectNotify.ClientIP"))
|
QString msg = QString(obs_module_text("OBSWebsocket.ConnectNotify.ClientIP"))
|
||||||
+ QString(" ")
|
+ QString(" ")
|
||||||
+ clientAddr.toString();
|
+ Utils::FormatIPAddress(clientAddr);
|
||||||
|
|
||||||
Utils::SysTrayNotify(msg,
|
Utils::SysTrayNotify(msg,
|
||||||
QSystemTrayIcon::Information,
|
QSystemTrayIcon::Information,
|
||||||
@ -129,23 +111,17 @@ void WSServer::onNewConnection()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSServer::textMessageReceived(QString message)
|
void WSServer::onTextMessageReceived(QString message) {
|
||||||
{
|
|
||||||
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
|
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
|
||||||
|
if (pSocket) {
|
||||||
if (pSocket)
|
|
||||||
{
|
|
||||||
WSRequestHandler handler(pSocket);
|
WSRequestHandler handler(pSocket);
|
||||||
handler.processIncomingMessage(message);
|
handler.processIncomingMessage(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSServer::socketDisconnected()
|
void WSServer::onSocketDisconnected() {
|
||||||
{
|
|
||||||
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
|
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
|
||||||
|
if (pSocket) {
|
||||||
if (pSocket)
|
|
||||||
{
|
|
||||||
pSocket->setProperty(PROP_AUTHENTICATED, false);
|
pSocket->setProperty(PROP_AUTHENTICATED, false);
|
||||||
|
|
||||||
_clMutex.lock();
|
_clMutex.lock();
|
||||||
@ -162,7 +138,7 @@ void WSServer::socketDisconnected()
|
|||||||
|
|
||||||
QString msg = QString(obs_module_text("OBSWebsocket.ConnectNotify.ClientIP"))
|
QString msg = QString(obs_module_text("OBSWebsocket.ConnectNotify.ClientIP"))
|
||||||
+ QString(" ")
|
+ QString(" ")
|
||||||
+ clientAddr.toString();
|
+ Utils::FormatIPAddress(clientAddr);
|
||||||
|
|
||||||
Utils::SysTrayNotify(msg,
|
Utils::SysTrayNotify(msg,
|
||||||
QSystemTrayIcon::Information,
|
QSystemTrayIcon::Information,
|
||||||
|
21
WSServer.h
21
WSServer.h
@ -19,19 +19,17 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
|||||||
#ifndef WSSERVER_H
|
#ifndef WSSERVER_H
|
||||||
#define WSSERVER_H
|
#define WSSERVER_H
|
||||||
|
|
||||||
#include <QtCore/QObject>
|
#include <QObject>
|
||||||
#include <QtCore/QList>
|
#include <QList>
|
||||||
#include <QtCore/QMutex>
|
#include <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(QObject* parent = Q_NULLPTR);
|
explicit WSServer(QObject* parent = Q_NULLPTR);
|
||||||
virtual ~WSServer();
|
virtual ~WSServer();
|
||||||
@ -40,16 +38,15 @@ class WSServer : public QObject
|
|||||||
void broadcast(QString message);
|
void broadcast(QString message);
|
||||||
static WSServer* Instance;
|
static WSServer* Instance;
|
||||||
|
|
||||||
private Q_SLOTS:
|
private slots:
|
||||||
void onNewConnection();
|
void onNewConnection();
|
||||||
void textMessageReceived(QString message);
|
void onTextMessageReceived(QString message);
|
||||||
void socketDisconnected();
|
void onSocketDisconnected();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QWebSocketServer* _wsServer;
|
QWebSocketServer* _wsServer;
|
||||||
QList<QWebSocket*> _clients;
|
QList<QWebSocket*> _clients;
|
||||||
QMutex _clMutex;
|
QMutex _clMutex;
|
||||||
QThread* _serverThread;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // WSSERVER_H
|
#endif // WSSERVER_H
|
@ -15,7 +15,7 @@ install:
|
|||||||
- set build_config=Release
|
- set build_config=Release
|
||||||
- git clone --recursive https://github.com/jp9000/obs-studio
|
- git clone --recursive https://github.com/jp9000/obs-studio
|
||||||
- cd C:\projects\obs-studio\
|
- cd C:\projects\obs-studio\
|
||||||
- git checkout 19.0.2
|
- git checkout 19.0.3
|
||||||
- mkdir build
|
- mkdir build
|
||||||
- mkdir build32
|
- mkdir build32
|
||||||
- mkdir build64
|
- mkdir build64
|
||||||
|
11
docs/.editorconfig
Normal file
11
docs/.editorconfig
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.md, *.mustache]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
insert_final_newline = false
|
4
docs/.gitignore
vendored
Normal file
4
docs/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
1
docs/.npmrc
Normal file
1
docs/.npmrc
Normal file
@ -0,0 +1 @@
|
|||||||
|
package-lock=false
|
21
docs/README.md
Normal file
21
docs/README.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
## Installation
|
||||||
|
|
||||||
|
Install node and update npm if necessary.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd obs-websocket/docs
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Just extract the comments.
|
||||||
|
npm run comments
|
||||||
|
|
||||||
|
# Just render the markdown.
|
||||||
|
npm run docs
|
||||||
|
|
||||||
|
# Do both comments and markdown.
|
||||||
|
npm run build
|
||||||
|
```
|
52
docs/comments.js
Normal file
52
docs/comments.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const glob = require('glob');
|
||||||
|
const parseComments = require('parse-comments');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read each file and call `parse-comments` on it.
|
||||||
|
*
|
||||||
|
* @param {String|Array} `files` List of file paths to read from.
|
||||||
|
* @return {Object|Array} Array of `parse-comments` objects.
|
||||||
|
*/
|
||||||
|
const parseFiles = files => {
|
||||||
|
let response = [];
|
||||||
|
files.forEach(file => {
|
||||||
|
const f = fs.readFileSync(file, 'utf8').toString();
|
||||||
|
response = response.concat(parseComments(f));
|
||||||
|
});
|
||||||
|
|
||||||
|
return response;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters/sorts the results from `parse-comments`.
|
||||||
|
* @param {Object|Array} `comments` Array of `parse-comments` objects.
|
||||||
|
* @return {Object} Filtered comments sorted by `@api` and `@category`.
|
||||||
|
*/
|
||||||
|
const processComments = comments => {
|
||||||
|
let sorted = {};
|
||||||
|
|
||||||
|
comments.forEach(comment => {
|
||||||
|
if (typeof comment.api === 'undefined') return;
|
||||||
|
|
||||||
|
// Store the object based on its api (ie. requests, events) and category (ie. general, scenes, etc).
|
||||||
|
comment.category = comment.category || 'miscellaneous';
|
||||||
|
|
||||||
|
// Remove some unnecessary properties to avoid result differences in travis.
|
||||||
|
comment.comment = undefined;
|
||||||
|
comment.context = undefined;
|
||||||
|
|
||||||
|
// Create an entry in sorted for the api/category if one does not exist.
|
||||||
|
sorted[comment.api] = sorted[comment.api] || {};
|
||||||
|
sorted[comment.api][comment.category] = sorted[comment.api][comment.category] || [];
|
||||||
|
|
||||||
|
// Store the comment in the appropriate api/category.
|
||||||
|
sorted[comment.api][comment.category].push(comment);
|
||||||
|
});
|
||||||
|
|
||||||
|
return sorted;
|
||||||
|
};
|
||||||
|
|
||||||
|
const files = glob.sync("./../*.@(cpp|h)");
|
||||||
|
const comments = processComments(parseFiles(files));
|
||||||
|
fs.writeFileSync('./generated/comments.json', JSON.stringify(comments, null, 2));
|
31
docs/docs.js
Normal file
31
docs/docs.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const toc = require('markdown-toc');
|
||||||
|
const handlebars = require('handlebars');
|
||||||
|
|
||||||
|
const helpers = require('handlebars-helpers')({
|
||||||
|
handlebars: handlebars
|
||||||
|
});
|
||||||
|
|
||||||
|
// Allows pipe characters to be used within markdown tables.
|
||||||
|
handlebars.registerHelper('depipe', (text) => {
|
||||||
|
return text.replace('|', `\\|`);
|
||||||
|
});
|
||||||
|
|
||||||
|
const insertHeader = (text) => {
|
||||||
|
return '<!-- This file was generated based on handlebars templates. Do not edit directly! -->\n\n' + text;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes `protocol.md` using `protocol.mustache`.
|
||||||
|
*
|
||||||
|
* @param {Object} `data` Data to assign to the mustache template.
|
||||||
|
*/
|
||||||
|
const generateProtocol = (templatePath, data) => {
|
||||||
|
const template = fs.readFileSync(templatePath).toString();
|
||||||
|
const generated = handlebars.compile(template)(data);
|
||||||
|
return insertHeader(toc.insert(generated));
|
||||||
|
};
|
||||||
|
|
||||||
|
const comments = fs.readFileSync('./generated/comments.json', 'utf8');
|
||||||
|
const markdown = generateProtocol('./protocol.hbs', JSON.parse(comments));
|
||||||
|
fs.writeFileSync('./generated/protocol.md', markdown);
|
4370
docs/generated/comments.json
Normal file
4370
docs/generated/comments.json
Normal file
File diff suppressed because it is too large
Load Diff
1840
docs/generated/protocol.md
Normal file
1840
docs/generated/protocol.md
Normal file
File diff suppressed because it is too large
Load Diff
21
docs/package.json
Normal file
21
docs/package.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "obs-websocket-docs",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "docs.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"docs": "node ./docs.js",
|
||||||
|
"comments": "node ./comments.js",
|
||||||
|
"build": "npm run comments && npm run docs"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"glob": "^7.1.2",
|
||||||
|
"handlebars": "^4.0.10",
|
||||||
|
"handlebars-helpers": "^0.9.6",
|
||||||
|
"markdown-toc": "^1.1.0",
|
||||||
|
"parse-comments": "^0.4.3"
|
||||||
|
}
|
||||||
|
}
|
11
docs/partials/eventsHeader.md
Normal file
11
docs/partials/eventsHeader.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Events
|
||||||
|
Events are broadcast by the server to each connected client when a recognized action occurs within OBS.
|
||||||
|
|
||||||
|
An event message will contain at least the following base fields:
|
||||||
|
- `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 sent using the format: `HH:MM:SS.mmm`
|
||||||
|
|
||||||
|
Additional fields may be present in the event message depending on the event type.
|
39
docs/partials/introduction.md
Normal file
39
docs/partials/introduction.md
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# obs-websocket 4.2.0 protocol reference
|
||||||
|
|
||||||
|
**This is the reference for obs-websocket 4.2.0. See the list below for older versions.**
|
||||||
|
- [4.1.0 protocol reference](https://github.com/Palakis/obs-websocket/blob/4.1.0/PROTOCOL.md)
|
||||||
|
- [4.0.0 protocol reference](https://github.com/Palakis/obs-websocket/blob/4.0.0/PROTOCOL.md)
|
||||||
|
|
||||||
|
# General Introduction
|
||||||
|
Messages are exchanged between the client and the server as JSON objects.
|
||||||
|
This protocol is based on the original OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio.
|
||||||
|
|
||||||
|
|
||||||
|
# Authentication
|
||||||
|
OBSWebSocket uses SHA256 to transmit credentials.
|
||||||
|
|
||||||
|
A request for [`GetAuthRequired`](#getauthrequired) returns two elements:
|
||||||
|
- A `challenge`: a random string that will be used to generate the auth response.
|
||||||
|
- A `salt`: applied to the password when generating the auth response.
|
||||||
|
|
||||||
|
To generate the answer to the auth challenge, follow this procedure:
|
||||||
|
- Concatenate the user declared password with the `salt` sent by the server (in this order: `password + server salt`).
|
||||||
|
- Generate a binary SHA256 hash of the result and encode the resulting SHA256 binary hash to base64, known as a `base64 secret`.
|
||||||
|
- Concatenate the base64 secret with the `challenge` sent by the server (in this order: `base64 secret + server challenge`).
|
||||||
|
- Generate a binary SHA256 hash of the result and encode it to base64.
|
||||||
|
- Voilà, this last base64 string is the `auth response`. You may now use it to authenticate to the server with the [`Authenticate`](#authenticate) request.
|
||||||
|
|
||||||
|
Pseudo Code Example:
|
||||||
|
```
|
||||||
|
password = "supersecretpassword"
|
||||||
|
challenge = "ztTBnnuqrqaKDzRM3xcVdbYm"
|
||||||
|
salt = "PZVbYpvAnZut2SS6JNJytDm9"
|
||||||
|
|
||||||
|
secret_string = password + salt
|
||||||
|
secret_hash = binary_sha256(secret_string)
|
||||||
|
secret = base64_encode(secret_hash)
|
||||||
|
|
||||||
|
auth_response_string = secret + challenge
|
||||||
|
auth_response_hash = binary_sha256(auth_response_string)
|
||||||
|
auth_response = base64_encode(auth_response_hash)
|
||||||
|
```
|
11
docs/partials/requestsHeader.md
Normal file
11
docs/partials/requestsHeader.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Requests
|
||||||
|
Requests are sent by the client and require at least the following two fields:
|
||||||
|
- `request-type` _String_: String name of the request type.
|
||||||
|
- `message-id` _String_: Client defined identifier for the message, will be echoed in the response.
|
||||||
|
|
||||||
|
Once a request is sent, the server will return a JSON response with at least the following fields:
|
||||||
|
- `message-id` _String_: The client defined identifier specified in the request.
|
||||||
|
- `status` _String_: Response status, will be one of the following: `ok`, `error`
|
||||||
|
- `error` _String_: An error message accompanying an `error` status.
|
||||||
|
|
||||||
|
Additional information may be required/returned depending on the request type. See below for more information.
|
92
docs/protocol.hbs
Normal file
92
docs/protocol.hbs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
{{#read "partials/introduction.md"}}{{/read}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Table of Contents
|
||||||
|
<!-- toc -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{{#read "partials/eventsHeader.md"}}{{/read}}
|
||||||
|
|
||||||
|
{{#each events}}
|
||||||
|
## {{capitalizeAll @key}}
|
||||||
|
|
||||||
|
{{#each this}}
|
||||||
|
### {{name}}
|
||||||
|
|
||||||
|
{{#eq since "unreleased"}}
|
||||||
|
- Unreleased
|
||||||
|
{{else}}
|
||||||
|
- Added in v{{since}}
|
||||||
|
{{/eq}}
|
||||||
|
|
||||||
|
{{{description}}}
|
||||||
|
|
||||||
|
**Response Items:**
|
||||||
|
|
||||||
|
{{#if returns.length}}
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | :---: | ------------|
|
||||||
|
{{#each returns}}
|
||||||
|
| `{{name}}` | _{{depipe type}}_ | {{{description}}} |
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
{{else}}
|
||||||
|
_No additional response items._
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
{{/each}}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{{#read "partials/requestsHeader.md"}}{{/read}}
|
||||||
|
|
||||||
|
{{#each requests}}
|
||||||
|
## {{capitalizeAll @key}}
|
||||||
|
|
||||||
|
{{#each this}}
|
||||||
|
### {{name}}
|
||||||
|
|
||||||
|
{{#eq since "unreleased"}}
|
||||||
|
- Unreleased
|
||||||
|
{{else}}
|
||||||
|
- Added in v{{since}}
|
||||||
|
{{/eq}}
|
||||||
|
|
||||||
|
{{{description}}}
|
||||||
|
|
||||||
|
**Request Fields:**
|
||||||
|
|
||||||
|
{{#if params.length}}
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | :---: | ------------|
|
||||||
|
{{#each params}}
|
||||||
|
| `{{name}}` | _{{depipe type}}_ | {{{description}}} |
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
{{else}}
|
||||||
|
_No specified parameters._
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
**Response Items:**
|
||||||
|
|
||||||
|
{{#if returns.length}}
|
||||||
|
| Name | Type | Description |
|
||||||
|
| ---- | :---: | ------------|
|
||||||
|
{{#each returns}}
|
||||||
|
| `{{name}}` | _{{depipe type}}_ | {{{description}}} |
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
{{else}}
|
||||||
|
_No additional response items._
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
{{/each}}
|
||||||
|
{{/each}}
|
||||||
|
|
@ -2,7 +2,7 @@
|
|||||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||||
|
|
||||||
#define MyAppName "obs-websocket"
|
#define MyAppName "obs-websocket"
|
||||||
#define MyAppVersion "4.0.0"
|
#define MyAppVersion "4.2.0"
|
||||||
#define MyAppPublisher "St<53>phane Lepin"
|
#define MyAppPublisher "St<53>phane Lepin"
|
||||||
#define MyAppURL "http://github.com/Palakis/obs-websocket"
|
#define MyAppURL "http://github.com/Palakis/obs-websocket"
|
||||||
|
|
||||||
|
@ -31,11 +31,12 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
|||||||
OBS_DECLARE_MODULE()
|
OBS_DECLARE_MODULE()
|
||||||
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
|
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
|
||||||
|
|
||||||
SettingsDialog *settings_dialog;
|
SettingsDialog* settings_dialog;
|
||||||
|
|
||||||
bool obs_module_load(void)
|
bool obs_module_load(void) {
|
||||||
{
|
|
||||||
blog(LOG_INFO, "you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION);
|
blog(LOG_INFO, "you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION);
|
||||||
|
blog(LOG_INFO, "qt version (compile-time): %s ; qt version (run-time): %s",
|
||||||
|
QT_VERSION_STR, qVersion());
|
||||||
|
|
||||||
// Core setup
|
// Core setup
|
||||||
Config* config = Config::Current();
|
Config* config = Config::Current();
|
||||||
@ -48,7 +49,7 @@ bool obs_module_load(void)
|
|||||||
WSServer::Instance->Start(config->ServerPort);
|
WSServer::Instance->Start(config->ServerPort);
|
||||||
|
|
||||||
// UI setup
|
// UI setup
|
||||||
QAction *menu_action = (QAction*)obs_frontend_add_tools_menu_qaction(
|
QAction* menu_action = (QAction*)obs_frontend_add_tools_menu_qaction(
|
||||||
obs_module_text("OBSWebsocket.Menu.SettingsItem"));
|
obs_module_text("OBSWebsocket.Menu.SettingsItem"));
|
||||||
|
|
||||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||||
@ -67,8 +68,7 @@ bool obs_module_load(void)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void obs_module_unload()
|
void obs_module_unload() {
|
||||||
{
|
|
||||||
blog(LOG_INFO, "goodbye!");
|
blog(LOG_INFO, "goodbye!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,8 +20,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
|||||||
#define OBSWEBSOCKET_H
|
#define OBSWEBSOCKET_H
|
||||||
|
|
||||||
#define PROP_AUTHENTICATED "wsclient_authenticated"
|
#define PROP_AUTHENTICATED "wsclient_authenticated"
|
||||||
#define OBS_WEBSOCKET_VERSION "4.1.0"
|
#define OBS_WEBSOCKET_VERSION "4.2.0"
|
||||||
#define API_VERSION 1.3
|
|
||||||
|
|
||||||
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)
|
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user