mirror of
https://github.com/Palakis/obs-websocket.git
synced 2024-08-30 18:12:16 +00:00
Compare commits
170 Commits
Author | SHA1 | Date | |
---|---|---|---|
beadb56b05 | |||
745fb5ea29 | |||
58434cac3b | |||
c34dff17ff | |||
73f00ca195 | |||
40503b4212 | |||
4ada388828 | |||
e2d261259d | |||
677e393f47 | |||
e5dae8f5a7 | |||
650de119a4 | |||
6d7975afeb | |||
811e3f8cc8 | |||
4f3be34db1 | |||
b160fd2320 | |||
002bf08b97 | |||
a6bab968f5 | |||
408b336057 | |||
7e5716185e | |||
6d47bd6477 | |||
989c8b1857 | |||
06e9e0afab | |||
d75523c111 | |||
dacec803c6 | |||
0ae4416242 | |||
079d7eb6ca | |||
b764b4d0e5 | |||
2c2d61d908 | |||
6a323b9371 | |||
d76ff16162 | |||
952f3a586a | |||
3ecb9702ef | |||
563936ea08 | |||
13cd2704ae | |||
ae83d9dca1 | |||
66a059ecdf | |||
53936a4f76 | |||
488a095fdf | |||
1878de9f6b | |||
2a2f3441ef | |||
53c939a97f | |||
8d7ed32fc2 | |||
a1fa5dc3cb | |||
4b9a84ccee | |||
650957b6d1 | |||
6e21d041fe | |||
e2b70fd795 | |||
2d49bcc437 | |||
8b3dce3256 | |||
8cf6a1e72c | |||
a3ce7197ac | |||
5da1e55f8e | |||
661fd4efa8 | |||
047e6e11bf | |||
db8bc1af2d | |||
1386e4f91c | |||
11e2717809 | |||
57bc0a2b95 | |||
8efb30c4ee | |||
969feefcc5 | |||
db1527ab9b | |||
e88a60fa50 | |||
7ab3e38da7 | |||
7b0b836809 | |||
c8e7cfcd7b | |||
c516c89c97 | |||
c2937d7857 | |||
fe644cfa82 | |||
e84e5388a5 | |||
5e11d0ea13 | |||
a3ecb6e0e9 | |||
1d30f13fd8 | |||
b9ae28483c | |||
6a6d316e09 | |||
22d2ee6bbd | |||
68e55613fc | |||
1d44d3a109 | |||
11a641cc0d | |||
7570fbf7a4 | |||
67f7e28867 | |||
9b2d30b4d5 | |||
399815525f | |||
191cfa08d5 | |||
fcf1fa8aff | |||
73302cb060 | |||
70a8533f5e | |||
fe724db12d | |||
096a8ec6ba | |||
cc5f9c9aa7 | |||
3e14b41600 | |||
ccc2bd8667 | |||
45f86b17f1 | |||
90aebecc5b | |||
b0170ef671 | |||
d418b4e624 | |||
47505547af | |||
de2e73c9d4 | |||
ab1a43163b | |||
2556dd320f | |||
2120381c0e | |||
dc06900f7f | |||
7571dd5042 | |||
d6caa872b8 | |||
2e9829ddd1 | |||
9621ea90f7 | |||
f58da1254b | |||
a6ba7f8feb | |||
9c89f12275 | |||
41181f7260 | |||
7251862ecf | |||
c0512d5b5f | |||
b9731dff21 | |||
3981abc5ca | |||
6aef437f58 | |||
9b7752896a | |||
d074027610 | |||
b137148186 | |||
e1f6260034 | |||
b24cbaa904 | |||
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
|
||||
- [ ] Bug
|
||||
- [ ] Feature request
|
||||
- [ ] Other
|
||||
##### Issue type
|
||||
Bug report? Feature request? Other?
|
||||
|
||||
#### Description
|
||||
##### Description
|
||||
*Replace this with a description of the bug encountered or feature requested.*
|
||||
|
||||
#### Steps to reproduce
|
||||
*If it's a bug, please describe the steps to reproduce it. Otherwise, remove this section.*
|
||||
##### Steps to reproduce and other useful info
|
||||
*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** :
|
||||
- **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.
|
||||
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/
|
||||
/release/
|
||||
/installer/Output/
|
||||
|
||||
.vscode
|
22
.travis.yml
22
.travis.yml
@ -1,4 +1,5 @@
|
||||
language: cpp
|
||||
|
||||
env:
|
||||
global:
|
||||
# AWS key ID
|
||||
@ -6,9 +7,18 @@ env:
|
||||
# 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=
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- os: linux
|
||||
env: _generate_docs
|
||||
script: "./CI/generate-docs.sh"
|
||||
|
||||
- os: linux
|
||||
env: _linux_build
|
||||
dist: trusty
|
||||
sudo: required
|
||||
services:
|
||||
@ -23,10 +33,12 @@ matrix:
|
||||
- docker exec -it xenial /root/obs-websocket/CI/package-xenial.sh
|
||||
|
||||
- os: osx
|
||||
env: _macos_build
|
||||
osx_image: xcode8.3
|
||||
before_install: "./CI/install-dependencies-osx.sh"
|
||||
script: "./CI/build-osx.sh"
|
||||
after_success: "./CI/package-osx.sh"
|
||||
before_install: "./CI/install-dependencies-macos.sh"
|
||||
script: "./CI/build-macos.sh"
|
||||
after_success:
|
||||
- ./CI/package-macos.sh
|
||||
|
||||
deploy:
|
||||
- provider: s3
|
||||
@ -39,7 +51,9 @@ deploy:
|
||||
acl: public_read
|
||||
on:
|
||||
repo: Palakis/obs-websocket
|
||||
condition: "$TRAVIS_OS_NAME = linux"
|
||||
condition:
|
||||
- "$TRAVIS_OS_NAME = linux"
|
||||
- "-d /home/travis/package"
|
||||
all_branches: true
|
||||
- provider: s3
|
||||
region: eu-central-1
|
||||
|
13
BUILDING.md
13
BUILDING.md
@ -1,6 +1,6 @@
|
||||
# Compiling obs-websocket
|
||||
## Prerequisites
|
||||
You'll need [QT 5.7.0](https://download.qt.io/official_releases/qt/5.7/5.7.0/), CMake, and a working development environment for OBS Studio installed on your computer.
|
||||
You'll need [QT 5.9.0](https://download.qt.io/official_releases/qt/5.7/5.7.0/), CMake, and a working development environment for OBS Studio installed on your computer.
|
||||
|
||||
## Windows
|
||||
In cmake-gui, you'll have to set the following variables :
|
||||
@ -22,7 +22,16 @@ sudo make install
|
||||
```
|
||||
|
||||
## OS X
|
||||
*To do*
|
||||
Use of the Travis macOS CI scripts is recommended. Please note that these scripts install new software and can change several settings on your system. An existing obs-studio development environment is not required, as `install-dependencies-macos.sh` will install it for you.
|
||||
Of course, you're encouraged to dig through the contents of these scripts to look for issues or specificities.
|
||||
```
|
||||
git clone --recursive https://github.com/Palakis/obs-websocket.git
|
||||
cd obs-websocket
|
||||
./CI/install-dependencies-macos.sh
|
||||
./CI/build-macos.sh
|
||||
./CI/package-macos.sh
|
||||
```
|
||||
This will result in a ready-to-use `obs-websocket.pkg` installer in the `release` subfolder.
|
||||
|
||||
## Automated Builds
|
||||
- Windows : [](https://ci.appveyor.com/project/Palakis/obs-websocket/history)
|
||||
|
@ -1,11 +1,13 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
|
||||
mkdir build && cd build
|
||||
#export QT_PREFIX="$(find /usr/local/Cellar/qt5 -d 1 | tail -n 1)"
|
||||
|
||||
mkdir build && cd build
|
||||
cmake .. \
|
||||
-DQTDIR=/usr/local/opt/qt5 \
|
||||
-DQTDIR=/usr/local/opt/qt \
|
||||
-DLIBOBS_INCLUDE_DIR=../../obs-studio/libobs \
|
||||
-DLIBOBS_LIB=../../obs-studio/libobs \
|
||||
-DOBS_FRONTEND_LIB="$(pwd)/../../obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \
|
||||
-DCMAKE_INSTALL_PREFIX=/usr \
|
||||
&& make -j4
|
33
CI/generate-docs.sh
Executable file
33
CI/generate-docs.sh
Executable file
@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
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
|
118
CI/install-build-obs.cmd
Normal file
118
CI/install-build-obs.cmd
Normal file
@ -0,0 +1,118 @@
|
||||
@echo off
|
||||
SETLOCAL EnableDelayedExpansion
|
||||
|
||||
REM Check if obs-studio build exists.
|
||||
REM If the obs-studio directory does exist, check if the last OBS tag built
|
||||
REM matches the latest OBS tag.
|
||||
REM If the tags match, do not build obs-studio.
|
||||
REM If the tags do not match, build obs-studio.
|
||||
REM If the obs-studio directory doesn't exist, build obs-studio.
|
||||
echo Checking for obs-studio build...
|
||||
|
||||
set OBSLatestTagPrePull=0
|
||||
set OBSLatestTagPostPull=0
|
||||
echo Latest tag pre-pull: %OBSLatestTagPrePull%
|
||||
echo Latest tag post-pull: %OBSLatestTagPostPull%
|
||||
|
||||
REM Set up the build flag as undefined.
|
||||
set "BuildOBS="
|
||||
|
||||
REM Check the last tag successfully built by CI.
|
||||
if exist C:\projects\obs-studio-last-tag-built.txt (
|
||||
set /p OBSLastTagBuilt=<C:\projects\obs-studio-last-tag-built.txt
|
||||
) else (
|
||||
set OBSLastTagBuilt=0
|
||||
)
|
||||
|
||||
REM If obs-studio directory exists, run git pull and get the latest tag number.
|
||||
if exist C:\projects\obs-studio\ (
|
||||
echo obs-studio directory exists
|
||||
echo Updating tag info
|
||||
cd C:\projects\obs-studio\
|
||||
git describe --tags --abbrev=0 > C:\projects\latest-obs-studio-tag-pre-pull.txt
|
||||
set /p OBSLatestTagPrePull=<C:\projects\latest-obs-studio-tag-pre-pull.txt
|
||||
git checkout master
|
||||
git pull
|
||||
git describe --tags --abbrev=0 > C:\projects\latest-obs-studio-tag-post-pull.txt
|
||||
set /p OBSLatestTagPostPull=<C:\projects\latest-obs-studio-tag-post-pull.txt
|
||||
set /p OBSLatestTag=<C:\projects\latest-obs-studio-tag-post-pull.txt
|
||||
echo %OBSLatestTagPostPull%> C:\projects\latest-obs-studio-tag.txt
|
||||
)
|
||||
|
||||
REM Check the obs-studio tags for mismatches.
|
||||
REM If a new tag was pulled, set the build flag.
|
||||
if not %OBSLatestTagPrePull%==%OBSLatestTagPostPull% (
|
||||
echo Latest tag pre-pull: %OBSLatestTagPrePull%
|
||||
echo Latest tag post-pull: %OBSLatestTagPostPull%
|
||||
echo Tags do not match. Need to rebuild OBS.
|
||||
set BuildOBS=true
|
||||
)
|
||||
|
||||
REM If the latest git tag doesn't match the last built tag, set the build flag.
|
||||
if not %OBSLatestTagPostPull%==%OBSLastTagBuilt% (
|
||||
echo Last built OBS tag: %OBSLastTagBuilt%
|
||||
echo Latest tag post-pull: %OBSLatestTagPostPull%
|
||||
echo Tags do not match. Need to rebuild OBS.
|
||||
set BuildOBS=true
|
||||
)
|
||||
|
||||
REM If obs-studio directory does not exist, clone the git repo, get the latest
|
||||
REM tag number, and set the build flag.
|
||||
if not exist C:\projects\obs-studio (
|
||||
echo obs-studio directory does not exist
|
||||
git clone --recursive https://github.com/jp9000/obs-studio
|
||||
cd C:\projects\obs-studio\
|
||||
git describe --tags --abbrev=0 > C:\projects\obs-studio-latest-tag.txt
|
||||
set /p OBSLatestTag=<C:\projects\obs-studio-latest-tag.txt
|
||||
set BuildOBS=true
|
||||
)
|
||||
|
||||
REM Some debug info
|
||||
echo:
|
||||
echo Latest tag pre-pull: %OBSLatestTagPrePull%
|
||||
echo Latest tag post-pull: %OBSLatestTagPostPull%
|
||||
echo Latest tag: %OBSLatestTag%
|
||||
echo Last built OBS tag: %OBSLastTagBuilt%
|
||||
|
||||
if defined BuildOBS (
|
||||
echo BuildOBS: true
|
||||
) else (
|
||||
echo BuildOBS: false
|
||||
)
|
||||
echo:
|
||||
|
||||
REM If the build flag is set, build obs-studio.
|
||||
if defined BuildOBS (
|
||||
echo Building obs-studio...
|
||||
echo git checkout %OBSLatestTag%
|
||||
git checkout %OBSLatestTag%
|
||||
echo:
|
||||
echo Removing previous build dirs...
|
||||
if exist build rmdir /s /q C:\projects\obs-studio\build
|
||||
if exist build32 rmdir /s /q C:\projects\obs-studio\build32
|
||||
if exist build64 rmdir /s /q C:\projects\obs-studio\build64
|
||||
echo Making new build dirs...
|
||||
mkdir build
|
||||
mkdir build32
|
||||
mkdir build64
|
||||
echo Running cmake for obs-studio %OBSLatestTag% 32-bit...
|
||||
cd ./build32
|
||||
cmake -G "Visual Studio 12 2013" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
||||
echo:
|
||||
echo:
|
||||
echo Running cmake for obs-studio %OBSLatestTag% 64-bit...
|
||||
cd ../build64
|
||||
cmake -G "Visual Studio 12 2013 Win64" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
||||
echo:
|
||||
echo:
|
||||
echo Building obs-studio %OBSLatestTag% 32-bit ^(Build Config: %build_config%^)...
|
||||
call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build32\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||
echo Building obs-studio %OBSLatestTag% 64-bit ^(Build Config: %build_config%^)...
|
||||
call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build64\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||
cd ..
|
||||
git describe --tags --abbrev=0 > C:\projects\obs-studio-last-tag-built.txt
|
||||
set /p OBSLastTagBuilt=<C:\projects\obs-studio-last-tag-built.txt
|
||||
) else (
|
||||
echo Last OBS tag built is: %OBSLastTagBuilt%
|
||||
echo No need to rebuild OBS.
|
||||
)
|
32
CI/install-dependencies-macos.sh
Executable file
32
CI/install-dependencies-macos.sh
Executable file
@ -0,0 +1,32 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
|
||||
# OBS Studio deps
|
||||
brew update
|
||||
brew install ffmpeg
|
||||
brew install libav
|
||||
|
||||
# qtwebsockets deps
|
||||
# qt latest
|
||||
#brew install qt5
|
||||
|
||||
# qt 5.9.2
|
||||
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/2b121c9a96e58a5da14228630cb71d5bead7137e/Formula/qt.rb
|
||||
|
||||
#echo "Qt path: $(find /usr/local/Cellar/qt5 -d 1 | tail -n 1)"
|
||||
|
||||
# Build obs-studio
|
||||
cd ..
|
||||
git clone --recursive https://github.com/jp9000/obs-studio
|
||||
cd obs-studio
|
||||
git checkout 20.1.0
|
||||
mkdir build && cd build
|
||||
cmake .. \
|
||||
-DCMAKE_PREFIX_PATH=/usr/local/opt/qt/lib/cmake \
|
||||
&& make -j4
|
||||
|
||||
# Packages app
|
||||
cd ..
|
||||
curl -L -O http://s.sudre.free.fr/Software/files/Packages.dmg -f --retry 5 -C -
|
||||
hdiutil attach ./Packages.dmg
|
||||
sudo installer -pkg /Volumes/Packages\ 1.2.2/packages/Packages.pkg -target /
|
@ -1,27 +0,0 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
|
||||
# OBS Studio deps
|
||||
brew update
|
||||
brew install ffmpeg
|
||||
brew install libav
|
||||
|
||||
# qtwebsockets deps
|
||||
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/fdb7c6e960e830b3bf630850c0002c5df9f68ed8/Formula/qt5.rb
|
||||
|
||||
# Build obs-studio
|
||||
cd ..
|
||||
git clone --recursive https://github.com/jp9000/obs-studio
|
||||
cd obs-studio
|
||||
git checkout 19.0.2
|
||||
mkdir build && cd build
|
||||
cmake .. \
|
||||
-DCMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake \
|
||||
&& make -j4
|
||||
|
||||
sudo make install
|
||||
|
||||
# Packages app
|
||||
cd ..
|
||||
curl -L -O https://www.slepin.fr/obs-websocket/ci/Packages.pkg -f --retry 5 -C -
|
||||
sudo installer -pkg ./Packages.pkg -target /
|
@ -48,7 +48,7 @@ apt-get install -y libqt5websockets5-dev
|
||||
cd /root
|
||||
git clone https://github.com/jp9000/obs-studio ./obs-studio
|
||||
cd obs-studio
|
||||
git checkout 19.0.2
|
||||
git checkout 20.1.0
|
||||
mkdir build && cd build
|
||||
cmake -DUNIX_STRUCTURE=1 -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
make -j4
|
||||
|
20
CI/install-setup-qt.cmd
Normal file
20
CI/install-setup-qt.cmd
Normal file
@ -0,0 +1,20 @@
|
||||
@echo off
|
||||
|
||||
REM Set default values to use AppVeyor's built-in Qt.
|
||||
set QTDIR32=C:\Qt\5.7\msvc2013
|
||||
set QTDIR64=C:\Qt\5.7\msvc2013_64
|
||||
set QTCompileVersion=5.7.1
|
||||
|
||||
REM If the AppVeyor cache couldn't recover qt570.7z,
|
||||
REM try to fetch Qt 5.7.0 from slepin.fr.
|
||||
if not exist qt570.7z (
|
||||
curl -kLO https://www.slepin.fr/obs-plugins/deps/qt570.7z -f --retry 5 -C -
|
||||
)
|
||||
|
||||
REM If qt570.7z exists now, use that instead of AppVeyor's built-in Qt.
|
||||
if exist qt570.7z (
|
||||
7z x qt570.7z -o"Qt5.7.0"
|
||||
set QTDIR32=%CD%\Qt5.7.0\msvc2013
|
||||
set QTDIR64=%CD%\Qt5.7.0\msvc2013_64
|
||||
set QTCompileVersion=5.7.0
|
||||
)
|
@ -635,7 +635,7 @@
|
||||
<key>OVERWRITE_PERMISSIONS</key>
|
||||
<false/>
|
||||
<key>VERSION</key>
|
||||
<string>4.0.0</string>
|
||||
<string>4.3.1</string>
|
||||
</dict>
|
||||
<key>PROJECT_COMMENTS</key>
|
||||
<dict>
|
@ -3,10 +3,10 @@
|
||||
set -e
|
||||
|
||||
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 NET_LIB="$QT_PREFIX/lib/QtNetwork.framework/QtNetwork"
|
||||
export WS_LIB="/usr/local/opt/qt/lib/QtWebSockets.framework/QtWebSockets"
|
||||
export NET_LIB="/usr/local/opt/qt/lib/QtNetwork.framework/QtNetwork"
|
||||
|
||||
export GIT_HASH=$(git rev-parse --short HEAD)
|
||||
|
||||
@ -27,32 +27,39 @@ cp $NET_LIB ./build
|
||||
chmod +rw ./build/QtWebSockets ./build/QtNetwork
|
||||
|
||||
echo "-- Modifying QtNetwork"
|
||||
# TODO : put a loop in there
|
||||
install_name_tool \
|
||||
-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
|
||||
|
||||
echo "-- Modifying QtWebSockets"
|
||||
install_name_tool \
|
||||
-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 /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
|
||||
-change $QT_CELLAR_PREFIX/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
|
||||
-change $QT_CELLAR_PREFIX/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
|
||||
./build/QtWebSockets
|
||||
|
||||
echo "-- Modifying obs-websocket.so"
|
||||
install_name_tool \
|
||||
-change "$QT_PREFIX/lib/QtWebSockets.framework/Versions/5/QtWebSockets" @rpath/QtWebSockets \
|
||||
-change "$QT_PREFIX/lib/QtWidgets.framework/Versions/5/QtWidgets" @rpath/QtWidgets \
|
||||
-change "$QT_PREFIX/lib/QtNetwork.framework/Versions/5/QtNetwork" @rpath/QtNetwork \
|
||||
-change "$QT_PREFIX/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/QtWebSockets.framework/Versions/5/QtWebSockets @rpath/QtWebSockets \
|
||||
-change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets \
|
||||
-change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork @rpath/QtNetwork \
|
||||
-change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @rpath/QtGui \
|
||||
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
|
||||
./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
|
||||
|
||||
echo "-- Actual package build"
|
||||
packagesbuild ./CI/osx/obs-websocket.pkgproj
|
||||
packagesbuild ./CI/macos/obs-websocket.pkgproj
|
||||
|
||||
echo "-- Renaming obs-websocket.pkg to $FILENAME"
|
||||
mv ./release/obs-websocket.pkg ./release/$FILENAME
|
@ -17,22 +17,33 @@ add_subdirectory(deps/mbedtls EXCLUDE_FROM_ALL)
|
||||
set(ENABLE_PROGRAMS false)
|
||||
|
||||
set(obs-websocket_SOURCES
|
||||
obs-websocket.cpp
|
||||
WSServer.cpp
|
||||
WSRequestHandler.cpp
|
||||
WSEvents.cpp
|
||||
Config.cpp
|
||||
Utils.cpp
|
||||
forms/settings-dialog.cpp)
|
||||
src/obs-websocket.cpp
|
||||
src/WSServer.cpp
|
||||
src/WSRequestHandler.cpp
|
||||
src/WSRequestHandler_General.cpp
|
||||
src/WSRequestHandler_Profiles.cpp
|
||||
src/WSRequestHandler_Recording.cpp
|
||||
src/WSRequestHandler_ReplayBuffer.cpp
|
||||
src/WSRequestHandler_SceneCollections.cpp
|
||||
src/WSRequestHandler_Scenes.cpp
|
||||
src/WSRequestHandler_SceneItems.cpp
|
||||
src/WSRequestHandler_Sources.cpp
|
||||
src/WSRequestHandler_Streaming.cpp
|
||||
src/WSRequestHandler_StudioMode.cpp
|
||||
src/WSRequestHandler_Transitions.cpp
|
||||
src/WSEvents.cpp
|
||||
src/Config.cpp
|
||||
src/Utils.cpp
|
||||
src/forms/settings-dialog.cpp)
|
||||
|
||||
set(obs-websocket_HEADERS
|
||||
obs-websocket.h
|
||||
WSServer.h
|
||||
WSRequestHandler.h
|
||||
WSEvents.h
|
||||
Config.h
|
||||
Utils.h
|
||||
forms/settings-dialog.h)
|
||||
src/obs-websocket.h
|
||||
src/WSServer.h
|
||||
src/WSRequestHandler.h
|
||||
src/WSEvents.h
|
||||
src/Config.h
|
||||
src/Utils.h
|
||||
src/forms/settings-dialog.h)
|
||||
|
||||
# --- Platform-independent build settings ---
|
||||
add_library(obs-websocket MODULE
|
||||
@ -65,6 +76,18 @@ if(WIN32)
|
||||
message(FATAL_ERROR "Could not find OBS Frontend API\'s library !")
|
||||
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
|
||||
"${OBS_FRONTEND_LIB}")
|
||||
|
||||
@ -84,12 +107,6 @@ if(WIN32)
|
||||
# The "release" folder has a structure similar OBS' one on Windows
|
||||
set(RELEASE_DIR "${PROJECT_SOURCE_DIR}/release")
|
||||
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(ARCH_NAME "64bit")
|
||||
else()
|
||||
set(ARCH_NAME "32bit")
|
||||
endif()
|
||||
|
||||
add_custom_command(TARGET obs-websocket POST_BUILD
|
||||
COMMAND if $<CONFIG:Release>==1 (
|
||||
"${CMAKE_COMMAND}" -E make_directory
|
||||
@ -105,6 +122,23 @@ if(WIN32)
|
||||
"${QTDIR}/bin/Qt5WebSockets.dll"
|
||||
"${QTDIR}/bin/Qt5Network.dll"
|
||||
"${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 ---
|
||||
|
||||
|
199
Config.cpp
199
Config.cpp
@ -1,199 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <mbedtls/base64.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <util/config-file.h>
|
||||
#include <string>
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
#define SECTION_NAME "WebsocketAPI"
|
||||
#define PARAM_ENABLE "ServerEnabled"
|
||||
#define PARAM_PORT "ServerPort"
|
||||
#define PARAM_DEBUG "DebugEnabled"
|
||||
#define PARAM_AUTHREQUIRED "AuthRequired"
|
||||
#define PARAM_SECRET "AuthSecret"
|
||||
#define PARAM_SALT "AuthSalt"
|
||||
|
||||
Config *Config::_instance = new Config();
|
||||
|
||||
Config::Config()
|
||||
{
|
||||
// Default settings
|
||||
ServerEnabled = true;
|
||||
ServerPort = 4444;
|
||||
|
||||
DebugEnabled = false;
|
||||
|
||||
AuthRequired = false;
|
||||
Secret = "";
|
||||
Salt = "";
|
||||
SettingsLoaded = false;
|
||||
|
||||
// OBS Config defaults
|
||||
config_t* obs_config = obs_frontend_get_global_config();
|
||||
if (obs_config)
|
||||
{
|
||||
config_set_default_bool(obs_config,
|
||||
SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||
config_set_default_uint(obs_config,
|
||||
SECTION_NAME, PARAM_PORT, ServerPort);
|
||||
|
||||
config_set_default_bool(obs_config,
|
||||
SECTION_NAME, PARAM_DEBUG, DebugEnabled);
|
||||
|
||||
config_set_default_bool(obs_config,
|
||||
SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
|
||||
config_set_default_string(obs_config,
|
||||
SECTION_NAME, PARAM_SECRET, Secret);
|
||||
config_set_default_string(obs_config,
|
||||
SECTION_NAME, PARAM_SALT, Salt);
|
||||
}
|
||||
|
||||
mbedtls_entropy_init(&entropy);
|
||||
mbedtls_ctr_drbg_init(&rng);
|
||||
mbedtls_ctr_drbg_seed(&rng, mbedtls_entropy_func, &entropy, nullptr, 0);
|
||||
//mbedtls_ctr_drbg_set_prediction_resistance(&rng, MBEDTLS_CTR_DRBG_PR_ON);
|
||||
|
||||
SessionChallenge = GenerateSalt();
|
||||
}
|
||||
|
||||
Config::~Config()
|
||||
{
|
||||
mbedtls_ctr_drbg_free(&rng);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
}
|
||||
|
||||
void Config::Load()
|
||||
{
|
||||
config_t* obs_config = obs_frontend_get_global_config();
|
||||
|
||||
ServerEnabled = config_get_bool(obs_config, SECTION_NAME, PARAM_ENABLE);
|
||||
ServerPort = config_get_uint(obs_config, SECTION_NAME, PARAM_PORT);
|
||||
|
||||
DebugEnabled = config_get_bool(obs_config, SECTION_NAME, PARAM_DEBUG);
|
||||
|
||||
AuthRequired = config_get_bool(obs_config, SECTION_NAME, PARAM_AUTHREQUIRED);
|
||||
Secret = config_get_string(obs_config, SECTION_NAME, PARAM_SECRET);
|
||||
Salt = config_get_string(obs_config, SECTION_NAME, PARAM_SALT);
|
||||
}
|
||||
|
||||
void Config::Save()
|
||||
{
|
||||
config_t* obs_config = obs_frontend_get_global_config();
|
||||
|
||||
config_set_bool(obs_config, SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||
config_set_uint(obs_config, SECTION_NAME, PARAM_PORT, ServerPort);
|
||||
|
||||
config_set_bool(obs_config, SECTION_NAME, PARAM_DEBUG, DebugEnabled);
|
||||
|
||||
config_set_bool(obs_config, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
|
||||
config_set_string(obs_config, SECTION_NAME, PARAM_SECRET, Secret);
|
||||
config_set_string(obs_config, SECTION_NAME, PARAM_SALT, Salt);
|
||||
|
||||
config_save(obs_config);
|
||||
}
|
||||
|
||||
const char* Config::GenerateSalt()
|
||||
{
|
||||
// Generate 32 random chars
|
||||
unsigned char* random_chars = (unsigned char*)bzalloc(32);
|
||||
mbedtls_ctr_drbg_random(&rng, random_chars, 32);
|
||||
|
||||
// Convert the 32 random chars to a base64 string
|
||||
char* salt = (char*)bzalloc(64);
|
||||
size_t salt_bytes;
|
||||
mbedtls_base64_encode(
|
||||
(unsigned char*)salt, 64, &salt_bytes,
|
||||
random_chars, 32);
|
||||
|
||||
bfree(random_chars);
|
||||
return salt;
|
||||
}
|
||||
|
||||
const char* Config::GenerateSecret(const char *password, const char *salt)
|
||||
{
|
||||
// Concatenate the password and the salt
|
||||
std::string passAndSalt = "";
|
||||
passAndSalt += password;
|
||||
passAndSalt += salt;
|
||||
|
||||
// Generate a SHA256 hash of the password
|
||||
unsigned char* challengeHash = (unsigned char*)bzalloc(32);
|
||||
mbedtls_sha256(
|
||||
(unsigned char*)passAndSalt.c_str(), passAndSalt.length(),
|
||||
challengeHash, 0);
|
||||
|
||||
// Encode SHA256 hash to Base64
|
||||
char* challenge = (char*)bzalloc(64);
|
||||
size_t challenge_bytes = 0;
|
||||
mbedtls_base64_encode(
|
||||
(unsigned char*)challenge, 64, &challenge_bytes,
|
||||
challengeHash, 32);
|
||||
|
||||
bfree(challengeHash);
|
||||
return challenge;
|
||||
}
|
||||
|
||||
void Config::SetPassword(const char *password)
|
||||
{
|
||||
const char *new_salt = GenerateSalt();
|
||||
const char *new_challenge = GenerateSecret(password, new_salt);
|
||||
|
||||
this->Salt = new_salt;
|
||||
this->Secret = new_challenge;
|
||||
}
|
||||
|
||||
bool Config::CheckAuth(const char *response)
|
||||
{
|
||||
// Concatenate auth secret with the challenge sent to the user
|
||||
std::string challengeAndResponse = "";
|
||||
challengeAndResponse += this->Secret;
|
||||
challengeAndResponse += this->SessionChallenge;
|
||||
|
||||
// Generate a SHA256 hash of challengeAndResponse
|
||||
unsigned char* hash = (unsigned char*)bzalloc(32);
|
||||
mbedtls_sha256(
|
||||
(unsigned char*)challengeAndResponse.c_str(),
|
||||
challengeAndResponse.length(),
|
||||
hash, 0);
|
||||
|
||||
// Encode the SHA256 hash to Base64
|
||||
char* expected_response = (char*)bzalloc(64);
|
||||
size_t base64_size = 0;
|
||||
mbedtls_base64_encode(
|
||||
(unsigned char*)expected_response, 64, &base64_size,
|
||||
hash, 32);
|
||||
|
||||
bool authSuccess = false;
|
||||
if (strcmp(expected_response, response) == 0) {
|
||||
SessionChallenge = GenerateSalt();
|
||||
authSuccess = true;
|
||||
}
|
||||
|
||||
bfree(hash);
|
||||
bfree(expected_response);
|
||||
return authSuccess;
|
||||
}
|
||||
|
||||
Config* Config::Current()
|
||||
{
|
||||
return _instance;
|
||||
}
|
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
|
||||
|
||||
---
|
34
README.md
34
README.md
@ -21,12 +21,13 @@ It is **highly recommended** to protect obs-websocket with a password against un
|
||||
|
||||
### For developers
|
||||
The server is a typical Websockets server running by default on port 4444 (the port number can be changed in the Settings dialog).
|
||||
The protocol understood by the server is documented in [PROTOCOL.md](PROTOCOL.md).
|
||||
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 :
|
||||
- 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)
|
||||
- Python : [obs-websocket-py](https://github.com/Elektordi/obs-websocket-py) by Guillaume Genty a.k.a Elektordi
|
||||
- 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)
|
||||
- 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` !
|
||||
|
||||
@ -38,14 +39,23 @@ See the [build instructions](BUILDING.md).
|
||||
|
||||
## Special thanks
|
||||
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
|
||||
- [Tobias Frahmer](https://github.com/Frahmer) : German translation
|
||||
- [Genture](https://github.com/Genteure) : Simplified Chinese and Traditional Chinese translations
|
||||
- [Larissa Gabilan](https://github.com/laris151) : Portuguese translation
|
||||
- [Andy Asquelt](https://github.com/asquelt) : Polish translation
|
||||
- [Marcel Haazen](https://github.com/inpothet) : Dutch translation
|
||||
- [Tobias Frahmer](https://github.com/Frahmer) : German localization
|
||||
- [Genture](https://github.com/Genteure) : Simplified Chinese and Traditional Chinese localizations
|
||||
- [Larissa Gabilan](https://github.com/laris151) : Portuguese localization
|
||||
- [Andy Asquelt](https://github.com/asquelt) : Polish localization
|
||||
- [Marcel Haazen](https://github.com/inpothet) : Dutch localization
|
||||
- [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
|
||||
- [Logan S.](https://github.com/lsdaniel) : Code contributions
|
||||
- [RainbowEK](https://github.com/RainbowEK) : Code contributions
|
||||
- [RytoEX](https://github.com/RytoEX) : CI script and code contributions
|
||||
- [Theodore Stoddard](https://github.com/TStod) : Code contributions
|
||||
- [Philip Loche](https://github.com/PicoCentauri) : Code contributions
|
||||
|
||||
And also: special thanks to supporters of the project!
|
||||
|
||||
@ -56,10 +66,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.
|
||||
|
||||
[](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.
|
||||
|
||||
[](http://www.mediaunit.no/)
|
||||
[](http://www.mediaunit.no/)
|
||||
|
522
Utils.cpp
522
Utils.cpp
@ -1,522 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
#include <obs.hpp>
|
||||
#include <QMainWindow>
|
||||
#include <QDir>
|
||||
#include <QUrl>
|
||||
#include "Utils.h"
|
||||
#include "obs-websocket.h"
|
||||
|
||||
Q_DECLARE_METATYPE(OBSScene);
|
||||
|
||||
obs_data_array_t* string_list_to_array(char** strings, char* key)
|
||||
{
|
||||
if (!strings)
|
||||
return obs_data_array_create();
|
||||
|
||||
obs_data_array_t* list = obs_data_array_create();
|
||||
|
||||
char* value = "";
|
||||
for (int i = 0; value != nullptr; i++)
|
||||
{
|
||||
value = strings[i];
|
||||
|
||||
obs_data_t* item = obs_data_create();
|
||||
obs_data_set_string(item, key, value);
|
||||
|
||||
if (value)
|
||||
obs_data_array_push_back(list, item);
|
||||
|
||||
obs_data_release(item);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetSceneItems(obs_source_t* source)
|
||||
{
|
||||
obs_data_array_t* items = obs_data_array_create();
|
||||
obs_scene_t* scene = obs_scene_from_source(source);
|
||||
|
||||
if (!scene)
|
||||
return nullptr;
|
||||
|
||||
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_t* item_data = GetSceneItemData(currentItem);
|
||||
obs_data_array_insert(data, 0, item_data);
|
||||
|
||||
obs_data_release(item_data);
|
||||
return true;
|
||||
}, items);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item)
|
||||
{
|
||||
if (!item)
|
||||
return nullptr;
|
||||
|
||||
vec2 pos;
|
||||
obs_sceneitem_get_pos(item, &pos);
|
||||
|
||||
vec2 scale;
|
||||
obs_sceneitem_get_scale(item, &scale);
|
||||
|
||||
obs_source_t* item_source = obs_sceneitem_get_source(item);
|
||||
float item_width = float(obs_source_get_width(item_source));
|
||||
float item_height = float(obs_source_get_height(item_source));
|
||||
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_string(data, "name",
|
||||
obs_source_get_name(obs_sceneitem_get_source(item)));
|
||||
obs_data_set_string(data, "type",
|
||||
obs_source_get_id(obs_sceneitem_get_source(item)));
|
||||
obs_data_set_double(data, "volume",
|
||||
obs_source_get_volume(obs_sceneitem_get_source(item)));
|
||||
obs_data_set_double(data, "x", pos.x);
|
||||
obs_data_set_double(data, "y", pos.y);
|
||||
obs_data_set_int(data, "source_cx", (int)item_width);
|
||||
obs_data_set_int(data, "source_cy", (int)item_height);
|
||||
obs_data_set_double(data, "cx", item_width* scale.x);
|
||||
obs_data_set_double(data, "cy", item_height* scale.y);
|
||||
obs_data_set_bool(data, "render", obs_sceneitem_visible(item));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, const char* name)
|
||||
{
|
||||
struct current_search {
|
||||
const char* query;
|
||||
obs_sceneitem_t* result;
|
||||
};
|
||||
|
||||
current_search search;
|
||||
search.query = name;
|
||||
search.result = nullptr;
|
||||
|
||||
obs_scene_t* scene = obs_scene_from_source(source);
|
||||
if (scene == nullptr)
|
||||
return nullptr;
|
||||
|
||||
obs_scene_enum_items(scene, [](obs_scene_t* scene, obs_sceneitem_t* currentItem, void* param)
|
||||
{
|
||||
current_search* search = static_cast<current_search* >(param);
|
||||
|
||||
const char* currentItemName =
|
||||
obs_source_get_name(obs_sceneitem_get_source(currentItem));
|
||||
|
||||
if (strcmp(currentItemName, search->query) == 0)
|
||||
{
|
||||
search->result = currentItem;
|
||||
obs_sceneitem_addref(search->result);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, &search);
|
||||
|
||||
return search.result;
|
||||
}
|
||||
|
||||
obs_source_t* Utils::GetTransitionFromName(const char* search_name)
|
||||
{
|
||||
obs_source_t* found_transition = NULL;
|
||||
|
||||
obs_frontend_source_list transition_list = {};
|
||||
obs_frontend_get_transitions(&transition_list);
|
||||
|
||||
for (size_t i = 0; i < transition_list.sources.num; i++)
|
||||
{
|
||||
obs_source_t* transition = transition_list.sources.array[i];
|
||||
|
||||
const char* transition_name = obs_source_get_name(transition);
|
||||
if (strcmp(transition_name, search_name) == 0)
|
||||
{
|
||||
found_transition = transition;
|
||||
obs_source_addref(found_transition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
obs_frontend_source_list_free(&transition_list);
|
||||
|
||||
return found_transition;
|
||||
}
|
||||
|
||||
obs_source_t* Utils::GetSceneFromNameOrCurrent(const char* scene_name)
|
||||
{
|
||||
obs_source_t* scene = nullptr;
|
||||
|
||||
if (!scene_name || !strlen(scene_name))
|
||||
scene = obs_frontend_get_current_scene();
|
||||
else
|
||||
scene = obs_get_source_by_name(scene_name);
|
||||
|
||||
return scene;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetScenes()
|
||||
{
|
||||
obs_frontend_source_list sceneList = {};
|
||||
obs_frontend_get_scenes(&sceneList);
|
||||
|
||||
obs_data_array_t* scenes = obs_data_array_create();
|
||||
for (size_t i = 0; i < sceneList.sources.num; i++)
|
||||
{
|
||||
obs_source_t* scene = sceneList.sources.array[i];
|
||||
|
||||
obs_data_t* scene_data = GetSceneData(scene);
|
||||
obs_data_array_push_back(scenes, scene_data);
|
||||
|
||||
obs_data_release(scene_data);
|
||||
}
|
||||
|
||||
obs_frontend_source_list_free(&sceneList);
|
||||
|
||||
return scenes;
|
||||
}
|
||||
|
||||
obs_data_t* Utils::GetSceneData(obs_source* source)
|
||||
{
|
||||
obs_data_array_t* scene_items = GetSceneItems(source);
|
||||
|
||||
obs_data_t* sceneData = obs_data_create();
|
||||
obs_data_set_string(sceneData, "name", obs_source_get_name(source));
|
||||
obs_data_set_array(sceneData, "sources", scene_items);
|
||||
|
||||
obs_data_array_release(scene_items);
|
||||
return sceneData;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetSceneCollections()
|
||||
{
|
||||
char** scene_collections = obs_frontend_get_scene_collections();
|
||||
obs_data_array_t* list = string_list_to_array(scene_collections, "sc-name");
|
||||
|
||||
bfree(scene_collections);
|
||||
return list;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetProfiles()
|
||||
{
|
||||
char** profiles = obs_frontend_get_profiles();
|
||||
obs_data_array_t* list = string_list_to_array(profiles, "profile-name");
|
||||
|
||||
bfree(profiles);
|
||||
return list;
|
||||
}
|
||||
|
||||
QSpinBox* Utils::GetTransitionDurationControl()
|
||||
{
|
||||
QMainWindow* window = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return window->findChild<QSpinBox*>("transitionDuration");
|
||||
}
|
||||
|
||||
int Utils::GetTransitionDuration()
|
||||
{
|
||||
QSpinBox* control = GetTransitionDurationControl();
|
||||
if (control)
|
||||
return control->value();
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Utils::SetTransitionDuration(int ms)
|
||||
{
|
||||
QSpinBox* control = GetTransitionDurationControl();
|
||||
|
||||
if (control && ms >= 0)
|
||||
control->setValue(ms);
|
||||
}
|
||||
|
||||
bool Utils::SetTransitionByName(const char* transition_name)
|
||||
{
|
||||
obs_source_t* transition = GetTransitionFromName(transition_name);
|
||||
|
||||
if (transition)
|
||||
{
|
||||
obs_frontend_set_current_transition(transition);
|
||||
obs_source_release(transition);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QPushButton* Utils::GetPreviewModeButtonControl()
|
||||
{
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChild<QPushButton*>("modeSwitch");
|
||||
}
|
||||
|
||||
QListWidget* Utils::GetSceneListControl()
|
||||
{
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChild<QListWidget*>("scenes");
|
||||
}
|
||||
|
||||
obs_scene_t* Utils::SceneListItemToScene(QListWidgetItem* item)
|
||||
{
|
||||
if (!item)
|
||||
return nullptr;
|
||||
|
||||
QVariant item_data = item->data(static_cast<int>(Qt::UserRole));
|
||||
return item_data.value<OBSScene>();
|
||||
}
|
||||
|
||||
QLayout* Utils::GetPreviewLayout()
|
||||
{
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChild<QLayout*>("previewLayout");
|
||||
}
|
||||
|
||||
bool Utils::IsPreviewModeActive()
|
||||
{
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
|
||||
// Clue 1 : "Studio Mode" button is toggled on
|
||||
bool buttonToggledOn = GetPreviewModeButtonControl()->isChecked();
|
||||
|
||||
// Clue 2 : Preview layout has more than one item
|
||||
int previewChildCount = GetPreviewLayout()->count();
|
||||
blog(LOG_INFO, "preview layout children count : %d", previewChildCount);
|
||||
|
||||
return buttonToggledOn || (previewChildCount >= 2);
|
||||
}
|
||||
|
||||
void Utils::EnablePreviewMode()
|
||||
{
|
||||
if (!IsPreviewModeActive())
|
||||
GetPreviewModeButtonControl()->click();
|
||||
}
|
||||
|
||||
void Utils::DisablePreviewMode()
|
||||
{
|
||||
if (IsPreviewModeActive())
|
||||
GetPreviewModeButtonControl()->click();
|
||||
}
|
||||
|
||||
void Utils::TogglePreviewMode()
|
||||
{
|
||||
GetPreviewModeButtonControl()->click();
|
||||
}
|
||||
|
||||
obs_scene_t* Utils::GetPreviewScene()
|
||||
{
|
||||
if (IsPreviewModeActive())
|
||||
{
|
||||
QListWidget* sceneList = GetSceneListControl();
|
||||
|
||||
QList<QListWidgetItem*> selected = sceneList->selectedItems();
|
||||
|
||||
// Qt::UserRole == QtUserRole::OBSRef
|
||||
obs_scene_t* scene = Utils::SceneListItemToScene(selected.first());
|
||||
|
||||
obs_scene_addref(scene);
|
||||
return scene;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Utils::SetPreviewScene(const char* name)
|
||||
{
|
||||
if (IsPreviewModeActive())
|
||||
{
|
||||
QListWidget* sceneList = GetSceneListControl();
|
||||
QList<QListWidgetItem*> matchingItems =
|
||||
sceneList->findItems(name, Qt::MatchExactly);
|
||||
|
||||
if (matchingItems.count() > 0)
|
||||
{
|
||||
sceneList->setCurrentItem(matchingItems.first());
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Utils::TransitionToProgram()
|
||||
{
|
||||
if (!IsPreviewModeActive())
|
||||
return;
|
||||
|
||||
// WARNING : if the layout created in OBS' CreateProgramOptions() changes
|
||||
// then this won't work as expected
|
||||
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
|
||||
// The program options widget is the second item in the left-to-right layout
|
||||
QWidget* programOptions = GetPreviewLayout()->itemAt(1)->widget();
|
||||
|
||||
// The "Transition" button lies in the mainButtonLayout
|
||||
// which is the first itemin the program options' layout
|
||||
QLayout* mainButtonLayout = programOptions->layout()->itemAt(1)->layout();
|
||||
QWidget* transitionBtnWidget = mainButtonLayout->itemAt(0)->widget();
|
||||
|
||||
// Try to cast that widget into a button
|
||||
QPushButton* transitionBtn = qobject_cast<QPushButton*>(transitionBtnWidget);
|
||||
|
||||
// Perform a click on that button
|
||||
transitionBtn->click();
|
||||
}
|
||||
|
||||
const char* Utils::OBSVersionString() {
|
||||
uint32_t version = obs_get_version();
|
||||
|
||||
uint8_t major, minor, patch;
|
||||
major = (version >> 24) & 0xFF;
|
||||
minor = (version >> 16) & 0xFF;
|
||||
patch = version & 0xFF;
|
||||
|
||||
char* result = (char*)bmalloc(sizeof(char) * 12);
|
||||
sprintf(result, "%d.%d.%d", major, minor, patch);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QSystemTrayIcon* Utils::GetTrayIcon()
|
||||
{
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChildren<QSystemTrayIcon*>().first();
|
||||
}
|
||||
|
||||
void Utils::SysTrayNotify(QString &text, QSystemTrayIcon::MessageIcon icon, QString title)
|
||||
{
|
||||
if (!QSystemTrayIcon::supportsMessages())
|
||||
return;
|
||||
|
||||
QSystemTrayIcon* trayIcon = GetTrayIcon();
|
||||
if (trayIcon)
|
||||
trayIcon->showMessage(title, text, icon);
|
||||
}
|
||||
|
||||
QString Utils::FormatIPAddress(QHostAddress &addr)
|
||||
{
|
||||
if (addr.protocol() == QAbstractSocket::IPv4Protocol)
|
||||
QString v4addr = addr.toString().replace("::fff:", "");
|
||||
|
||||
return addr.toString();
|
||||
}
|
||||
|
||||
const char* Utils::GetRecordingFolder()
|
||||
{
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
const char* outputMode = config_get_string(profile, "Output", "Mode");
|
||||
|
||||
if (strcmp(outputMode, "Advanced") == 0)
|
||||
{
|
||||
// Advanced mode
|
||||
return config_get_string(profile, "AdvOut", "RecFilePath");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Simple mode
|
||||
return config_get_string(profile, "SimpleOutput", "FilePath");
|
||||
}
|
||||
}
|
||||
|
||||
bool Utils::SetRecordingFolder(const char* path)
|
||||
{
|
||||
if (!QDir(path).exists())
|
||||
return false;
|
||||
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
const char* outputMode = config_get_string(profile, "Output", "Mode");
|
||||
|
||||
if (strcmp(outputMode, "Advanced") == 0)
|
||||
{
|
||||
config_set_string(profile, "AdvOut", "RecFilePath", path);
|
||||
}
|
||||
else
|
||||
{
|
||||
config_set_string(profile, "SimpleOutput", "FilePath", path);
|
||||
}
|
||||
|
||||
config_save(profile);
|
||||
return true;
|
||||
}
|
||||
|
||||
QString* Utils::ParseDataToQueryString(obs_data_t * data)
|
||||
{
|
||||
QString* query = nullptr;
|
||||
if (data)
|
||||
{
|
||||
obs_data_item_t* item = obs_data_first(data);
|
||||
if (item)
|
||||
{
|
||||
query = new QString();
|
||||
bool isFirst = true;
|
||||
do
|
||||
{
|
||||
if (!obs_data_item_has_user_value(item))
|
||||
continue;
|
||||
|
||||
if (!isFirst)
|
||||
query->append('&');
|
||||
else
|
||||
isFirst = false;
|
||||
|
||||
const char* attrName = obs_data_item_get_name(item);
|
||||
query->append(attrName).append("=");
|
||||
switch (obs_data_item_gettype(item))
|
||||
{
|
||||
case OBS_DATA_BOOLEAN:
|
||||
query->append(obs_data_item_get_bool(item)?"true":"false");
|
||||
break;
|
||||
case OBS_DATA_NUMBER:
|
||||
switch (obs_data_item_numtype(item))
|
||||
{
|
||||
case OBS_DATA_NUM_DOUBLE:
|
||||
query->append(QString::number(obs_data_item_get_double(item)));
|
||||
break;
|
||||
case OBS_DATA_NUM_INT:
|
||||
query->append(QString::number(obs_data_item_get_int(item)));
|
||||
break;
|
||||
case OBS_DATA_NUM_INVALID:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case OBS_DATA_STRING:
|
||||
query->append(QUrl::toPercentEncoding(QString(obs_data_item_get_string(item))));
|
||||
break;
|
||||
default:
|
||||
//other types are not supported
|
||||
break;
|
||||
}
|
||||
} while ( obs_data_item_next( &item ) );
|
||||
}
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
83
Utils.h
83
Utils.h
@ -1,83 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#include <QSpinBox>
|
||||
#include <QPushButton>
|
||||
#include <QLayout>
|
||||
#include <QListWidget>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QHostAddress>
|
||||
#include <stdio.h>
|
||||
#include <obs-module.h>
|
||||
#include <util/config-file.h>
|
||||
|
||||
class Utils
|
||||
{
|
||||
public:
|
||||
static obs_data_array_t* GetSceneItems(obs_source_t* source);
|
||||
static obs_data_t* GetSceneItemData(obs_scene_item* item);
|
||||
static obs_sceneitem_t* GetSceneItemFromName(
|
||||
obs_source_t* source, const char* name);
|
||||
static obs_source_t* GetTransitionFromName(const char* search_name);
|
||||
static obs_source_t* GetSceneFromNameOrCurrent(const char* scene_name);
|
||||
|
||||
static obs_data_array_t* GetScenes();
|
||||
static obs_data_t* GetSceneData(obs_source* source);
|
||||
|
||||
static obs_data_array_t* GetSceneCollections();
|
||||
static obs_data_array_t* GetProfiles();
|
||||
|
||||
static QSpinBox* GetTransitionDurationControl();
|
||||
static int GetTransitionDuration();
|
||||
static void SetTransitionDuration(int ms);
|
||||
|
||||
static bool SetTransitionByName(const char* transition_name);
|
||||
|
||||
static QPushButton* GetPreviewModeButtonControl();
|
||||
static QLayout* GetPreviewLayout();
|
||||
static QListWidget* GetSceneListControl();
|
||||
static obs_scene_t* SceneListItemToScene(QListWidgetItem* item);
|
||||
|
||||
static bool IsPreviewModeActive();
|
||||
static void EnablePreviewMode();
|
||||
static void DisablePreviewMode();
|
||||
static void TogglePreviewMode();
|
||||
|
||||
static obs_scene_t* GetPreviewScene();
|
||||
static bool SetPreviewScene(const char* name);
|
||||
static void TransitionToProgram();
|
||||
|
||||
static const char* OBSVersionString();
|
||||
|
||||
static QSystemTrayIcon* GetTrayIcon();
|
||||
static void SysTrayNotify(
|
||||
QString &text,
|
||||
QSystemTrayIcon::MessageIcon n,
|
||||
QString title = QString("obs-websocket"));
|
||||
|
||||
static QString FormatIPAddress(QHostAddress &addr);
|
||||
static const char* GetRecordingFolder();
|
||||
static bool SetRecordingFolder(const char* path);
|
||||
|
||||
static QString* ParseDataToQueryString(obs_data_t * data);
|
||||
};
|
||||
|
||||
#endif // UTILS_H
|
647
WSEvents.cpp
647
WSEvents.cpp
@ -1,647 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2017 Brendan Hagan <https://github.com/haganbmj>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <util/platform.h>
|
||||
#include <QTimer>
|
||||
#include <QPushButton>
|
||||
#include "Config.h"
|
||||
#include "Utils.h"
|
||||
#include "WSEvents.h"
|
||||
#include "obs-websocket.h"
|
||||
|
||||
bool transition_is_cut(obs_source_t* transition)
|
||||
{
|
||||
if (!transition)
|
||||
return false;
|
||||
|
||||
if (obs_source_get_type(transition) == OBS_SOURCE_TYPE_TRANSITION
|
||||
&& strcmp(obs_source_get_id(transition), "cut_transition") == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* ns_to_timestamp(uint64_t ns)
|
||||
{
|
||||
uint64_t ms = ns / (1000 * 1000);
|
||||
uint64_t secs = ms / 1000;
|
||||
uint64_t minutes = secs / 60;
|
||||
|
||||
uint64_t hours_part = minutes / 60;
|
||||
uint64_t minutes_part = minutes % 60;
|
||||
uint64_t secs_part = secs % 60;
|
||||
uint64_t ms_part = ms % 1000;
|
||||
|
||||
char* ts = (char*)bmalloc(64);
|
||||
sprintf(ts, "%02d:%02d:%02d.%03d",
|
||||
hours_part, minutes_part, secs_part, ms_part);
|
||||
|
||||
return ts;
|
||||
}
|
||||
|
||||
WSEvents* WSEvents::Instance = nullptr;
|
||||
|
||||
WSEvents::WSEvents(WSServer* srv)
|
||||
{
|
||||
_srv = srv;
|
||||
obs_frontend_add_event_callback(WSEvents::FrontendEventHandler, this);
|
||||
|
||||
QSpinBox* duration_control = Utils::GetTransitionDurationControl();
|
||||
connect(duration_control, SIGNAL(valueChanged(int)),
|
||||
this, SLOT(TransitionDurationChanged(int)));
|
||||
|
||||
QTimer* statusTimer = new QTimer();
|
||||
connect(statusTimer, SIGNAL(timeout()),
|
||||
this, SLOT(StreamStatus()));
|
||||
statusTimer->start(2000); // equal to frontend's constant BITRATE_UPDATE_SECONDS
|
||||
|
||||
QListWidget* sceneList = Utils::GetSceneListControl();
|
||||
connect(sceneList, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)),
|
||||
this, SLOT(SelectedSceneChanged(QListWidgetItem*, QListWidgetItem*)));
|
||||
|
||||
QPushButton* modeSwitch = Utils::GetPreviewModeButtonControl();
|
||||
connect(modeSwitch, SIGNAL(clicked(bool)), this, SLOT(ModeSwitchClicked(bool)));
|
||||
|
||||
transition_handler = nullptr;
|
||||
scene_handler = nullptr;
|
||||
|
||||
QTimer::singleShot(1000, this, SLOT(deferredInitOperations()));
|
||||
|
||||
_streaming_active = false;
|
||||
_recording_active = false;
|
||||
|
||||
_stream_starttime = 0;
|
||||
_rec_starttime = 0;
|
||||
}
|
||||
|
||||
WSEvents::~WSEvents()
|
||||
{
|
||||
obs_frontend_remove_event_callback(WSEvents::FrontendEventHandler, this);
|
||||
}
|
||||
|
||||
void WSEvents::deferredInitOperations()
|
||||
{
|
||||
obs_source_t* transition = obs_frontend_get_current_transition();
|
||||
connectTransitionSignals(transition);
|
||||
obs_source_release(transition);
|
||||
|
||||
obs_source_t* scene = obs_frontend_get_current_scene();
|
||||
connectSceneSignals(scene);
|
||||
obs_source_release(scene);
|
||||
}
|
||||
|
||||
void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private_data)
|
||||
{
|
||||
WSEvents* owner = static_cast<WSEvents*>(private_data);
|
||||
|
||||
if (!owner->_srv)
|
||||
return;
|
||||
|
||||
// TODO : implement SourceOrderChanged and RepopulateSources
|
||||
|
||||
if (event == OBS_FRONTEND_EVENT_SCENE_CHANGED)
|
||||
{
|
||||
owner->OnSceneChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED)
|
||||
{
|
||||
owner->OnSceneListChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED)
|
||||
{
|
||||
owner->OnSceneCollectionChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED)
|
||||
{
|
||||
owner->OnSceneCollectionListChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_TRANSITION_CHANGED)
|
||||
{
|
||||
owner->OnTransitionChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED)
|
||||
{
|
||||
owner->OnTransitionListChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_PROFILE_CHANGED)
|
||||
{
|
||||
owner->OnProfileChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED)
|
||||
{
|
||||
owner->OnProfileListChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTING)
|
||||
{
|
||||
owner->OnStreamStarting();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTED)
|
||||
{
|
||||
owner->_streaming_active = true;
|
||||
owner->OnStreamStarted();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPING)
|
||||
{
|
||||
owner->OnStreamStopping();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPED)
|
||||
{
|
||||
owner->_streaming_active = false;
|
||||
owner->OnStreamStopped();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTING)
|
||||
{
|
||||
owner->OnRecordingStarting();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTED)
|
||||
{
|
||||
owner->_recording_active = true;
|
||||
owner->OnRecordingStarted();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPING)
|
||||
{
|
||||
owner->OnRecordingStopping();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPED)
|
||||
{
|
||||
owner->_recording_active = false;
|
||||
owner->OnRecordingStopped();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_EXIT)
|
||||
{
|
||||
owner->OnExit();
|
||||
}
|
||||
}
|
||||
|
||||
void WSEvents::broadcastUpdate(const char* updateType, obs_data_t* additionalFields = NULL)
|
||||
{
|
||||
obs_data_t* update = obs_data_create();
|
||||
obs_data_set_string(update, "update-type", updateType);
|
||||
|
||||
const char* ts = nullptr;
|
||||
if (_streaming_active)
|
||||
{
|
||||
ts = ns_to_timestamp(os_gettime_ns() - _stream_starttime);
|
||||
obs_data_set_string(update, "stream-timecode", ts);
|
||||
bfree((void*)ts);
|
||||
}
|
||||
|
||||
if (_recording_active)
|
||||
{
|
||||
ts = ns_to_timestamp(os_gettime_ns() - _rec_starttime);
|
||||
obs_data_set_string(update, "rec-timecode", ts);
|
||||
bfree((void*)ts);
|
||||
}
|
||||
|
||||
if (additionalFields != NULL)
|
||||
obs_data_apply(update, additionalFields);
|
||||
|
||||
const char *json = obs_data_get_json(update);
|
||||
_srv->broadcast(json);
|
||||
if (Config::Current()->DebugEnabled)
|
||||
blog(LOG_DEBUG, "Update << '%s'", json);
|
||||
|
||||
obs_data_release(update);
|
||||
}
|
||||
|
||||
void WSEvents::connectTransitionSignals(obs_source_t* transition)
|
||||
{
|
||||
if (transition_handler)
|
||||
{
|
||||
signal_handler_disconnect(transition_handler,
|
||||
"transition_start", OnTransitionBegin, this);
|
||||
}
|
||||
|
||||
if (!transition_is_cut(transition))
|
||||
{
|
||||
transition_handler = obs_source_get_signal_handler(transition);
|
||||
signal_handler_connect(transition_handler,
|
||||
"transition_start", OnTransitionBegin, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
transition_handler = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void WSEvents::connectSceneSignals(obs_source_t* scene)
|
||||
{
|
||||
if (scene_handler)
|
||||
{
|
||||
signal_handler_disconnect(scene_handler,
|
||||
"reorder", OnSceneReordered, this);
|
||||
signal_handler_disconnect(scene_handler,
|
||||
"item_add", OnSceneItemAdd, this);
|
||||
signal_handler_disconnect(scene_handler,
|
||||
"item_remove", OnSceneItemDelete, this);
|
||||
signal_handler_disconnect(scene_handler,
|
||||
"item_visible", OnSceneItemVisibilityChanged, this);
|
||||
}
|
||||
|
||||
// TODO : connect to all scenes, not just the current one.
|
||||
scene_handler = obs_source_get_signal_handler(scene);
|
||||
signal_handler_connect(scene_handler,
|
||||
"reorder", OnSceneReordered, this);
|
||||
signal_handler_connect(scene_handler,
|
||||
"item_add", OnSceneItemAdd, this);
|
||||
signal_handler_connect(scene_handler,
|
||||
"item_remove", OnSceneItemDelete, this);
|
||||
signal_handler_connect(scene_handler,
|
||||
"item_visible", OnSceneItemVisibilityChanged, this);
|
||||
}
|
||||
|
||||
uint64_t WSEvents::GetStreamingTime()
|
||||
{
|
||||
if (_streaming_active)
|
||||
return (os_gettime_ns() - _stream_starttime);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* WSEvents::GetStreamingTimecode()
|
||||
{
|
||||
return ns_to_timestamp(GetStreamingTime());
|
||||
}
|
||||
|
||||
uint64_t WSEvents::GetRecordingTime()
|
||||
{
|
||||
if (_recording_active)
|
||||
return (os_gettime_ns() - _rec_starttime);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* WSEvents::GetRecordingTimecode()
|
||||
{
|
||||
return ns_to_timestamp(GetRecordingTime());
|
||||
}
|
||||
|
||||
void WSEvents::OnSceneChange()
|
||||
{
|
||||
// Implements an existing update type from bilhamil's OBS Remote
|
||||
obs_data_t* data = obs_data_create();
|
||||
|
||||
obs_source_t* current_scene = obs_frontend_get_current_scene();
|
||||
obs_data_array_t* scene_items = Utils::GetSceneItems(current_scene);
|
||||
connectSceneSignals(current_scene);
|
||||
|
||||
obs_data_set_string(data, "scene-name", obs_source_get_name(current_scene));
|
||||
obs_data_set_array(data, "sources", scene_items);
|
||||
|
||||
broadcastUpdate("SwitchScenes", data);
|
||||
|
||||
obs_data_array_release(scene_items);
|
||||
obs_source_release(current_scene);
|
||||
obs_data_release(data);
|
||||
|
||||
// Dirty fix : OBS blocks signals when swapping scenes in Studio Mode
|
||||
// after transition end, so SelectedSceneChanged is never called...
|
||||
if (Utils::IsPreviewModeActive())
|
||||
{
|
||||
QListWidget* list = Utils::GetSceneListControl();
|
||||
SelectedSceneChanged(list->currentItem(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void WSEvents::OnSceneListChange()
|
||||
{
|
||||
broadcastUpdate("ScenesChanged");
|
||||
}
|
||||
|
||||
void WSEvents::OnSceneCollectionChange()
|
||||
{
|
||||
broadcastUpdate("SceneCollectionChanged");
|
||||
|
||||
scene_handler = nullptr;
|
||||
transition_handler = nullptr;
|
||||
|
||||
OnTransitionListChange();
|
||||
OnTransitionChange();
|
||||
|
||||
OnSceneListChange();
|
||||
OnSceneChange();
|
||||
}
|
||||
|
||||
void WSEvents::OnSceneCollectionListChange()
|
||||
{
|
||||
broadcastUpdate("SceneCollectionListChanged");
|
||||
}
|
||||
|
||||
void WSEvents::OnTransitionChange()
|
||||
{
|
||||
obs_source_t* current_transition = obs_frontend_get_current_transition();
|
||||
connectTransitionSignals(current_transition);
|
||||
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_string(data, "transition-name",
|
||||
obs_source_get_name(current_transition));
|
||||
|
||||
broadcastUpdate("SwitchTransition", data);
|
||||
|
||||
obs_data_release(data);
|
||||
obs_source_release(current_transition);
|
||||
}
|
||||
|
||||
void WSEvents::OnTransitionListChange()
|
||||
{
|
||||
broadcastUpdate("TransitionListChanged");
|
||||
}
|
||||
|
||||
void WSEvents::OnProfileChange()
|
||||
{
|
||||
broadcastUpdate("ProfileChanged");
|
||||
}
|
||||
|
||||
void WSEvents::OnProfileListChange()
|
||||
{
|
||||
broadcastUpdate("ProfileListChanged");
|
||||
}
|
||||
|
||||
void WSEvents::OnStreamStarting()
|
||||
{
|
||||
// Implements an existing update type from bilhamil's OBS Remote
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_bool(data, "preview-only", false);
|
||||
|
||||
broadcastUpdate("StreamStarting", data);
|
||||
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void WSEvents::OnStreamStarted()
|
||||
{
|
||||
// New update type specific to OBS Studio
|
||||
_stream_starttime = os_gettime_ns();
|
||||
_lastBytesSent = 0;
|
||||
broadcastUpdate("StreamStarted");
|
||||
}
|
||||
|
||||
void WSEvents::OnStreamStopping()
|
||||
{
|
||||
// Implements an existing update type from bilhamil's OBS Remote
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_bool(data, "preview-only", false);
|
||||
|
||||
broadcastUpdate("StreamStopping", data);
|
||||
|
||||
obs_data_release(data);
|
||||
}
|
||||
|
||||
void WSEvents::OnStreamStopped()
|
||||
{
|
||||
// New update type specific to OBS Studio
|
||||
_stream_starttime = 0;
|
||||
broadcastUpdate("StreamStopped");
|
||||
}
|
||||
|
||||
void WSEvents::OnRecordingStarting()
|
||||
{
|
||||
// New update type specific to OBS Studio
|
||||
broadcastUpdate("RecordingStarting");
|
||||
}
|
||||
|
||||
void WSEvents::OnRecordingStarted()
|
||||
{
|
||||
// New update type specific to OBS Studio
|
||||
_rec_starttime = os_gettime_ns();
|
||||
broadcastUpdate("RecordingStarted");
|
||||
}
|
||||
|
||||
void WSEvents::OnRecordingStopping()
|
||||
{
|
||||
// New update type specific to OBS Studio
|
||||
broadcastUpdate("RecordingStopping");
|
||||
}
|
||||
|
||||
void WSEvents::OnRecordingStopped()
|
||||
{
|
||||
// New update type specific to OBS Studio
|
||||
_rec_starttime = 0;
|
||||
broadcastUpdate("RecordingStopped");
|
||||
}
|
||||
|
||||
void WSEvents::OnExit()
|
||||
{
|
||||
// New update type specific to OBS Studio
|
||||
broadcastUpdate("Exiting");
|
||||
}
|
||||
|
||||
void WSEvents::StreamStatus()
|
||||
{
|
||||
bool streaming_active = obs_frontend_streaming_active();
|
||||
bool recording_active = obs_frontend_recording_active();
|
||||
|
||||
obs_output_t* stream_output = obs_frontend_get_streaming_output();
|
||||
|
||||
if (!stream_output || !streaming_active)
|
||||
{
|
||||
if (stream_output)
|
||||
obs_output_release(stream_output);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t bytes_sent = obs_output_get_total_bytes(stream_output);
|
||||
uint64_t bytes_sent_time = os_gettime_ns();
|
||||
|
||||
if (bytes_sent < _lastBytesSent)
|
||||
bytes_sent = 0;
|
||||
|
||||
if (bytes_sent == 0)
|
||||
_lastBytesSent = 0;
|
||||
|
||||
uint64_t bytes_between = bytes_sent - _lastBytesSent;
|
||||
double time_passed =
|
||||
double(bytes_sent_time - _lastBytesSentTime) / 1000000000.0;
|
||||
|
||||
uint64_t bytes_per_sec = bytes_between / time_passed;
|
||||
|
||||
_lastBytesSent = bytes_sent;
|
||||
_lastBytesSentTime = bytes_sent_time;
|
||||
|
||||
uint64_t totalStreamTime =
|
||||
(os_gettime_ns() - _stream_starttime) / 1000000000;
|
||||
|
||||
int total_frames = obs_output_get_total_frames(stream_output);
|
||||
int dropped_frames = obs_output_get_frames_dropped(stream_output);
|
||||
|
||||
float strain = obs_output_get_congestion(stream_output);
|
||||
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_bool(data, "streaming", streaming_active);
|
||||
obs_data_set_bool(data, "recording", recording_active);
|
||||
obs_data_set_int(data, "bytes-per-sec", bytes_per_sec);
|
||||
obs_data_set_int(data, "kbits-per-sec", (bytes_per_sec * 8) / 1024);
|
||||
obs_data_set_int(data, "total-stream-time", totalStreamTime);
|
||||
obs_data_set_int(data, "num-total-frames", total_frames);
|
||||
obs_data_set_int(data, "num-dropped-frames", dropped_frames);
|
||||
obs_data_set_double(data, "fps", obs_get_active_fps());
|
||||
obs_data_set_double(data, "strain", strain);
|
||||
obs_data_set_bool(data, "preview-only", false); // Retrocompat with OBSRemote
|
||||
|
||||
broadcastUpdate("StreamStatus", data);
|
||||
|
||||
obs_data_release(data);
|
||||
obs_output_release(stream_output);
|
||||
}
|
||||
|
||||
void WSEvents::TransitionDurationChanged(int ms)
|
||||
{
|
||||
obs_data_t* fields = obs_data_create();
|
||||
obs_data_set_int(fields, "new-duration", ms);
|
||||
|
||||
broadcastUpdate("TransitionDurationChanged", fields);
|
||||
|
||||
obs_data_release(fields);
|
||||
}
|
||||
|
||||
void WSEvents::OnTransitionBegin(void* param, calldata_t* data)
|
||||
{
|
||||
UNUSED_PARAMETER(data);
|
||||
|
||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||
instance->broadcastUpdate("TransitionBegin");
|
||||
|
||||
blog(LOG_INFO, "transition begin");
|
||||
}
|
||||
|
||||
void WSEvents::OnSceneReordered(void* param, calldata_t* data)
|
||||
{
|
||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||
|
||||
obs_scene_t* scene = nullptr;
|
||||
calldata_get_ptr(data, "scene", &scene);
|
||||
|
||||
obs_data_t* fields = obs_data_create();
|
||||
obs_data_set_string(fields, "scene-name",
|
||||
obs_source_get_name(obs_scene_get_source(scene)));
|
||||
|
||||
instance->broadcastUpdate("SourceOrderChanged", fields);
|
||||
|
||||
obs_data_release(fields);
|
||||
}
|
||||
|
||||
void WSEvents::OnSceneItemAdd(void* param, calldata_t* data)
|
||||
{
|
||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||
|
||||
obs_scene_t* scene = nullptr;
|
||||
calldata_get_ptr(data, "scene", &scene);
|
||||
|
||||
obs_sceneitem_t* scene_item = nullptr;
|
||||
calldata_get_ptr(data, "item", &scene_item);
|
||||
|
||||
const char* scene_name =
|
||||
obs_source_get_name(obs_scene_get_source(scene));
|
||||
const char* sceneitem_name =
|
||||
obs_source_get_name(obs_sceneitem_get_source(scene_item));
|
||||
|
||||
obs_data_t* fields = obs_data_create();
|
||||
obs_data_set_string(fields, "scene-name", scene_name);
|
||||
obs_data_set_string(fields, "item-name", sceneitem_name);
|
||||
|
||||
instance->broadcastUpdate("SceneItemAdded", fields);
|
||||
|
||||
obs_data_release(fields);
|
||||
}
|
||||
|
||||
void WSEvents::OnSceneItemDelete(void* param, calldata_t* data)
|
||||
{
|
||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||
|
||||
obs_scene_t* scene = nullptr;
|
||||
calldata_get_ptr(data, "scene", &scene);
|
||||
|
||||
obs_sceneitem_t* scene_item = nullptr;
|
||||
calldata_get_ptr(data, "item", &scene_item);
|
||||
|
||||
const char* scene_name =
|
||||
obs_source_get_name(obs_scene_get_source(scene));
|
||||
const char* sceneitem_name =
|
||||
obs_source_get_name(obs_sceneitem_get_source(scene_item));
|
||||
|
||||
obs_data_t* fields = obs_data_create();
|
||||
obs_data_set_string(fields, "scene-name", scene_name);
|
||||
obs_data_set_string(fields, "item-name", sceneitem_name);
|
||||
|
||||
instance->broadcastUpdate("SceneItemRemoved", fields);
|
||||
|
||||
obs_data_release(fields);
|
||||
}
|
||||
|
||||
void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data)
|
||||
{
|
||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||
|
||||
obs_scene_t* scene = nullptr;
|
||||
calldata_get_ptr(data, "scene", &scene);
|
||||
|
||||
obs_sceneitem_t* scene_item = nullptr;
|
||||
calldata_get_ptr(data, "item", &scene_item);
|
||||
|
||||
bool visible = false;
|
||||
calldata_get_bool(data, "visible", &visible);
|
||||
|
||||
const char* scene_name =
|
||||
obs_source_get_name(obs_scene_get_source(scene));
|
||||
const char* sceneitem_name =
|
||||
obs_source_get_name(obs_sceneitem_get_source(scene_item));
|
||||
|
||||
obs_data_t* fields = obs_data_create();
|
||||
obs_data_set_string(fields, "scene-name", scene_name);
|
||||
obs_data_set_string(fields, "item-name", sceneitem_name);
|
||||
obs_data_set_bool(fields, "item-visible", visible);
|
||||
|
||||
instance->broadcastUpdate("SceneItemVisibilityChanged", fields);
|
||||
|
||||
obs_data_release(fields);
|
||||
}
|
||||
|
||||
void WSEvents::SelectedSceneChanged(QListWidgetItem* current, QListWidgetItem* prev)
|
||||
{
|
||||
if (Utils::IsPreviewModeActive())
|
||||
{
|
||||
obs_scene_t* scene = Utils::SceneListItemToScene(current);
|
||||
if (!scene) return;
|
||||
|
||||
obs_source_t* scene_source = obs_scene_get_source(scene);
|
||||
obs_data_array_t* scene_items = Utils::GetSceneItems(scene_source);
|
||||
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_string(data, "scene-name", obs_source_get_name(scene_source));
|
||||
obs_data_set_array(data, "sources", scene_items);
|
||||
|
||||
broadcastUpdate("PreviewSceneChanged", data);
|
||||
|
||||
obs_data_array_release(scene_items);
|
||||
obs_data_release(data);
|
||||
}
|
||||
}
|
||||
|
||||
void WSEvents::ModeSwitchClicked(bool checked)
|
||||
{
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_bool(data, "new-state", checked);
|
||||
|
||||
broadcastUpdate("StudioModeSwitched", data);
|
||||
|
||||
obs_data_release(data);
|
||||
}
|
101
WSEvents.h
101
WSEvents.h
@ -1,101 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2017 Brendan Hagan <https://github.com/haganbmj>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef WSEVENTS_H
|
||||
#define WSEVENTS_H
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QListWidgetItem>
|
||||
#include "WSServer.h"
|
||||
|
||||
class WSEvents : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WSEvents(WSServer* srv);
|
||||
~WSEvents();
|
||||
static void FrontendEventHandler(
|
||||
enum obs_frontend_event event, void* private_data);
|
||||
void connectTransitionSignals(obs_source_t* transition);
|
||||
void connectSceneSignals(obs_source_t* scene);
|
||||
static WSEvents* Instance;
|
||||
|
||||
uint64_t GetStreamingTime();
|
||||
const char* GetStreamingTimecode();
|
||||
uint64_t GetRecordingTime();
|
||||
const char* GetRecordingTimecode();
|
||||
|
||||
private Q_SLOTS:
|
||||
void deferredInitOperations();
|
||||
void StreamStatus();
|
||||
void TransitionDurationChanged(int ms);
|
||||
void SelectedSceneChanged(
|
||||
QListWidgetItem* current, QListWidgetItem* prev);
|
||||
void ModeSwitchClicked(bool checked);
|
||||
|
||||
private:
|
||||
WSServer* _srv;
|
||||
signal_handler_t* transition_handler;
|
||||
signal_handler_t* scene_handler;
|
||||
|
||||
bool _streaming_active;
|
||||
bool _recording_active;
|
||||
|
||||
uint64_t _stream_starttime;
|
||||
uint64_t _rec_starttime;
|
||||
|
||||
uint64_t _lastBytesSent;
|
||||
uint64_t _lastBytesSentTime;
|
||||
|
||||
void broadcastUpdate(const char* updateType,
|
||||
obs_data_t* additionalFields);
|
||||
|
||||
void OnSceneChange();
|
||||
void OnSceneListChange();
|
||||
void OnSceneCollectionChange();
|
||||
void OnSceneCollectionListChange();
|
||||
|
||||
void OnTransitionChange();
|
||||
void OnTransitionListChange();
|
||||
|
||||
void OnProfileChange();
|
||||
void OnProfileListChange();
|
||||
|
||||
void OnStreamStarting();
|
||||
void OnStreamStarted();
|
||||
void OnStreamStopping();
|
||||
void OnStreamStopped();
|
||||
|
||||
void OnRecordingStarting();
|
||||
void OnRecordingStarted();
|
||||
void OnRecordingStopping();
|
||||
void OnRecordingStopped();
|
||||
|
||||
void OnExit();
|
||||
|
||||
static void OnTransitionBegin(void* param, calldata_t* data);
|
||||
|
||||
static void OnSceneReordered(void* param, calldata_t* data);
|
||||
static void OnSceneItemAdd(void* param, calldata_t* data);
|
||||
static void OnSceneItemDelete(void* param, calldata_t* data);
|
||||
static void OnSceneItemVisibilityChanged(void* param, calldata_t* data);
|
||||
};
|
||||
|
||||
#endif // WSEVENTS_H
|
1711
WSRequestHandler.cpp
1711
WSRequestHandler.cpp
File diff suppressed because it is too large
Load Diff
@ -1,116 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2017 Mikhail Swift <https://github.com/mikhailswift>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef WSREQUESTHANDLER_H
|
||||
#define WSREQUESTHANDLER_H
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
#include <QtWebSockets/QWebSocket>
|
||||
#include <QtWebSockets/QWebSocketServer>
|
||||
|
||||
class WSRequestHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WSRequestHandler(QWebSocket* client);
|
||||
~WSRequestHandler();
|
||||
void processIncomingMessage(QString textMessage);
|
||||
bool hasField(const char* name);
|
||||
|
||||
private:
|
||||
static obs_service_t* _service;
|
||||
QWebSocket* _client;
|
||||
const char* _messageId;
|
||||
const char* _requestType;
|
||||
obs_data_t* data;
|
||||
|
||||
QMap<QString, void(*)(WSRequestHandler*)> messageMap;
|
||||
QSet<QString> authNotRequired;
|
||||
|
||||
void SendOKResponse(obs_data_t* additionalFields = NULL);
|
||||
void SendErrorResponse(const char* errorMessage);
|
||||
void SendResponse(obs_data_t* response);
|
||||
|
||||
static void HandleGetVersion(WSRequestHandler* req);
|
||||
static void HandleGetAuthRequired(WSRequestHandler* req);
|
||||
static void HandleAuthenticate(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetCurrentScene(WSRequestHandler* req);
|
||||
static void HandleGetCurrentScene(WSRequestHandler* req);
|
||||
static void HandleGetSceneList(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetSceneItemRender(WSRequestHandler* req);
|
||||
static void HandleSetSceneItemPosition(WSRequestHandler* req);
|
||||
static void HandleSetSceneItemTransform(WSRequestHandler* req);
|
||||
static void HandleSetSceneItemCrop(WSRequestHandler* req);
|
||||
|
||||
static void HandleGetStreamingStatus(WSRequestHandler* req);
|
||||
static void HandleStartStopStreaming(WSRequestHandler* req);
|
||||
static void HandleStartStopRecording(WSRequestHandler* req);
|
||||
static void HandleStartStreaming(WSRequestHandler* req);
|
||||
static void HandleStopStreaming(WSRequestHandler* req);
|
||||
static void HandleStartRecording(WSRequestHandler* req);
|
||||
static void HandleStopRecording(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetRecordingFolder(WSRequestHandler* req);
|
||||
static void HandleGetRecordingFolder(WSRequestHandler* req);
|
||||
|
||||
static void HandleGetTransitionList(WSRequestHandler* req);
|
||||
static void HandleGetCurrentTransition(WSRequestHandler* req);
|
||||
static void HandleSetCurrentTransition(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetVolume(WSRequestHandler* req);
|
||||
static void HandleGetVolume(WSRequestHandler* req);
|
||||
static void HandleToggleMute(WSRequestHandler* req);
|
||||
static void HandleSetMute(WSRequestHandler* req);
|
||||
static void HandleGetMute(WSRequestHandler* req);
|
||||
static void HandleGetSpecialSources(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetCurrentSceneCollection(WSRequestHandler* req);
|
||||
static void HandleGetCurrentSceneCollection(WSRequestHandler* req);
|
||||
static void HandleListSceneCollections(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetCurrentProfile(WSRequestHandler* req);
|
||||
static void HandleGetCurrentProfile(WSRequestHandler* req);
|
||||
static void HandleListProfiles(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetStreamSettings(WSRequestHandler* req);
|
||||
static void HandleGetStreamSettings(WSRequestHandler* req);
|
||||
static void HandleSaveStreamSettings(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetTransitionDuration(WSRequestHandler* req);
|
||||
static void HandleGetTransitionDuration(WSRequestHandler* req);
|
||||
|
||||
static void HandleGetStudioModeStatus(WSRequestHandler* req);
|
||||
static void HandleGetPreviewScene(WSRequestHandler* req);
|
||||
static void HandleSetPreviewScene(WSRequestHandler* req);
|
||||
static void HandleTransitionToProgram(WSRequestHandler* req);
|
||||
static void HandleEnableStudioMode(WSRequestHandler* req);
|
||||
static void HandleDisableStudioMode(WSRequestHandler* req);
|
||||
static void HandleToggleStudioMode(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetTextGDIPlusProperties(WSRequestHandler* req);
|
||||
static void HandleGetTextGDIPlusProperties(WSRequestHandler* req);
|
||||
static void HandleSetBrowserSourceProperties(WSRequestHandler* req);
|
||||
static void HandleGetBrowserSourceProperties(WSRequestHandler* req);
|
||||
};
|
||||
|
||||
#endif // WSPROTOCOL_H
|
171
WSServer.cpp
171
WSServer.cpp
@ -1,171 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <QtWebSockets/QWebSocket>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
#include "WSServer.h"
|
||||
#include "obs-websocket.h"
|
||||
#include "Config.h"
|
||||
#include "Utils.h"
|
||||
|
||||
QT_USE_NAMESPACE
|
||||
|
||||
WSServer* WSServer::Instance = nullptr;
|
||||
|
||||
WSServer::WSServer(QObject* parent) :
|
||||
QObject(parent),
|
||||
_wsServer(Q_NULLPTR),
|
||||
_clients(),
|
||||
_clMutex(QMutex::Recursive)
|
||||
{
|
||||
_serverThread = new QThread();
|
||||
|
||||
_wsServer = new QWebSocketServer(
|
||||
QStringLiteral("obs-websocket"),
|
||||
QWebSocketServer::NonSecureMode,
|
||||
_serverThread);
|
||||
|
||||
_serverThread->start();
|
||||
}
|
||||
|
||||
WSServer::~WSServer()
|
||||
{
|
||||
Stop();
|
||||
delete _serverThread;
|
||||
}
|
||||
|
||||
void WSServer::Start(quint16 port)
|
||||
{
|
||||
if (port == _wsServer->serverPort())
|
||||
return;
|
||||
|
||||
if(_wsServer->isListening())
|
||||
Stop();
|
||||
|
||||
bool serverStarted = _wsServer->listen(QHostAddress::Any, port);
|
||||
if (serverStarted)
|
||||
{
|
||||
connect(_wsServer, &QWebSocketServer::newConnection,
|
||||
this, &WSServer::onNewConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::Stop()
|
||||
{
|
||||
_clMutex.lock();
|
||||
for(QWebSocket* pClient : _clients) {
|
||||
pClient->close();
|
||||
}
|
||||
_clMutex.unlock();
|
||||
|
||||
_wsServer->close();
|
||||
}
|
||||
|
||||
void WSServer::broadcast(QString message)
|
||||
{
|
||||
_clMutex.lock();
|
||||
|
||||
for(QWebSocket* pClient : _clients) {
|
||||
if (Config::Current()->AuthRequired
|
||||
&& (pClient->property(PROP_AUTHENTICATED).toBool() == false))
|
||||
{
|
||||
// Skip this client if unauthenticated
|
||||
continue;
|
||||
}
|
||||
|
||||
pClient->sendTextMessage(message);
|
||||
}
|
||||
|
||||
_clMutex.unlock();
|
||||
}
|
||||
|
||||
void WSServer::onNewConnection()
|
||||
{
|
||||
QWebSocket* pSocket = _wsServer->nextPendingConnection();
|
||||
|
||||
if (pSocket)
|
||||
{
|
||||
connect(pSocket, &QWebSocket::textMessageReceived,
|
||||
this, &WSServer::textMessageReceived);
|
||||
connect(pSocket, &QWebSocket::disconnected,
|
||||
this, &WSServer::socketDisconnected);
|
||||
pSocket->setProperty(PROP_AUTHENTICATED, false);
|
||||
|
||||
_clMutex.lock();
|
||||
_clients << pSocket;
|
||||
_clMutex.unlock();
|
||||
|
||||
QHostAddress clientAddr = pSocket->peerAddress();
|
||||
QString clientIp = Utils::FormatIPAddress(clientAddr);
|
||||
|
||||
blog(LOG_INFO, "new client connection from %s:%d",
|
||||
clientIp.toUtf8().constData(), pSocket->peerPort());
|
||||
|
||||
QString msg = QString(obs_module_text("OBSWebsocket.ConnectNotify.ClientIP"))
|
||||
+ QString(" ")
|
||||
+ clientAddr.toString();
|
||||
|
||||
Utils::SysTrayNotify(msg,
|
||||
QSystemTrayIcon::Information,
|
||||
QString(obs_module_text("OBSWebsocket.ConnectNotify.Connected")));
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::textMessageReceived(QString message)
|
||||
{
|
||||
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
|
||||
|
||||
if (pSocket)
|
||||
{
|
||||
WSRequestHandler handler(pSocket);
|
||||
handler.processIncomingMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::socketDisconnected()
|
||||
{
|
||||
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
|
||||
|
||||
if (pSocket)
|
||||
{
|
||||
pSocket->setProperty(PROP_AUTHENTICATED, false);
|
||||
|
||||
_clMutex.lock();
|
||||
_clients.removeAll(pSocket);
|
||||
_clMutex.unlock();
|
||||
|
||||
pSocket->deleteLater();
|
||||
|
||||
QHostAddress clientAddr = pSocket->peerAddress();
|
||||
QString clientIp = Utils::FormatIPAddress(clientAddr);
|
||||
|
||||
blog(LOG_INFO, "client %s:%d disconnected",
|
||||
clientIp.toUtf8().constData(), pSocket->peerPort());
|
||||
|
||||
QString msg = QString(obs_module_text("OBSWebsocket.ConnectNotify.ClientIP"))
|
||||
+ QString(" ")
|
||||
+ clientAddr.toString();
|
||||
|
||||
Utils::SysTrayNotify(msg,
|
||||
QSystemTrayIcon::Information,
|
||||
QString(obs_module_text("OBSWebsocket.ConnectNotify.Disconnected")));
|
||||
}
|
||||
}
|
22
appveyor.yml
22
appveyor.yml
@ -6,25 +6,11 @@ install:
|
||||
- cd C:\projects\
|
||||
- if not exist dependencies2013.zip curl -kLO https://obsproject.com/downloads/dependencies2013.zip -f --retry 5 -C -
|
||||
- 7z x dependencies2013.zip -odependencies2013
|
||||
- if not exist qt570.zip curl -kLO https://www.slepin.fr/obs-websocket/ci/qt570.zip -f --retry 5 -C -
|
||||
- 7z x qt570.zip -o"Qt5.7.0"
|
||||
- set DepsPath32=%CD%\dependencies2013\win32
|
||||
- set DepsPath64=%CD%\dependencies2013\win64
|
||||
- set QTDIR32=%CD%\Qt5.7.0\msvc2013
|
||||
- set QTDIR64=%CD%\Qt5.7.0\msvc2013_64
|
||||
- call C:\projects\obs-websocket\CI\install-setup-qt.cmd
|
||||
- set build_config=Release
|
||||
- git clone --recursive https://github.com/jp9000/obs-studio
|
||||
- cd C:\projects\obs-studio\
|
||||
- git checkout 19.0.2
|
||||
- mkdir build
|
||||
- mkdir build32
|
||||
- mkdir build64
|
||||
- cd ./build32
|
||||
- cmake -G "Visual Studio 12 2013" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
||||
- cd ../build64
|
||||
- cmake -G "Visual Studio 12 2013 Win64" -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
||||
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build32\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build64\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
||||
- call C:\projects\obs-websocket\CI\install-build-obs.cmd
|
||||
- cd C:\projects\obs-websocket\
|
||||
- mkdir build32
|
||||
- mkdir build64
|
||||
@ -47,4 +33,6 @@ test: off
|
||||
|
||||
cache:
|
||||
- C:\projects\dependencies2013.zip
|
||||
- C:\projects\qt570.zip
|
||||
- C:\projects\qt570.7z
|
||||
- C:\projects\obs-studio-last-tag-built.txt
|
||||
- C:\projects\obs-studio\
|
||||
|
3
crowdin.yml
Normal file
3
crowdin.yml
Normal file
@ -0,0 +1,3 @@
|
||||
files:
|
||||
- source: /data/locale/en-US.ini
|
||||
translation: /data/locale/%locale%.ini
|
@ -4,3 +4,11 @@ OBSWebsocket.Settings.ServerEnable="Websocket-Server aktivieren"
|
||||
OBSWebsocket.Settings.ServerPort="Server Port"
|
||||
OBSWebsocket.Settings.AuthRequired="Authentifizierung erforderlich"
|
||||
OBSWebsocket.Settings.Password="Passwort"
|
||||
OBSWebsocket.Settings.DebugEnable="Debug-Protokollierung aktivieren"
|
||||
OBSWebsocket.Settings.AlertsEnable="Infobereich-Benachrichtigungen aktivieren"
|
||||
OBSWebsocket.NotifyConnect.Title="Neue WebSocket Verbindung"
|
||||
OBSWebsocket.NotifyConnect.Message="Client %1 verbunden"
|
||||
OBSWebsocket.NotifyDisconnect.Title="WebSocket-Client getrennt"
|
||||
OBSWebsocket.NotifyDisconnect.Message="Client %1 getrennt"
|
||||
OBSWebsocket.Server.StartFailed.Title="WebSocket-Server Fehler"
|
||||
OBSWebsocket.Server.StartFailed.Message="Der WebSocket-Server konnte nicht gestartet werden, mögliche Gründe:\n - TCP Port %1 wird möglicherweise gerade von einem anderen Programm verwendet. Versuchen Sie einen anderen Port in den WebSocket-Server Einstellungen zu setzten oder alle Programme zu beenden, die den Port möglicherweise verwenden.\n - Ein unbekannter Netzwerkfehler ist aufgetreten. Versuchen Sie es erneut mit anderen Einstellungen, einem OBS neustart oder einem System neustart."
|
||||
|
@ -5,6 +5,10 @@ OBSWebsocket.Settings.ServerPort="Server Port"
|
||||
OBSWebsocket.Settings.AuthRequired="Enable authentication"
|
||||
OBSWebsocket.Settings.Password="Password"
|
||||
OBSWebsocket.Settings.DebugEnable="Enable debug logging"
|
||||
OBSWebsocket.ConnectNotify.Connected="New WebSocket connection"
|
||||
OBSWebsocket.ConnectNotify.Disconnected="WebSocket client disconnected"
|
||||
OBSWebsocket.ConnectNotify.ClientIP="Client Address:"
|
||||
OBSWebsocket.Settings.AlertsEnable="Enable System Tray Alerts"
|
||||
OBSWebsocket.NotifyConnect.Title="New WebSocket connection"
|
||||
OBSWebsocket.NotifyConnect.Message="Client %1 connected"
|
||||
OBSWebsocket.NotifyDisconnect.Title="WebSocket client disconnected"
|
||||
OBSWebsocket.NotifyDisconnect.Message="Client %1 disconnected"
|
||||
OBSWebsocket.Server.StartFailed.Title="WebSocket Server failure"
|
||||
OBSWebsocket.Server.StartFailed.Message="The obs-websocket server failed to start, maybe because:\n - TCP port %1 may currently be in use elsewhere on this system, possibly by another application. Try setting a different TCP port in the WebSocket server settings, or stop any application that could be using this port.\n - An unknown network error happened on your system. Try again by changing settings, restarting OBS or restarting your system."
|
14
data/locale/es-ES.ini
Normal file
14
data/locale/es-ES.ini
Normal file
@ -0,0 +1,14 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Configuración del servidor obs-websocket"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Habilitar el servidor Websocket"
|
||||
OBSWebsocket.Settings.ServerPort="Puerto del Servidor"
|
||||
OBSWebsocket.Settings.AuthRequired="Habilitar autenticación"
|
||||
OBSWebsocket.Settings.Password="Contraseña"
|
||||
OBSWebsocket.Settings.DebugEnable="Habilitar registro de depuración"
|
||||
OBSWebsocket.Settings.AlertsEnable="Habilitar alertas de la bandeja de sistema"
|
||||
OBSWebsocket.NotifyConnect.Title="Nueva conexión WebSocket"
|
||||
OBSWebsocket.NotifyConnect.Message="Cliente %1 conectado"
|
||||
OBSWebsocket.NotifyDisconnect.Title="Cliente WebSocket desconectado"
|
||||
OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado"
|
||||
OBSWebsocket.Server.StartFailed.Title="Fallo del servidor WebSocket"
|
||||
OBSWebsocket.Server.StartFailed.Message="El servidor obs-websocket no se pudo iniciar, tal vez porque: \n - el puerto TCP %1 podría estar actualmente en uso en este sistema, posiblemente por otra aplicación. Intente configurar un puerto TCP diferente en la configuración del servidor WebSocket, o detenga cualquier aplicación que pudiese estar utilizando este puerto \n - Un error de red desconocido ha ocurrido en su sistema. Inténtalo de nuevo cambiando la configuración, reiniciando OBS o reiniciando su sistema."
|
@ -1,9 +1,13 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Paramètres du serveur Websocket"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Activer le serveur Websockets"
|
||||
OBSWebsocket.Settings.ServerEnable="Activer le serveur WebSocket"
|
||||
OBSWebsocket.Settings.ServerPort="Port du serveur"
|
||||
OBSWebsocket.Settings.AuthRequired="Activer l'authentification"
|
||||
OBSWebsocket.Settings.Password="Mot de passe"
|
||||
OBSWebsocket.ConnectNotify.Connected="Nouvelle connexion WebSocket"
|
||||
OBSWebsocket.ConnectNotify.Disconnected="Déconnexion WebSocket"
|
||||
OBSWebsocket.ConnectNotify.ClientIP="Adresse du client :"
|
||||
OBSWebsocket.Settings.DebugEnable="Débogage dans le fichier journal"
|
||||
OBSWebsocket.Settings.AlertsEnable="Notifications de connexion/déconnexion"
|
||||
OBSWebsocket.NotifyConnect.Title="Nouvelle connexion WebSocket"
|
||||
OBSWebsocket.NotifyConnect.Message="Le client %1 s'est connecté"
|
||||
OBSWebsocket.NotifyDisconnect.Title="Déconnexion WebSocket"
|
||||
OBSWebsocket.NotifyDisconnect.Message="Le client %1 s'est déconnecté"
|
||||
OBSWebsocket.Server.StartFailed.Title="Impossible de démarrer le serveur WebSocket"
|
||||
OBSWebsocket.Server.StartFailed.Message="Le serveur WebSocket n'a pas pu démarrer, peut-être parce que :\n - Le port TCP %1 est en cours d'utilisation sur ce système, certainement par un autre programme. Essayez un port différent dans les réglages du serveur WebSocket, ou arrêtez tout programme susceptible d'utiliser ce port.\n - Une erreur réseau inconnue est survenue. Essayez à nouveau en modifiant vos réglages, en redémarrant OBS ou en redémarrant votre ordinateur."
|
||||
|
14
data/locale/it-IT.ini
Normal file
14
data/locale/it-IT.ini
Normal file
@ -0,0 +1,14 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Impostazioni del server di WebSocket"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Abilitare il server Websocket"
|
||||
OBSWebsocket.Settings.ServerPort="Porta del server"
|
||||
OBSWebsocket.Settings.AuthRequired="Abilitare l'autenticazione"
|
||||
OBSWebsocket.Settings.Password="Password"
|
||||
OBSWebsocket.Settings.DebugEnable="Attivare la registrazione di debug"
|
||||
OBSWebsocket.Settings.AlertsEnable="Attivare gli avvisi di vassoio di sistema"
|
||||
OBSWebsocket.NotifyConnect.Title="Nuova connessione WebSocket"
|
||||
OBSWebsocket.NotifyConnect.Message="%1 cliente collegato"
|
||||
OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente disconnesso"
|
||||
OBSWebsocket.NotifyDisconnect.Message="%1 cliente disconnesso"
|
||||
OBSWebsocket.Server.StartFailed.Title="Errore del WebSocket Server"
|
||||
OBSWebsocket.Server.StartFailed.Message="Impossibile avviare, forse perché il server di obs-websocket: \n - %1 porta TCP potrebbe essere attualmente in uso altrove su questo sistema, possibilmente da un'altra applicazione. Provare a impostare una porta TCP diversa nelle impostazioni del server di WebSocket, o arrestare tutte le applicazioni che potrebbero utilizzare questa porta. \n - è verificato un errore di rete sconosciuto sul sistema. Riprova modificando le impostazioni, riavviare OBS o riavvio del sistema."
|
8
data/locale/ja-JP.ini
Normal file
8
data/locale/ja-JP.ini
Normal file
@ -0,0 +1,8 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Websocket サーバー設定"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Websocket サーバーを有効にする"
|
||||
OBSWebsocket.Settings.ServerPort="サーバーポート"
|
||||
OBSWebsocket.Settings.AuthRequired="認証を有効にする"
|
||||
OBSWebsocket.Settings.Password="パスワード"
|
||||
OBSWebsocket.Settings.DebugEnable="デバッグログを有効にする"
|
||||
OBSWebsocket.NotifyConnect.Title="新しいWebSocket接続"
|
0
data/locale/ko-KR.ini
Normal file
0
data/locale/ko-KR.ini
Normal file
@ -1,6 +1,14 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Websocket server instellingen"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Activeer Websocket server"
|
||||
OBSWebsocket.Settings.ServerPort="Server Poort"
|
||||
OBSWebsocket.Settings.ServerPort="Serverpoort"
|
||||
OBSWebsocket.Settings.AuthRequired="Activeer authenticatie"
|
||||
OBSWebsocket.Settings.Password="Wachtwoord"
|
||||
OBSWebsocket.Settings.DebugEnable="Activeer debug logs"
|
||||
OBSWebsocket.Settings.AlertsEnable="Systemvak waarschuwingen inschakelen"
|
||||
OBSWebsocket.NotifyConnect.Title="Nieuwe WebSocket verbinding"
|
||||
OBSWebsocket.NotifyConnect.Message="Client %1 verbonden"
|
||||
OBSWebsocket.NotifyDisconnect.Title="WebSocket client connectie verbroken"
|
||||
OBSWebsocket.NotifyDisconnect.Message="Client %1 losgekoppeld"
|
||||
OBSWebsocket.Server.StartFailed.Title="WebSocket Server mislukt"
|
||||
OBSWebsocket.Server.StartFailed.Message="De obs-websocket server kan niet worden gestart, misschien omdat: \n - TCP-poort %1 momenteel wordt gebruikt elders op dit systeem, eventueel door een andere toepassing. Probeer een andere TCP-poort instellen in de WebSocket Server-instellingen of stoppen van elke toepassing die deze poort zouden kunnen gebruiken.\n Een onbekende netwerkfout gebeurde op uw systeem. Probeer het opnieuw door de instellingen wijzigen, OBS herstarten of opnieuw opstarten van uw systeem."
|
||||
|
@ -4,3 +4,11 @@ OBSWebsocket.Settings.ServerEnable="Włącz serwer zdalnego sterowania (Websocke
|
||||
OBSWebsocket.Settings.ServerPort="Port serwera"
|
||||
OBSWebsocket.Settings.AuthRequired="Wymagaj hasła"
|
||||
OBSWebsocket.Settings.Password="Hasło"
|
||||
OBSWebsocket.Settings.DebugEnable="Włącz rejestrowanie debugowania"
|
||||
OBSWebsocket.Settings.AlertsEnable="Włącz powiadomienia o zasobniku systemowym"
|
||||
OBSWebsocket.NotifyConnect.Title="Nowe połączenie WebSocket"
|
||||
OBSWebsocket.NotifyConnect.Message="Klient %1 połączony"
|
||||
OBSWebsocket.NotifyDisconnect.Title="Klient WebSocket odłączony"
|
||||
OBSWebsocket.NotifyDisconnect.Message="Klient %1 połączony"
|
||||
OBSWebsocket.Server.StartFailed.Title="Awaria serwera WebSocket"
|
||||
OBSWebsocket.Server.StartFailed.Message="Nie udało się uruchomić serwera obs-websocket, może a powodu: \n - TCP port %1 może być obecnie używany gdzie indziej w tym systemie, możliwie przez inną aplikację. Spróbuj ustawić inny port TCP w ustawieniach serwera WebSocket, lub zatrzymać dowolną aplikację, która może używać tego portu \n -nieznany błąd sieci wydarzył się w systemie. Spróbuj ponownie, zmieniając ustawienia, ponownie uruchamiając OBS lub ponownie uruchamiając system."
|
||||
|
@ -1,5 +1,4 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Configuraçes do Servidor Websocket"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Habilitar o Servidor Websocket"
|
||||
OBSWebsocket.Settings.ServerPort="Porta do Servidor"
|
||||
OBSWebsocket.Settings.AuthRequired="Autenticação Requerida"
|
||||
|
14
data/locale/pt-PT.ini
Normal file
14
data/locale/pt-PT.ini
Normal file
@ -0,0 +1,14 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Configurações do servidor Websocket"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Habilitar servidor de WebSockets"
|
||||
OBSWebsocket.Settings.ServerPort="Porta do Servidor"
|
||||
OBSWebsocket.Settings.AuthRequired="Activar autenticação"
|
||||
OBSWebsocket.Settings.Password="Palavra passe"
|
||||
OBSWebsocket.Settings.DebugEnable="Habilitar registro de debug"
|
||||
OBSWebsocket.Settings.AlertsEnable="Ativar Alertas da bandeja do sistema"
|
||||
OBSWebsocket.NotifyConnect.Title="Nova conexão WebSocket"
|
||||
OBSWebsocket.NotifyConnect.Message="Cliente %1 conectado"
|
||||
OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente desconectado"
|
||||
OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado"
|
||||
OBSWebsocket.Server.StartFailed.Title="Falha do servidor de WebSocket"
|
||||
OBSWebsocket.Server.StartFailed.Message="O servidor obs-websocket falhou ao iniciar, talvez porque: \n - TCP port %1 pode estar atualmente em uso em outro lugar sobre este sistema, possivelmente por outro aplicativo. Tente definir uma porta TCP diferente nas configurações do servidor de WebSocket, ou parar qualquer aplicativo que poderia estar usando este porto \n - um erro de rede desconhecido aconteceu no seu sistema. Tente novamente alterar configurações, reiniciando OBS ou reiniciando o sistema."
|
14
data/locale/ru-RU.ini
Normal file
14
data/locale/ru-RU.ini
Normal file
@ -0,0 +1,14 @@
|
||||
OBSWebsocket.Menu.SettingsItem="Параметры сервера Websocket"
|
||||
OBSWebsocket.Settings.DialogTitle="obs-websocket"
|
||||
OBSWebsocket.Settings.ServerEnable="Включить сервер Websocket"
|
||||
OBSWebsocket.Settings.ServerPort="Порт сервера"
|
||||
OBSWebsocket.Settings.AuthRequired="Включить аутентификацию"
|
||||
OBSWebsocket.Settings.Password="Пароль"
|
||||
OBSWebsocket.Settings.DebugEnable="Включить ведение журнала отладки"
|
||||
OBSWebsocket.Settings.AlertsEnable="Включить оповещения в системном трее"
|
||||
OBSWebsocket.NotifyConnect.Title="Новое соединение WebSocket"
|
||||
OBSWebsocket.NotifyConnect.Message="Клиент %1 подключен"
|
||||
OBSWebsocket.NotifyDisconnect.Title="Клиент WebSocket отключён"
|
||||
OBSWebsocket.NotifyDisconnect.Message="Клиент %1 отключен"
|
||||
OBSWebsocket.Server.StartFailed.Title="Сбой сервера WebSocket"
|
||||
OBSWebsocket.Server.StartFailed.Message="Сбой запуска сервера obs-websocket. Вероятные причины:\n - Возможно, TCP-порт %1 занят другим приложением в системе. Попробуйте задать другой TCP-порт в настройках сервера WebSocket или закройте приложение, которое может использовать данный порт.\n - Произошла неизвестная сетевая ошибка в системе. Попробуйте снова после изменения настроек, перезапуска OBS или перезапуска системы."
|
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
|
||||
```
|
98
docs/comments.js
Normal file
98
docs/comments.js
Normal file
@ -0,0 +1,98 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
const parseComments = require('parse-comments');
|
||||
const config = require('./config.json');
|
||||
|
||||
/**
|
||||
* 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 = {};
|
||||
let errors = [];
|
||||
|
||||
comments.forEach(comment => {
|
||||
if (typeof comment.api === 'undefined') return;
|
||||
let validationFailures = validateComment(comment);
|
||||
|
||||
if (validationFailures) {
|
||||
errors.push(validationFailures);
|
||||
}
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
throw JSON.stringify(errors, null, 2);
|
||||
}
|
||||
|
||||
return sorted;
|
||||
};
|
||||
|
||||
// Rudimentary validation of documentation content, returns an error object or undefined.
|
||||
const validateComment = comment => {
|
||||
let errors = [];
|
||||
[].concat(comment.params).concat(comment.returns).filter(Boolean).forEach(param => {
|
||||
if (typeof param.name !== 'string' || param.name === '') {
|
||||
errors.push({
|
||||
description: `Invalid param or return value name`,
|
||||
param: param
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof param.type !== 'string' || param.type === '') {
|
||||
errors.push({
|
||||
description: `Invalid param or return value type`,
|
||||
param: param
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (errors.length) {
|
||||
return {
|
||||
errors: errors,
|
||||
fullContext: Object.assign({}, comment)
|
||||
};
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const files = glob.sync(config.srcGlob);
|
||||
const comments = processComments(parseFiles(files));
|
||||
|
||||
if (!fs.existsSync(config.outDirectory)){
|
||||
fs.mkdirSync(config.outDirectory);
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(config.outDirectory, 'comments.json'), JSON.stringify(comments, null, 2));
|
5
docs/config.json
Normal file
5
docs/config.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"srcGlob": "./../src/**/*.@(cpp|h)",
|
||||
"srcTemplate": "./protocol.hbs",
|
||||
"outDirectory": "./generated"
|
||||
}
|
37
docs/docs.js
Normal file
37
docs/docs.js
Normal file
@ -0,0 +1,37 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const toc = require('markdown-toc');
|
||||
const handlebars = require('handlebars');
|
||||
const config = require('./config.json');
|
||||
|
||||
const helpers = require('handlebars-helpers')({
|
||||
handlebars: handlebars
|
||||
});
|
||||
|
||||
// Allows pipe characters to be used within markdown tables.
|
||||
handlebars.registerHelper('depipe', (text) => {
|
||||
return typeof text === 'string' ? text.replace('|', '\\|') : text;
|
||||
});
|
||||
|
||||
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));
|
||||
};
|
||||
|
||||
if (!fs.existsSync(config.outDirectory)){
|
||||
fs.mkdirSync(config.outDirectory);
|
||||
}
|
||||
|
||||
const comments = fs.readFileSync(path.join(config.outDirectory, 'comments.json'), 'utf8');
|
||||
const markdown = generateProtocol(config.srcTemplate, JSON.parse(comments));
|
||||
fs.writeFileSync(path.join(config.outDirectory, 'protocol.md'), markdown);
|
5292
docs/generated/comments.json
Normal file
5292
docs/generated/comments.json
Normal file
File diff suppressed because it is too large
Load Diff
2236
docs/generated/protocol.md
Normal file
2236
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.
|
40
docs/partials/introduction.md
Normal file
40
docs/partials/introduction.md
Normal file
@ -0,0 +1,40 @@
|
||||
# obs-websocket 4.2.1 protocol reference
|
||||
|
||||
**This is the reference for the unreleased obs-websocket 4.2.1. See the list below for older versions.**
|
||||
- [4.2.0 protocol reference](https://github.com/Palakis/obs-websocket/blob/4.2.0/docs/generated/protocol.md)
|
||||
- [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.
|
99
docs/protocol.hbs
Normal file
99
docs/protocol.hbs
Normal file
@ -0,0 +1,99 @@
|
||||
{{#read "partials/introduction.md"}}{{/read}}
|
||||
|
||||
|
||||
|
||||
# Table of Contents
|
||||
<!-- toc -->
|
||||
|
||||
|
||||
|
||||
{{#read "partials/eventsHeader.md"}}{{/read}}
|
||||
|
||||
{{#each events}}
|
||||
## {{capitalizeAll @key}}
|
||||
|
||||
{{#each this}}
|
||||
### {{name}}
|
||||
|
||||
{{#if deprecated}}
|
||||
- **⚠️ Deprecated. {{deprecated}} ⚠️**
|
||||
{{/if}}
|
||||
|
||||
{{#eq since "unreleased"}}
|
||||
- Unreleased
|
||||
{{else}}
|
||||
- Added in v{{since}}
|
||||
{{/eq}}
|
||||
|
||||
{{{description}}}
|
||||
|
||||
**Response Items:**
|
||||
|
||||
{{#if returns.length}}
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
{{#each returns}}
|
||||
| `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} |
|
||||
{{/each}}
|
||||
|
||||
{{else}}
|
||||
_No additional response items._
|
||||
{{/if}}
|
||||
|
||||
---
|
||||
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
|
||||
|
||||
|
||||
{{#read "partials/requestsHeader.md"}}{{/read}}
|
||||
|
||||
{{#each requests}}
|
||||
## {{capitalizeAll @key}}
|
||||
|
||||
{{#each this}}
|
||||
### {{name}}
|
||||
|
||||
{{#if deprecated}}
|
||||
- **⚠️ Deprecated. {{deprecated}} ⚠️**
|
||||
{{/if}}
|
||||
|
||||
{{#eq since "unreleased"}}
|
||||
- Unreleased
|
||||
{{else}}
|
||||
- Added in v{{since}}
|
||||
{{/eq}}
|
||||
|
||||
{{{description}}}
|
||||
|
||||
**Request Fields:**
|
||||
|
||||
{{#if params.length}}
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
{{#each params}}
|
||||
| `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} |
|
||||
{{/each}}
|
||||
|
||||
{{else}}
|
||||
_No specified parameters._
|
||||
{{/if}}
|
||||
|
||||
**Response Items:**
|
||||
|
||||
{{#if returns.length}}
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
{{#each returns}}
|
||||
| `{{name}}` | _{{depipe type}}_ | {{{depipe description}}} |
|
||||
{{/each}}
|
||||
|
||||
{{else}}
|
||||
_No additional response items._
|
||||
{{/if}}
|
||||
|
||||
---
|
||||
|
||||
{{/each}}
|
||||
{{/each}}
|
@ -1,113 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
#include "obs-websocket.h"
|
||||
#include "Config.h"
|
||||
#include "WSServer.h"
|
||||
#include "settings-dialog.h"
|
||||
#include "ui_settings-dialog.h"
|
||||
|
||||
#define CHANGE_ME "changeme"
|
||||
|
||||
SettingsDialog::SettingsDialog(QWidget* parent) :
|
||||
QDialog(parent, Qt::Dialog),
|
||||
ui(new Ui::SettingsDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->authRequired, &QCheckBox::stateChanged,
|
||||
this, &SettingsDialog::AuthCheckboxChanged);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted,
|
||||
this, &SettingsDialog::FormAccepted);
|
||||
|
||||
|
||||
AuthCheckboxChanged();
|
||||
}
|
||||
|
||||
void SettingsDialog::showEvent(QShowEvent* event)
|
||||
{
|
||||
Config* conf = Config::Current();
|
||||
|
||||
ui->serverEnabled->setChecked(conf->ServerEnabled);
|
||||
ui->serverPort->setValue(conf->ServerPort);
|
||||
|
||||
ui->debugEnabled->setChecked(conf->DebugEnabled);
|
||||
|
||||
ui->authRequired->setChecked(conf->AuthRequired);
|
||||
ui->password->setText(CHANGE_ME);
|
||||
}
|
||||
|
||||
void SettingsDialog::ToggleShowHide()
|
||||
{
|
||||
if (!isVisible())
|
||||
setVisible(true);
|
||||
else
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
void SettingsDialog::AuthCheckboxChanged()
|
||||
{
|
||||
if (ui->authRequired->isChecked())
|
||||
ui->password->setEnabled(true);
|
||||
else
|
||||
ui->password->setEnabled(false);
|
||||
}
|
||||
|
||||
void SettingsDialog::FormAccepted()
|
||||
{
|
||||
Config* conf = Config::Current();
|
||||
|
||||
conf->ServerEnabled = ui->serverEnabled->isChecked();
|
||||
conf->ServerPort = ui->serverPort->value();
|
||||
|
||||
conf->DebugEnabled = ui->debugEnabled->isChecked();
|
||||
|
||||
if (ui->authRequired->isChecked())
|
||||
{
|
||||
if (ui->password->text() != CHANGE_ME)
|
||||
{
|
||||
QByteArray pwd = ui->password->text().toUtf8();
|
||||
const char *new_password = pwd;
|
||||
|
||||
conf->SetPassword(new_password);
|
||||
}
|
||||
|
||||
if (strcmp(Config::Current()->Secret, "") != 0)
|
||||
conf->AuthRequired = true;
|
||||
else
|
||||
conf->AuthRequired = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
conf->AuthRequired = false;
|
||||
}
|
||||
|
||||
conf->Save();
|
||||
|
||||
if (conf->ServerEnabled)
|
||||
WSServer::Instance->Start(conf->ServerPort);
|
||||
else
|
||||
WSServer::Instance->Stop();
|
||||
}
|
||||
|
||||
SettingsDialog::~SettingsDialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
@ -2,8 +2,8 @@
|
||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||
|
||||
#define MyAppName "obs-websocket"
|
||||
#define MyAppVersion "4.0.0"
|
||||
#define MyAppPublisher "St<EFBFBD>phane Lepin"
|
||||
#define MyAppVersion "4.3.1"
|
||||
#define MyAppPublisher "Stephane Lepin"
|
||||
#define MyAppURL "http://github.com/Palakis/obs-websocket"
|
||||
|
||||
[Setup]
|
||||
|
@ -1,74 +0,0 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <obs-module.h>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QAction>
|
||||
#include <QMainWindow>
|
||||
#include <QTimer>
|
||||
|
||||
#include "obs-websocket.h"
|
||||
#include "WSServer.h"
|
||||
#include "WSEvents.h"
|
||||
#include "Config.h"
|
||||
#include "forms/settings-dialog.h"
|
||||
|
||||
OBS_DECLARE_MODULE()
|
||||
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
|
||||
|
||||
SettingsDialog *settings_dialog;
|
||||
|
||||
bool obs_module_load(void)
|
||||
{
|
||||
blog(LOG_INFO, "you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION);
|
||||
|
||||
// Core setup
|
||||
Config* config = Config::Current();
|
||||
config->Load();
|
||||
|
||||
WSServer::Instance = new WSServer();
|
||||
WSEvents::Instance = new WSEvents(WSServer::Instance);
|
||||
|
||||
if (config->ServerEnabled)
|
||||
WSServer::Instance->Start(config->ServerPort);
|
||||
|
||||
// UI setup
|
||||
QAction *menu_action = (QAction*)obs_frontend_add_tools_menu_qaction(
|
||||
obs_module_text("OBSWebsocket.Menu.SettingsItem"));
|
||||
|
||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||
QMainWindow* main_window = (QMainWindow*)obs_frontend_get_main_window();
|
||||
settings_dialog = new SettingsDialog(main_window);
|
||||
obs_frontend_pop_ui_translation();
|
||||
|
||||
auto menu_cb = [] {
|
||||
settings_dialog->ToggleShowHide();
|
||||
};
|
||||
menu_action->connect(menu_action, &QAction::triggered, menu_cb);
|
||||
|
||||
// Loading finished
|
||||
blog(LOG_INFO, "module loaded!");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void obs_module_unload()
|
||||
{
|
||||
blog(LOG_INFO, "goodbye!");
|
||||
}
|
||||
|
194
src/Config.cpp
Normal file
194
src/Config.cpp
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <mbedtls/base64.h>
|
||||
#include <mbedtls/sha256.h>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <util/config-file.h>
|
||||
#include <string>
|
||||
|
||||
#define SECTION_NAME "WebsocketAPI"
|
||||
#define PARAM_ENABLE "ServerEnabled"
|
||||
#define PARAM_PORT "ServerPort"
|
||||
#define PARAM_DEBUG "DebugEnabled"
|
||||
#define PARAM_ALERT "AlertsEnabled"
|
||||
#define PARAM_AUTHREQUIRED "AuthRequired"
|
||||
#define PARAM_SECRET "AuthSecret"
|
||||
#define PARAM_SALT "AuthSalt"
|
||||
|
||||
#include "Config.h"
|
||||
#include "Utils.h"
|
||||
|
||||
Config* Config::_instance = new Config();
|
||||
|
||||
Config::Config() :
|
||||
ServerEnabled(true),
|
||||
ServerPort(4444),
|
||||
DebugEnabled(false),
|
||||
AlertsEnabled(true),
|
||||
AuthRequired(false),
|
||||
Secret(""),
|
||||
Salt(""),
|
||||
SettingsLoaded(false)
|
||||
{
|
||||
// OBS Config defaults
|
||||
config_t* obsConfig = obs_frontend_get_global_config();
|
||||
if (obsConfig) {
|
||||
config_set_default_bool(obsConfig,
|
||||
SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||
config_set_default_uint(obsConfig,
|
||||
SECTION_NAME, PARAM_PORT, ServerPort);
|
||||
|
||||
config_set_default_bool(obsConfig,
|
||||
SECTION_NAME, PARAM_DEBUG, DebugEnabled);
|
||||
config_set_default_bool(obsConfig,
|
||||
SECTION_NAME, PARAM_ALERT, AlertsEnabled);
|
||||
|
||||
config_set_default_bool(obsConfig,
|
||||
SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
|
||||
config_set_default_string(obsConfig,
|
||||
SECTION_NAME, PARAM_SECRET, qstring_data_copy(Secret));
|
||||
config_set_default_string(obsConfig,
|
||||
SECTION_NAME, PARAM_SALT, qstring_data_copy(Salt));
|
||||
}
|
||||
|
||||
mbedtls_entropy_init(&entropy);
|
||||
mbedtls_ctr_drbg_init(&rng);
|
||||
mbedtls_ctr_drbg_seed(&rng, mbedtls_entropy_func, &entropy, nullptr, 0);
|
||||
|
||||
SessionChallenge = GenerateSalt();
|
||||
}
|
||||
|
||||
Config::~Config() {
|
||||
mbedtls_ctr_drbg_free(&rng);
|
||||
mbedtls_entropy_free(&entropy);
|
||||
}
|
||||
|
||||
void Config::Load() {
|
||||
config_t* obsConfig = obs_frontend_get_global_config();
|
||||
|
||||
ServerEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ENABLE);
|
||||
ServerPort = config_get_uint(obsConfig, SECTION_NAME, PARAM_PORT);
|
||||
|
||||
DebugEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_DEBUG);
|
||||
AlertsEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ALERT);
|
||||
|
||||
AuthRequired = config_get_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED);
|
||||
Secret = config_get_string(obsConfig, SECTION_NAME, PARAM_SECRET);
|
||||
Salt = config_get_string(obsConfig, SECTION_NAME, PARAM_SALT);
|
||||
}
|
||||
|
||||
void Config::Save() {
|
||||
config_t* obsConfig = obs_frontend_get_global_config();
|
||||
|
||||
config_set_bool(obsConfig, SECTION_NAME, PARAM_ENABLE, ServerEnabled);
|
||||
config_set_uint(obsConfig, SECTION_NAME, PARAM_PORT, ServerPort);
|
||||
|
||||
config_set_bool(obsConfig, SECTION_NAME, PARAM_DEBUG, DebugEnabled);
|
||||
config_set_bool(obsConfig, SECTION_NAME, PARAM_ALERT, AlertsEnabled);
|
||||
|
||||
config_set_bool(obsConfig, SECTION_NAME, PARAM_AUTHREQUIRED, AuthRequired);
|
||||
config_set_string(obsConfig, SECTION_NAME, PARAM_SECRET,
|
||||
qstring_data_copy(Secret));
|
||||
config_set_string(obsConfig, SECTION_NAME, PARAM_SALT,
|
||||
qstring_data_copy(Salt));
|
||||
|
||||
config_save(obsConfig);
|
||||
}
|
||||
|
||||
QString Config::GenerateSalt() {
|
||||
// Generate 32 random chars
|
||||
unsigned char* randomChars = (unsigned char*)bzalloc(32);
|
||||
mbedtls_ctr_drbg_random(&rng, randomChars, 32);
|
||||
|
||||
// Convert the 32 random chars to a base64 string
|
||||
char* salt = (char*)bzalloc(64);
|
||||
size_t saltBytes;
|
||||
mbedtls_base64_encode(
|
||||
(unsigned char*)salt, 64, &saltBytes,
|
||||
randomChars, 32);
|
||||
|
||||
bfree(randomChars);
|
||||
return salt;
|
||||
}
|
||||
|
||||
QString Config::GenerateSecret(QString password, QString salt) {
|
||||
// Concatenate the password and the salt
|
||||
QString passAndSalt = "";
|
||||
passAndSalt += password;
|
||||
passAndSalt += salt;
|
||||
|
||||
// Generate a SHA256 hash of the password
|
||||
unsigned char* challengeHash = (unsigned char*)bzalloc(32);
|
||||
mbedtls_sha256(
|
||||
(unsigned char*)passAndSalt.toUtf8().constData(), passAndSalt.length(),
|
||||
challengeHash, 0);
|
||||
|
||||
// Encode SHA256 hash to Base64
|
||||
char* challenge = (char*)bzalloc(64);
|
||||
size_t challengeBytes = 0;
|
||||
mbedtls_base64_encode(
|
||||
(unsigned char*)challenge, 64, &challengeBytes,
|
||||
challengeHash, 32);
|
||||
|
||||
bfree(challengeHash);
|
||||
return challenge;
|
||||
}
|
||||
|
||||
void Config::SetPassword(QString password) {
|
||||
QString newSalt = GenerateSalt();
|
||||
QString newChallenge = GenerateSecret(password, newSalt);
|
||||
|
||||
this->Salt = newSalt;
|
||||
this->Secret = newChallenge;
|
||||
}
|
||||
|
||||
bool Config::CheckAuth(QString response) {
|
||||
// Concatenate auth secret with the challenge sent to the user
|
||||
QString challengeAndResponse = "";
|
||||
challengeAndResponse += Secret;
|
||||
challengeAndResponse += SessionChallenge;
|
||||
|
||||
// Generate a SHA256 hash of challengeAndResponse
|
||||
unsigned char* hash = (unsigned char*)bzalloc(32);
|
||||
mbedtls_sha256(
|
||||
(unsigned char*)challengeAndResponse.toUtf8().constData(),
|
||||
challengeAndResponse.length(),
|
||||
hash, 0);
|
||||
|
||||
// Encode the SHA256 hash to Base64
|
||||
char* expectedResponse = (char*)bzalloc(64);
|
||||
size_t base64_size = 0;
|
||||
mbedtls_base64_encode(
|
||||
(unsigned char*)expectedResponse, 64, &base64_size,
|
||||
hash, 32);
|
||||
|
||||
bool authSuccess = false;
|
||||
if (response == QString(expectedResponse)) {
|
||||
SessionChallenge = GenerateSalt();
|
||||
authSuccess = true;
|
||||
}
|
||||
|
||||
bfree(hash);
|
||||
bfree(expectedResponse);
|
||||
return authSuccess;
|
||||
}
|
||||
|
||||
Config* Config::Current() {
|
||||
return _instance;
|
||||
}
|
@ -19,40 +19,42 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include <mbedtls/entropy.h>
|
||||
#include <mbedtls/ctr_drbg.h>
|
||||
|
||||
class Config
|
||||
{
|
||||
public:
|
||||
Config();
|
||||
~Config();
|
||||
void Load();
|
||||
void Save();
|
||||
class Config {
|
||||
public:
|
||||
Config();
|
||||
~Config();
|
||||
void Load();
|
||||
void Save();
|
||||
|
||||
void SetPassword(const char *password);
|
||||
bool CheckAuth(const char *userChallenge);
|
||||
const char* GenerateSalt();
|
||||
static const char* GenerateSecret(
|
||||
const char *password, const char *salt);
|
||||
void SetPassword(QString password);
|
||||
bool CheckAuth(QString userChallenge);
|
||||
QString GenerateSalt();
|
||||
static QString GenerateSecret(
|
||||
QString password, QString salt);
|
||||
|
||||
bool ServerEnabled;
|
||||
uint64_t ServerPort;
|
||||
bool ServerEnabled;
|
||||
uint64_t ServerPort;
|
||||
|
||||
bool DebugEnabled;
|
||||
bool DebugEnabled;
|
||||
bool AlertsEnabled;
|
||||
|
||||
bool AuthRequired;
|
||||
const char *Secret;
|
||||
const char *Salt;
|
||||
const char *SessionChallenge;
|
||||
bool SettingsLoaded;
|
||||
bool AuthRequired;
|
||||
QString Secret;
|
||||
QString Salt;
|
||||
QString SessionChallenge;
|
||||
bool SettingsLoaded;
|
||||
|
||||
static Config* Current();
|
||||
static Config* Current();
|
||||
|
||||
private:
|
||||
static Config *_instance;
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_ctr_drbg_context rng;
|
||||
private:
|
||||
static Config* _instance;
|
||||
mbedtls_entropy_context entropy;
|
||||
mbedtls_ctr_drbg_context rng;
|
||||
};
|
||||
|
||||
#endif // CONFIG_H
|
516
src/Utils.cpp
Normal file
516
src/Utils.cpp
Normal file
@ -0,0 +1,516 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QDir>
|
||||
#include <QUrl>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <obs.hpp>
|
||||
#include "obs-websocket.h"
|
||||
|
||||
#include "Utils.h"
|
||||
#include "Config.h"
|
||||
|
||||
Q_DECLARE_METATYPE(OBSScene);
|
||||
|
||||
const char* qstring_data_copy(QString value) {
|
||||
QByteArray stringData = value.toUtf8();
|
||||
const char* constStringData = new const char[stringData.size()]();
|
||||
memcpy((void*)constStringData, stringData.constData(), stringData.size());
|
||||
return constStringData;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::StringListToArray(char** strings, char* key) {
|
||||
if (!strings)
|
||||
return obs_data_array_create();
|
||||
|
||||
obs_data_array_t* list = obs_data_array_create();
|
||||
|
||||
char* value = "";
|
||||
for (int i = 0; value != nullptr; i++) {
|
||||
value = strings[i];
|
||||
|
||||
OBSDataAutoRelease item = obs_data_create();
|
||||
obs_data_set_string(item, key, value);
|
||||
|
||||
if (value)
|
||||
obs_data_array_push_back(list, item);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetSceneItems(obs_source_t* source) {
|
||||
obs_data_array_t* items = obs_data_array_create();
|
||||
OBSScene scene = obs_scene_from_source(source);
|
||||
|
||||
if (!scene)
|
||||
return nullptr;
|
||||
|
||||
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);
|
||||
|
||||
OBSDataAutoRelease itemData = GetSceneItemData(currentItem);
|
||||
obs_data_array_insert(data, 0, itemData);
|
||||
return true;
|
||||
}, items);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) {
|
||||
if (!item)
|
||||
return nullptr;
|
||||
|
||||
vec2 pos;
|
||||
obs_sceneitem_get_pos(item, &pos);
|
||||
|
||||
vec2 scale;
|
||||
obs_sceneitem_get_scale(item, &scale);
|
||||
|
||||
// obs_sceneitem_get_source doesn't increase the refcount
|
||||
OBSSource itemSource = obs_sceneitem_get_source(item);
|
||||
float item_width = float(obs_source_get_width(itemSource));
|
||||
float item_height = float(obs_source_get_height(itemSource));
|
||||
|
||||
obs_data_t* data = obs_data_create();
|
||||
obs_data_set_string(data, "name",
|
||||
obs_source_get_name(obs_sceneitem_get_source(item)));
|
||||
obs_data_set_string(data, "type",
|
||||
obs_source_get_id(obs_sceneitem_get_source(item)));
|
||||
obs_data_set_double(data, "volume",
|
||||
obs_source_get_volume(obs_sceneitem_get_source(item)));
|
||||
obs_data_set_double(data, "x", pos.x);
|
||||
obs_data_set_double(data, "y", pos.y);
|
||||
obs_data_set_int(data, "source_cx", (int)item_width);
|
||||
obs_data_set_int(data, "source_cy", (int)item_height);
|
||||
obs_data_set_double(data, "cx", item_width* scale.x);
|
||||
obs_data_set_double(data, "cy", item_height* scale.y);
|
||||
obs_data_set_bool(data, "render", obs_sceneitem_visible(item));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, QString name) {
|
||||
struct current_search {
|
||||
QString query;
|
||||
obs_sceneitem_t* result;
|
||||
};
|
||||
|
||||
current_search search;
|
||||
search.query = name;
|
||||
search.result = nullptr;
|
||||
|
||||
OBSScene scene = obs_scene_from_source(source);
|
||||
if (!scene)
|
||||
return nullptr;
|
||||
|
||||
obs_scene_enum_items(scene, [](
|
||||
obs_scene_t* scene,
|
||||
obs_sceneitem_t* currentItem,
|
||||
void* param)
|
||||
{
|
||||
current_search* search = static_cast<current_search*>(param);
|
||||
|
||||
QString currentItemName =
|
||||
obs_source_get_name(obs_sceneitem_get_source(currentItem));
|
||||
|
||||
if (currentItemName == search->query) {
|
||||
search->result = currentItem;
|
||||
obs_sceneitem_addref(search->result);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, &search);
|
||||
|
||||
return search.result;
|
||||
}
|
||||
|
||||
bool Utils::IsValidAlignment(const uint32_t alignment) {
|
||||
switch (alignment) {
|
||||
case OBS_ALIGN_CENTER:
|
||||
case OBS_ALIGN_LEFT:
|
||||
case OBS_ALIGN_RIGHT:
|
||||
case OBS_ALIGN_TOP:
|
||||
case OBS_ALIGN_BOTTOM:
|
||||
case OBS_ALIGN_TOP | OBS_ALIGN_LEFT:
|
||||
case OBS_ALIGN_TOP | OBS_ALIGN_RIGHT:
|
||||
case OBS_ALIGN_BOTTOM | OBS_ALIGN_LEFT:
|
||||
case OBS_ALIGN_BOTTOM | OBS_ALIGN_RIGHT: {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
obs_source_t* Utils::GetTransitionFromName(QString searchName) {
|
||||
obs_source_t* foundTransition = nullptr;
|
||||
|
||||
obs_frontend_source_list transition_list = {};
|
||||
obs_frontend_get_transitions(&transition_list);
|
||||
|
||||
for (size_t i = 0; i < transition_list.sources.num; i++) {
|
||||
obs_source_t* transition = transition_list.sources.array[i];
|
||||
QString transitionName = obs_source_get_name(transition);
|
||||
|
||||
if (transitionName == searchName) {
|
||||
foundTransition = transition;
|
||||
obs_source_addref(foundTransition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
obs_frontend_source_list_free(&transition_list);
|
||||
return foundTransition;
|
||||
}
|
||||
|
||||
obs_source_t* Utils::GetSceneFromNameOrCurrent(QString sceneName) {
|
||||
// Both obs_frontend_get_current_scene() and obs_get_source_by_name()
|
||||
// do addref on the return source, so no need to use an OBSSource helper
|
||||
obs_source_t* scene = nullptr;
|
||||
|
||||
if (sceneName.isEmpty() || sceneName.isNull())
|
||||
scene = obs_frontend_get_current_scene();
|
||||
else
|
||||
scene = obs_get_source_by_name(sceneName.toUtf8());
|
||||
|
||||
return scene;
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetScenes() {
|
||||
obs_frontend_source_list sceneList = {};
|
||||
obs_frontend_get_scenes(&sceneList);
|
||||
|
||||
obs_data_array_t* scenes = obs_data_array_create();
|
||||
for (size_t i = 0; i < sceneList.sources.num; i++) {
|
||||
obs_source_t* scene = sceneList.sources.array[i];
|
||||
OBSDataAutoRelease sceneData = GetSceneData(scene);
|
||||
obs_data_array_push_back(scenes, sceneData);
|
||||
}
|
||||
|
||||
obs_frontend_source_list_free(&sceneList);
|
||||
return scenes;
|
||||
}
|
||||
|
||||
obs_data_t* Utils::GetSceneData(obs_source_t* source) {
|
||||
OBSDataArrayAutoRelease sceneItems = GetSceneItems(source);
|
||||
|
||||
obs_data_t* sceneData = obs_data_create();
|
||||
obs_data_set_string(sceneData, "name", obs_source_get_name(source));
|
||||
obs_data_set_array(sceneData, "sources", sceneItems);
|
||||
|
||||
return sceneData;
|
||||
}
|
||||
|
||||
QSpinBox* Utils::GetTransitionDurationControl() {
|
||||
QMainWindow* window = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return window->findChild<QSpinBox*>("transitionDuration");
|
||||
}
|
||||
|
||||
int Utils::GetTransitionDuration() {
|
||||
QSpinBox* control = GetTransitionDurationControl();
|
||||
if (control)
|
||||
return control->value();
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
void Utils::SetTransitionDuration(int ms) {
|
||||
QSpinBox* control = GetTransitionDurationControl();
|
||||
if (control && ms >= 0)
|
||||
control->setValue(ms);
|
||||
}
|
||||
|
||||
bool Utils::SetTransitionByName(QString transitionName) {
|
||||
OBSSourceAutoRelease transition = GetTransitionFromName(transitionName);
|
||||
|
||||
if (transition) {
|
||||
obs_frontend_set_current_transition(transition);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
QPushButton* Utils::GetPreviewModeButtonControl() {
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChild<QPushButton*>("modeSwitch");
|
||||
}
|
||||
|
||||
QListWidget* Utils::GetSceneListControl() {
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChild<QListWidget*>("scenes");
|
||||
}
|
||||
|
||||
obs_scene_t* Utils::SceneListItemToScene(QListWidgetItem* item) {
|
||||
if (!item)
|
||||
return nullptr;
|
||||
|
||||
QVariant itemData = item->data(static_cast<int>(Qt::UserRole));
|
||||
return itemData.value<OBSScene>();
|
||||
}
|
||||
|
||||
QLayout* Utils::GetPreviewLayout() {
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChild<QLayout*>("previewLayout");
|
||||
}
|
||||
|
||||
void Utils::TransitionToProgram() {
|
||||
if (!obs_frontend_preview_program_mode_active())
|
||||
return;
|
||||
|
||||
// WARNING : if the layout created in OBS' CreateProgramOptions() changes
|
||||
// then this won't work as expected
|
||||
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
|
||||
// The program options widget is the second item in the left-to-right layout
|
||||
QWidget* programOptions = GetPreviewLayout()->itemAt(1)->widget();
|
||||
|
||||
// The "Transition" button lies in the mainButtonLayout
|
||||
// which is the first itemin the program options' layout
|
||||
QLayout* mainButtonLayout = programOptions->layout()->itemAt(1)->layout();
|
||||
QWidget* transitionBtnWidget = mainButtonLayout->itemAt(0)->widget();
|
||||
|
||||
// Try to cast that widget into a button
|
||||
QPushButton* transitionBtn = qobject_cast<QPushButton*>(transitionBtnWidget);
|
||||
|
||||
// Perform a click on that button
|
||||
transitionBtn->click();
|
||||
}
|
||||
|
||||
QString Utils::OBSVersionString() {
|
||||
uint32_t version = obs_get_version();
|
||||
|
||||
uint8_t major, minor, patch;
|
||||
major = (version >> 24) & 0xFF;
|
||||
minor = (version >> 16) & 0xFF;
|
||||
patch = version & 0xFF;
|
||||
|
||||
QString result = QString("%1.%2.%3")
|
||||
.arg(major).arg(minor).arg(patch);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QSystemTrayIcon* Utils::GetTrayIcon() {
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChildren<QSystemTrayIcon*>().first();
|
||||
}
|
||||
|
||||
void Utils::SysTrayNotify(QString &text,
|
||||
QSystemTrayIcon::MessageIcon icon, QString title) {
|
||||
if (!Config::Current()->AlertsEnabled || !QSystemTrayIcon::supportsMessages())
|
||||
return;
|
||||
|
||||
QSystemTrayIcon* trayIcon = GetTrayIcon();
|
||||
if (trayIcon)
|
||||
trayIcon->showMessage(title, text, icon);
|
||||
}
|
||||
|
||||
QString Utils::FormatIPAddress(QHostAddress &addr) {
|
||||
QRegExp v4regex("(::ffff:)(((\\d).){3})", Qt::CaseInsensitive);
|
||||
QString addrString = addr.toString();
|
||||
if (addrString.contains(v4regex)) {
|
||||
addrString = QHostAddress(addr.toIPv4Address()).toString();
|
||||
}
|
||||
return addrString;
|
||||
}
|
||||
|
||||
const char* Utils::GetRecordingFolder() {
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
QString outputMode = config_get_string(profile, "Output", "Mode");
|
||||
|
||||
if (outputMode == "Advanced") {
|
||||
// Advanced mode
|
||||
return config_get_string(profile, "AdvOut", "RecFilePath");
|
||||
} else {
|
||||
// Simple mode
|
||||
return config_get_string(profile, "SimpleOutput", "FilePath");
|
||||
}
|
||||
}
|
||||
|
||||
bool Utils::SetRecordingFolder(const char* path) {
|
||||
QDir dir(path);
|
||||
if (!dir.exists())
|
||||
dir.mkpath(".");
|
||||
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
QString outputMode = config_get_string(profile, "Output", "Mode");
|
||||
|
||||
if (outputMode == "Advanced") {
|
||||
config_set_string(profile, "AdvOut", "RecFilePath", path);
|
||||
} else {
|
||||
config_set_string(profile, "SimpleOutput", "FilePath", path);
|
||||
}
|
||||
|
||||
config_save(profile);
|
||||
return true;
|
||||
}
|
||||
|
||||
QString Utils::ParseDataToQueryString(obs_data_t* data) {
|
||||
if (!data)
|
||||
return QString();
|
||||
|
||||
QString query;
|
||||
|
||||
obs_data_item_t* item = obs_data_first(data);
|
||||
if (item) {
|
||||
bool isFirst = true;
|
||||
do {
|
||||
if (!obs_data_item_has_user_value(item))
|
||||
continue;
|
||||
|
||||
if (!isFirst)
|
||||
query += "&";
|
||||
else
|
||||
isFirst = false;
|
||||
|
||||
QString attrName = obs_data_item_get_name(item);
|
||||
query += (attrName + "=");
|
||||
|
||||
switch (obs_data_item_gettype(item)) {
|
||||
case OBS_DATA_BOOLEAN:
|
||||
query += (obs_data_item_get_bool(item) ? "true" : "false");
|
||||
break;
|
||||
|
||||
case OBS_DATA_NUMBER:
|
||||
switch (obs_data_item_numtype(item)) {
|
||||
case OBS_DATA_NUM_DOUBLE:
|
||||
query +=
|
||||
QString::number(obs_data_item_get_double(item));
|
||||
break;
|
||||
case OBS_DATA_NUM_INT:
|
||||
query +=
|
||||
QString::number(obs_data_item_get_int(item));
|
||||
break;
|
||||
case OBS_DATA_NUM_INVALID:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case OBS_DATA_STRING:
|
||||
query +=
|
||||
QUrl::toPercentEncoding(
|
||||
QString(obs_data_item_get_string(item)));
|
||||
break;
|
||||
|
||||
default:
|
||||
//other types are not supported
|
||||
break;
|
||||
}
|
||||
} while (obs_data_item_next(&item));
|
||||
}
|
||||
|
||||
return query;
|
||||
}
|
||||
|
||||
obs_hotkey_t* Utils::FindHotkeyByName(QString name) {
|
||||
struct current_search {
|
||||
QString 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 (hk_name == search->query) {
|
||||
search->result = hotkey;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, &search);
|
||||
|
||||
return search.result;
|
||||
}
|
||||
|
||||
bool Utils::ReplayBufferEnabled() {
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
QString outputMode = config_get_string(profile, "Output", "Mode");
|
||||
|
||||
if (outputMode == "Simple") {
|
||||
return config_get_bool(profile, "SimpleOutput", "RecRB");
|
||||
}
|
||||
else if (outputMode == "Advanced") {
|
||||
return config_get_bool(profile, "AdvOut", "RecRB");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Utils::StartReplayBuffer() {
|
||||
if (obs_frontend_replay_buffer_active())
|
||||
return;
|
||||
|
||||
if (!IsRPHotkeySet()) {
|
||||
obs_output_t* rpOutput = obs_frontend_get_replay_buffer_output();
|
||||
OBSData outputHotkeys = obs_hotkeys_save_output(rpOutput);
|
||||
|
||||
OBSData dummyBinding = obs_data_create();
|
||||
obs_data_set_bool(dummyBinding, "control", true);
|
||||
obs_data_set_bool(dummyBinding, "alt", true);
|
||||
obs_data_set_bool(dummyBinding, "shift", true);
|
||||
obs_data_set_bool(dummyBinding, "command", true);
|
||||
obs_data_set_string(dummyBinding, "key", "OBS_KEY_0");
|
||||
|
||||
OBSDataArray rpSaveHotkey = obs_data_get_array(
|
||||
outputHotkeys, "ReplayBuffer.Save");
|
||||
obs_data_array_push_back(rpSaveHotkey, dummyBinding);
|
||||
|
||||
obs_hotkeys_load_output(rpOutput, outputHotkeys);
|
||||
obs_frontend_replay_buffer_start();
|
||||
|
||||
obs_output_release(rpOutput);
|
||||
}
|
||||
else {
|
||||
obs_frontend_replay_buffer_start();
|
||||
}
|
||||
}
|
||||
|
||||
bool Utils::IsRPHotkeySet() {
|
||||
OBSOutputAutoRelease rpOutput = obs_frontend_get_replay_buffer_output();
|
||||
OBSDataAutoRelease hotkeys = obs_hotkeys_save_output(rpOutput);
|
||||
OBSDataArrayAutoRelease bindings = obs_data_get_array(hotkeys,
|
||||
"ReplayBuffer.Save");
|
||||
|
||||
size_t count = obs_data_array_count(bindings);
|
||||
return (count > 0);
|
||||
}
|
||||
|
||||
const char* Utils::GetFilenameFormatting() {
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
return config_get_string(profile, "Output", "FilenameFormatting");
|
||||
}
|
||||
|
||||
bool Utils::SetFilenameFormatting(const char* filenameFormatting) {
|
||||
config_t* profile = obs_frontend_get_profile_config();
|
||||
config_set_string(profile, "Output", "FilenameFormatting", filenameFormatting);
|
||||
config_save(profile);
|
||||
return true;
|
||||
}
|
89
src/Utils.h
Normal file
89
src/Utils.h
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <QSpinBox>
|
||||
#include <QPushButton>
|
||||
#include <QLayout>
|
||||
#include <QListWidget>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QHostAddress>
|
||||
|
||||
#include <obs.hpp>
|
||||
#include <obs-module.h>
|
||||
#include <util/config-file.h>
|
||||
|
||||
const char* qstring_data_copy(QString value);
|
||||
|
||||
class Utils {
|
||||
public:
|
||||
static obs_data_array_t* StringListToArray(char** strings, char* key);
|
||||
static obs_data_array_t* GetSceneItems(obs_source_t* source);
|
||||
static obs_data_t* GetSceneItemData(obs_sceneitem_t* item);
|
||||
static obs_sceneitem_t* GetSceneItemFromName(
|
||||
obs_source_t* source, QString name);
|
||||
static obs_source_t* GetTransitionFromName(QString transitionName);
|
||||
static obs_source_t* GetSceneFromNameOrCurrent(QString sceneName);
|
||||
|
||||
static bool IsValidAlignment(const uint32_t alignment);
|
||||
|
||||
static obs_data_array_t* GetScenes();
|
||||
static obs_data_t* GetSceneData(obs_source_t* source);
|
||||
|
||||
static obs_data_array_t* GetProfiles();
|
||||
|
||||
static QSpinBox* GetTransitionDurationControl();
|
||||
static int GetTransitionDuration();
|
||||
static void SetTransitionDuration(int ms);
|
||||
|
||||
static bool SetTransitionByName(QString transitionName);
|
||||
|
||||
static QPushButton* GetPreviewModeButtonControl();
|
||||
static QLayout* GetPreviewLayout();
|
||||
static QListWidget* GetSceneListControl();
|
||||
static obs_scene_t* SceneListItemToScene(QListWidgetItem* item);
|
||||
|
||||
static void TransitionToProgram();
|
||||
|
||||
static QString OBSVersionString();
|
||||
|
||||
static QSystemTrayIcon* GetTrayIcon();
|
||||
static void SysTrayNotify(
|
||||
QString &text,
|
||||
QSystemTrayIcon::MessageIcon n,
|
||||
QString title = QString("obs-websocket"));
|
||||
|
||||
static QString FormatIPAddress(QHostAddress &addr);
|
||||
|
||||
static const char* GetRecordingFolder();
|
||||
static bool SetRecordingFolder(const char* path);
|
||||
|
||||
static QString ParseDataToQueryString(obs_data_t* data);
|
||||
static obs_hotkey_t* FindHotkeyByName(QString name);
|
||||
static bool ReplayBufferEnabled();
|
||||
static void StartReplayBuffer();
|
||||
static bool IsRPHotkeySet();
|
||||
static const char* GetFilenameFormatting();
|
||||
static bool SetFilenameFormatting(const char* filenameFormatting);
|
||||
};
|
||||
|
||||
#endif // UTILS_H
|
944
src/WSEvents.cpp
Normal file
944
src/WSEvents.cpp
Normal file
@ -0,0 +1,944 @@
|
||||
/**
|
||||
* obs-websocket
|
||||
* Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
* Copyright (C) 2017 Brendan Hagan <https://github.com/haganbmj>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <util/platform.h>
|
||||
|
||||
#include <QTimer>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "Config.h"
|
||||
#include "Utils.h"
|
||||
#include "WSEvents.h"
|
||||
|
||||
#include "obs-websocket.h"
|
||||
|
||||
bool transitionIsCut(obs_source_t* transition) {
|
||||
if (!transition)
|
||||
return false;
|
||||
|
||||
if (obs_source_get_type(transition) == OBS_SOURCE_TYPE_TRANSITION
|
||||
&& QString(obs_source_get_id(transition)) == "cut_transition") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* nsToTimestamp(uint64_t ns) {
|
||||
uint64_t ms = ns / (1000 * 1000);
|
||||
uint64_t secs = ms / 1000;
|
||||
uint64_t minutes = secs / 60;
|
||||
|
||||
uint64_t hoursPart = minutes / 60;
|
||||
uint64_t minutesPart = minutes % 60;
|
||||
uint64_t secsPart = secs % 60;
|
||||
uint64_t msPart = ms % 1000;
|
||||
|
||||
char* ts = (char*)bmalloc(64);
|
||||
sprintf(ts, "%02d:%02d:%02d.%03d",
|
||||
hoursPart, minutesPart, secsPart, msPart);
|
||||
|
||||
return ts;
|
||||
}
|
||||
|
||||
void* calldata_get_ptr(const calldata_t* data, const char* name) {
|
||||
void* ptr = nullptr;
|
||||
calldata_get_ptr(data, name, &ptr);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
WSEvents* WSEvents::Instance = nullptr;
|
||||
|
||||
WSEvents::WSEvents(WSServer* srv) {
|
||||
_srv = srv;
|
||||
obs_frontend_add_event_callback(WSEvents::FrontendEventHandler, this);
|
||||
|
||||
QSpinBox* durationControl = Utils::GetTransitionDurationControl();
|
||||
connect(durationControl, SIGNAL(valueChanged(int)),
|
||||
this, SLOT(TransitionDurationChanged(int)));
|
||||
|
||||
QTimer* statusTimer = new QTimer();
|
||||
connect(statusTimer, SIGNAL(timeout()),
|
||||
this, SLOT(StreamStatus()));
|
||||
pulse = false;
|
||||
connect(statusTimer, SIGNAL(timeout()),
|
||||
this, SLOT(Heartbeat()));
|
||||
statusTimer->start(2000); // equal to frontend's constant BITRATE_UPDATE_SECONDS
|
||||
|
||||
QListWidget* sceneList = Utils::GetSceneListControl();
|
||||
connect(sceneList, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)),
|
||||
this, SLOT(SelectedSceneChanged(QListWidgetItem*, QListWidgetItem*)));
|
||||
|
||||
currentScene = nullptr;
|
||||
currentTransition = nullptr;
|
||||
|
||||
QTimer::singleShot(1000, this, SLOT(deferredInitOperations()));
|
||||
|
||||
HeartbeatIsActive = false;
|
||||
|
||||
_streamingActive = false;
|
||||
_recordingActive = false;
|
||||
|
||||
_streamStarttime = 0;
|
||||
_recStarttime = 0;
|
||||
}
|
||||
|
||||
WSEvents::~WSEvents() {
|
||||
obs_frontend_remove_event_callback(WSEvents::FrontendEventHandler, this);
|
||||
}
|
||||
|
||||
void WSEvents::deferredInitOperations() {
|
||||
OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
|
||||
connectTransitionSignals(transition);
|
||||
|
||||
OBSSourceAutoRelease scene = obs_frontend_get_current_scene();
|
||||
connectSceneSignals(scene);
|
||||
}
|
||||
|
||||
void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private_data) {
|
||||
WSEvents* owner = static_cast<WSEvents*>(private_data);
|
||||
|
||||
if (!owner->_srv)
|
||||
return;
|
||||
|
||||
if (event == OBS_FRONTEND_EVENT_SCENE_CHANGED) {
|
||||
owner->OnSceneChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED) {
|
||||
owner->OnSceneListChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED) {
|
||||
owner->OnSceneCollectionChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED) {
|
||||
owner->OnSceneCollectionListChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_TRANSITION_CHANGED) {
|
||||
owner->OnTransitionChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED) {
|
||||
owner->OnTransitionListChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_PROFILE_CHANGED) {
|
||||
owner->OnProfileChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED) {
|
||||
owner->OnProfileListChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTING) {
|
||||
owner->OnStreamStarting();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STARTED) {
|
||||
owner->_streamingActive = true;
|
||||
owner->OnStreamStarted();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPING) {
|
||||
owner->OnStreamStopping();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPED) {
|
||||
owner->_streamingActive = false;
|
||||
owner->OnStreamStopped();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTING) {
|
||||
owner->OnRecordingStarting();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTED) {
|
||||
owner->_recordingActive = true;
|
||||
owner->OnRecordingStarted();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPING) {
|
||||
owner->OnRecordingStopping();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPED) {
|
||||
owner->_recordingActive = false;
|
||||
owner->OnRecordingStopped();
|
||||
}
|
||||
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_STUDIO_MODE_ENABLED) {
|
||||
owner->OnStudioModeSwitched(true);
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_STUDIO_MODE_DISABLED) {
|
||||
owner->OnStudioModeSwitched(false);
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_EXIT) {
|
||||
owner->connectSceneSignals(nullptr);
|
||||
owner->connectTransitionSignals(nullptr);
|
||||
owner->OnExit();
|
||||
}
|
||||
}
|
||||
|
||||
void WSEvents::broadcastUpdate(const char* updateType,
|
||||
obs_data_t* additionalFields = nullptr)
|
||||
{
|
||||
OBSDataAutoRelease update = obs_data_create();
|
||||
obs_data_set_string(update, "update-type", updateType);
|
||||
|
||||
const char* ts = nullptr;
|
||||
if (_streamingActive) {
|
||||
ts = nsToTimestamp(os_gettime_ns() - _streamStarttime);
|
||||
obs_data_set_string(update, "stream-timecode", ts);
|
||||
bfree((void*)ts);
|
||||
}
|
||||
|
||||
if (_recordingActive) {
|
||||
ts = nsToTimestamp(os_gettime_ns() - _recStarttime);
|
||||
obs_data_set_string(update, "rec-timecode", ts);
|
||||
bfree((void*)ts);
|
||||
}
|
||||
|
||||
if (additionalFields)
|
||||
obs_data_apply(update, additionalFields);
|
||||
|
||||
QString json = obs_data_get_json(update);
|
||||
_srv->broadcast(json);
|
||||
|
||||
if (Config::Current()->DebugEnabled)
|
||||
blog(LOG_DEBUG, "Update << '%s'", json.toUtf8().constData());
|
||||
}
|
||||
|
||||
void WSEvents::connectTransitionSignals(obs_source_t* transition) {
|
||||
signal_handler_t* sh = nullptr;
|
||||
|
||||
if (currentTransition) {
|
||||
sh = obs_source_get_signal_handler(currentTransition);
|
||||
signal_handler_disconnect(sh,
|
||||
"transition_start", OnTransitionBegin, this);
|
||||
}
|
||||
|
||||
currentTransition = transition;
|
||||
|
||||
if (currentTransition) {
|
||||
if (!transitionIsCut(transition)) {
|
||||
currentTransition = transition;
|
||||
|
||||
sh = obs_source_get_signal_handler(currentTransition);
|
||||
signal_handler_connect(sh,
|
||||
"transition_start", OnTransitionBegin, this);
|
||||
}
|
||||
else {
|
||||
currentTransition = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WSEvents::connectSceneSignals(obs_source_t* scene) {
|
||||
signal_handler_t* sh = nullptr;
|
||||
|
||||
if (currentScene) {
|
||||
sh = obs_source_get_signal_handler(currentScene);
|
||||
|
||||
signal_handler_disconnect(sh,
|
||||
"reorder", OnSceneReordered, this);
|
||||
signal_handler_disconnect(sh,
|
||||
"item_add", OnSceneItemAdd, this);
|
||||
signal_handler_disconnect(sh,
|
||||
"item_remove", OnSceneItemDelete, this);
|
||||
signal_handler_disconnect(sh,
|
||||
"item_visible", OnSceneItemVisibilityChanged, this);
|
||||
}
|
||||
|
||||
currentScene = scene;
|
||||
|
||||
if (currentScene) {
|
||||
// TODO : connect to all scenes, not just the current one.
|
||||
sh = obs_source_get_signal_handler(currentScene);
|
||||
signal_handler_connect(sh,
|
||||
"reorder", OnSceneReordered, this);
|
||||
signal_handler_connect(sh,
|
||||
"item_add", OnSceneItemAdd, this);
|
||||
signal_handler_connect(sh,
|
||||
"item_remove", OnSceneItemDelete, this);
|
||||
signal_handler_connect(sh,
|
||||
"item_visible", OnSceneItemVisibilityChanged, this);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t WSEvents::GetStreamingTime() {
|
||||
if (_streamingActive)
|
||||
return (os_gettime_ns() - _streamStarttime);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* WSEvents::GetStreamingTimecode() {
|
||||
return nsToTimestamp(GetStreamingTime());
|
||||
}
|
||||
|
||||
uint64_t WSEvents::GetRecordingTime() {
|
||||
if (_recordingActive)
|
||||
return (os_gettime_ns() - _recStarttime);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char* WSEvents::GetRecordingTimecode() {
|
||||
return nsToTimestamp(GetRecordingTime());
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates a scene change.
|
||||
*
|
||||
* @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() {
|
||||
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
|
||||
OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(currentScene);
|
||||
connectSceneSignals(currentScene);
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_string(data, "scene-name", obs_source_get_name(currentScene));
|
||||
obs_data_set_array(data, "sources", sceneItems);
|
||||
|
||||
broadcastUpdate("SwitchScenes", data);
|
||||
|
||||
// Dirty fix : OBS blocks signals when swapping scenes in Studio Mode
|
||||
// after transition end, so SelectedSceneChanged is never called...
|
||||
if (obs_frontend_preview_program_mode_active()) {
|
||||
QListWidget* list = Utils::GetSceneListControl();
|
||||
SelectedSceneChanged(list->currentItem(), nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
|
||||
currentScene = nullptr;
|
||||
currentTransition = nullptr;
|
||||
|
||||
OnTransitionListChange();
|
||||
OnTransitionChange();
|
||||
|
||||
OnSceneListChange();
|
||||
OnSceneChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
|
||||
connectTransitionSignals(currentTransition);
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_string(data, "transition-name",
|
||||
obs_source_get_name(currentTransition));
|
||||
|
||||
broadcastUpdate("SwitchTransition", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
|
||||
/**
|
||||
* A request to start streaming has been issued.
|
||||
*
|
||||
* @return {boolean} `preview-only` Always false (retrocompatibility).
|
||||
*
|
||||
* @api events
|
||||
* @name StreamStarting
|
||||
* @category streaming
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnStreamStarting() {
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_bool(data, "preview-only", false);
|
||||
|
||||
broadcastUpdate("StreamStarting", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streaming started successfully.
|
||||
*
|
||||
* @api events
|
||||
* @name StreamStarted
|
||||
* @category streaming
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnStreamStarted() {
|
||||
_streamStarttime = os_gettime_ns();
|
||||
_lastBytesSent = 0;
|
||||
broadcastUpdate("StreamStarted");
|
||||
}
|
||||
|
||||
/**
|
||||
* A request to stop streaming has been issued.
|
||||
*
|
||||
* @return {boolean} `preview-only` Always false (retrocompatibility).
|
||||
*
|
||||
* @api events
|
||||
* @name StreamStopping
|
||||
* @category streaming
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnStreamStopping() {
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_bool(data, "preview-only", false);
|
||||
|
||||
broadcastUpdate("StreamStopping", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streaming stopped successfully.
|
||||
*
|
||||
* @api events
|
||||
* @name StreamStopped
|
||||
* @category streaming
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnStreamStopped() {
|
||||
_streamStarttime = 0;
|
||||
broadcastUpdate("StreamStopped");
|
||||
}
|
||||
|
||||
/**
|
||||
* A request to start recording has been issued.
|
||||
*
|
||||
* @api events
|
||||
* @name RecordingStarting
|
||||
* @category recording
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnRecordingStarting() {
|
||||
broadcastUpdate("RecordingStarting");
|
||||
}
|
||||
|
||||
/**
|
||||
* Recording started successfully.
|
||||
*
|
||||
* @api events
|
||||
* @name RecordingStarted
|
||||
* @category recording
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnRecordingStarted() {
|
||||
_recStarttime = os_gettime_ns();
|
||||
broadcastUpdate("RecordingStarted");
|
||||
}
|
||||
|
||||
/**
|
||||
* A request to stop recording has been issued.
|
||||
*
|
||||
* @api events
|
||||
* @name RecordingStopping
|
||||
* @category recording
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnRecordingStopping() {
|
||||
broadcastUpdate("RecordingStopping");
|
||||
}
|
||||
|
||||
/**
|
||||
* Recording stopped successfully.
|
||||
*
|
||||
* @api events
|
||||
* @name RecordingStopped
|
||||
* @category recording
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnRecordingStopped() {
|
||||
_recStarttime = 0;
|
||||
broadcastUpdate("RecordingStopped");
|
||||
}
|
||||
|
||||
/**
|
||||
* A request to start the replay buffer has been issued.
|
||||
*
|
||||
* @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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 streamingActive = obs_frontend_streaming_active();
|
||||
bool recordingActive = obs_frontend_recording_active();
|
||||
|
||||
OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output();
|
||||
|
||||
if (!streamOutput || !streamingActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t bytesSent = obs_output_get_total_bytes(streamOutput);
|
||||
uint64_t bytesSentTime = os_gettime_ns();
|
||||
|
||||
if (bytesSent < _lastBytesSent)
|
||||
bytesSent = 0;
|
||||
|
||||
if (bytesSent == 0)
|
||||
_lastBytesSent = 0;
|
||||
|
||||
uint64_t bytesBetween = bytesSent - _lastBytesSent;
|
||||
double timePassed =
|
||||
double(bytesSentTime - _lastBytesSentTime) / 1000000000.0;
|
||||
|
||||
uint64_t bytesPerSec = bytesBetween / timePassed;
|
||||
|
||||
_lastBytesSent = bytesSent;
|
||||
_lastBytesSentTime = bytesSentTime;
|
||||
|
||||
uint64_t totalStreamTime =
|
||||
(os_gettime_ns() - _streamStarttime) / 1000000000;
|
||||
|
||||
int totalFrames = obs_output_get_total_frames(streamOutput);
|
||||
int droppedFrames = obs_output_get_frames_dropped(streamOutput);
|
||||
|
||||
float strain = obs_output_get_congestion(streamOutput);
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_bool(data, "streaming", streamingActive);
|
||||
obs_data_set_bool(data, "recording", recordingActive);
|
||||
obs_data_set_int(data, "bytes-per-sec", bytesPerSec);
|
||||
obs_data_set_int(data, "kbits-per-sec", (bytesPerSec * 8) / 1024);
|
||||
obs_data_set_int(data, "total-stream-time", totalStreamTime);
|
||||
obs_data_set_int(data, "num-total-frames", totalFrames);
|
||||
obs_data_set_int(data, "num-dropped-frames", droppedFrames);
|
||||
obs_data_set_double(data, "fps", obs_get_active_fps());
|
||||
obs_data_set_double(data, "strain", strain);
|
||||
obs_data_set_bool(data, "preview-only", false); // Retrocompat with OBSRemote
|
||||
|
||||
broadcastUpdate("StreamStatus", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Emitted every 2 seconds after enabling it by calling SetHeartbeat.
|
||||
*
|
||||
* @return {boolean} `pulse` Toggles between every JSON meassage as an "I am alive" indicator.
|
||||
* @return {string (optional)} `current-profile` Current active profile.
|
||||
* @return {string (optional)} `current-scene` Current active scene.
|
||||
* @return {boolean (optional)} `streaming` Current streaming state.
|
||||
* @return {int (optional)} `total-stream-time` Total time (in seconds) since the stream started.
|
||||
* @return {int (optional)} `total-stream-bytes` Total bytes sent since the stream started.
|
||||
* @return {int (optional)} `total-stream-frames` Total frames streamed since the stream started.
|
||||
* @return {boolean (optional)} `recording` Current recording state.
|
||||
* @return {int (optional)} `total-record-time` Total time (in seconds) since recording started.
|
||||
* @return {int (optional)} `total-record-bytes` Total bytes recorded since the recording started.
|
||||
* @return {int (optional)} `total-record-frames` Total frames recorded since the recording started.
|
||||
*
|
||||
* @api events
|
||||
* @name Heartbeat
|
||||
* @category general
|
||||
*/
|
||||
void WSEvents::Heartbeat() {
|
||||
|
||||
if (!HeartbeatIsActive) return;
|
||||
|
||||
bool streamingActive = obs_frontend_streaming_active();
|
||||
bool recordingActive = obs_frontend_recording_active();
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output();
|
||||
OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output();
|
||||
|
||||
pulse = !pulse;
|
||||
obs_data_set_bool(data, "pulse", pulse);
|
||||
|
||||
obs_data_set_string(data, "current-profile", obs_frontend_get_current_profile());
|
||||
|
||||
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
|
||||
obs_data_set_string(data, "current-scene", obs_source_get_name(currentScene));
|
||||
|
||||
obs_data_set_bool(data, "streaming", streamingActive);
|
||||
if (streamingActive) {
|
||||
uint64_t totalStreamTime = (os_gettime_ns() - _streamStarttime) / 1000000000;
|
||||
obs_data_set_int(data, "total-stream-time", totalStreamTime);
|
||||
obs_data_set_int(data, "total-stream-bytes", (uint64_t)obs_output_get_total_bytes(streamOutput));
|
||||
obs_data_set_int(data, "total-stream-frames", obs_output_get_total_frames(streamOutput));
|
||||
}
|
||||
|
||||
obs_data_set_bool(data, "recording", recordingActive);
|
||||
if (recordingActive) {
|
||||
uint64_t totalRecordTime = (os_gettime_ns() - _recStarttime) / 1000000000;
|
||||
obs_data_set_int(data, "total-record-time", totalRecordTime);
|
||||
obs_data_set_int(data, "total-record-bytes", (uint64_t)obs_output_get_total_bytes(recordOutput));
|
||||
obs_data_set_int(data, "total-record-frames", obs_output_get_total_frames(recordOutput));
|
||||
}
|
||||
|
||||
broadcastUpdate("Heartbeat", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
OBSDataAutoRelease fields = obs_data_create();
|
||||
obs_data_set_int(fields, "new-duration", ms);
|
||||
|
||||
broadcastUpdate("TransitionDurationChanged", fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
WSEvents* instance = static_cast<WSEvents*>(param);
|
||||
|
||||
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
|
||||
|
||||
OBSDataAutoRelease fields = obs_data_create();
|
||||
obs_data_set_string(fields, "name", obs_source_get_name(currentTransition));
|
||||
obs_data_set_int(fields, "duration", Utils::GetTransitionDuration());
|
||||
|
||||
instance->broadcastUpdate("TransitionBegin", fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
obs_scene_t* scene = nullptr;
|
||||
calldata_get_ptr(data, "scene", &scene);
|
||||
|
||||
OBSDataAutoRelease fields = obs_data_create();
|
||||
obs_data_set_string(fields, "scene-name",
|
||||
obs_source_get_name(obs_scene_get_source(scene)));
|
||||
|
||||
instance->broadcastUpdate("SourceOrderChanged", fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
obs_scene_t* scene = nullptr;
|
||||
calldata_get_ptr(data, "scene", &scene);
|
||||
|
||||
obs_sceneitem_t* sceneItem = nullptr;
|
||||
calldata_get_ptr(data, "item", &sceneItem);
|
||||
|
||||
const char* sceneName =
|
||||
obs_source_get_name(obs_scene_get_source(scene));
|
||||
const char* sceneItemName =
|
||||
obs_source_get_name(obs_sceneitem_get_source(sceneItem));
|
||||
|
||||
OBSDataAutoRelease fields = obs_data_create();
|
||||
obs_data_set_string(fields, "scene-name", sceneName);
|
||||
obs_data_set_string(fields, "item-name", sceneItemName);
|
||||
|
||||
instance->broadcastUpdate("SceneItemAdded", fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
obs_scene_t* scene = nullptr;
|
||||
calldata_get_ptr(data, "scene", &scene);
|
||||
|
||||
obs_sceneitem_t* sceneItem = nullptr;
|
||||
calldata_get_ptr(data, "item", &sceneItem);
|
||||
|
||||
const char* sceneName =
|
||||
obs_source_get_name(obs_scene_get_source(scene));
|
||||
const char* sceneItemName =
|
||||
obs_source_get_name(obs_sceneitem_get_source(sceneItem));
|
||||
|
||||
OBSDataAutoRelease fields = obs_data_create();
|
||||
obs_data_set_string(fields, "scene-name", sceneName);
|
||||
obs_data_set_string(fields, "item-name", sceneItemName);
|
||||
|
||||
instance->broadcastUpdate("SceneItemRemoved", fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
obs_scene_t* scene = nullptr;
|
||||
calldata_get_ptr(data, "scene", &scene);
|
||||
|
||||
obs_sceneitem_t* sceneItem = nullptr;
|
||||
calldata_get_ptr(data, "item", &sceneItem);
|
||||
|
||||
bool visible = false;
|
||||
calldata_get_bool(data, "visible", &visible);
|
||||
|
||||
const char* sceneName =
|
||||
obs_source_get_name(obs_scene_get_source(scene));
|
||||
const char* sceneItemName =
|
||||
obs_source_get_name(obs_sceneitem_get_source(sceneItem));
|
||||
|
||||
OBSDataAutoRelease fields = obs_data_create();
|
||||
obs_data_set_string(fields, "scene-name", sceneName);
|
||||
obs_data_set_string(fields, "item-name", sceneItemName);
|
||||
obs_data_set_bool(fields, "item-visible", visible);
|
||||
|
||||
instance->broadcastUpdate("SceneItemVisibilityChanged", fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* The selected preview scene has changed (only available in Studio Mode).
|
||||
*
|
||||
* @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 (obs_frontend_preview_program_mode_active()) {
|
||||
OBSScene scene = Utils::SceneListItemToScene(current);
|
||||
if (!scene)
|
||||
return;
|
||||
|
||||
OBSSource sceneSource = obs_scene_get_source(scene);
|
||||
OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(sceneSource);
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_string(data, "scene-name", obs_source_get_name(sceneSource));
|
||||
obs_data_set_array(data, "sources", sceneItems);
|
||||
|
||||
broadcastUpdate("PreviewSceneChanged", data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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::OnStudioModeSwitched(bool checked) {
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_bool(data, "new-state", checked);
|
||||
|
||||
broadcastUpdate("StudioModeSwitched", data);
|
||||
}
|
111
src/WSEvents.h
Normal file
111
src/WSEvents.h
Normal file
@ -0,0 +1,111 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2017 Brendan Hagan <https://github.com/haganbmj>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef WSEVENTS_H
|
||||
#define WSEVENTS_H
|
||||
|
||||
#include <obs.hpp>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QListWidgetItem>
|
||||
#include "WSServer.h"
|
||||
|
||||
class WSEvents : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WSEvents(WSServer* srv);
|
||||
~WSEvents();
|
||||
static void FrontendEventHandler(
|
||||
enum obs_frontend_event event, void* privateData);
|
||||
static WSEvents* Instance;
|
||||
void connectTransitionSignals(obs_source_t* transition);
|
||||
void connectSceneSignals(obs_source_t* scene);
|
||||
|
||||
uint64_t GetStreamingTime();
|
||||
const char* GetStreamingTimecode();
|
||||
uint64_t GetRecordingTime();
|
||||
const char* GetRecordingTimecode();
|
||||
|
||||
bool HeartbeatIsActive;
|
||||
|
||||
private slots:
|
||||
void deferredInitOperations();
|
||||
void StreamStatus();
|
||||
void Heartbeat();
|
||||
void TransitionDurationChanged(int ms);
|
||||
void SelectedSceneChanged(
|
||||
QListWidgetItem* current, QListWidgetItem* prev);
|
||||
|
||||
private:
|
||||
WSServer* _srv;
|
||||
OBSSource currentScene;
|
||||
OBSSource currentTransition;
|
||||
|
||||
bool pulse;
|
||||
|
||||
bool _streamingActive;
|
||||
bool _recordingActive;
|
||||
|
||||
uint64_t _streamStarttime;
|
||||
uint64_t _recStarttime;
|
||||
|
||||
uint64_t _lastBytesSent;
|
||||
uint64_t _lastBytesSentTime;
|
||||
|
||||
void broadcastUpdate(const char* updateType,
|
||||
obs_data_t* additionalFields);
|
||||
|
||||
void OnSceneChange();
|
||||
void OnSceneListChange();
|
||||
void OnSceneCollectionChange();
|
||||
void OnSceneCollectionListChange();
|
||||
|
||||
void OnTransitionChange();
|
||||
void OnTransitionListChange();
|
||||
|
||||
void OnProfileChange();
|
||||
void OnProfileListChange();
|
||||
|
||||
void OnStreamStarting();
|
||||
void OnStreamStarted();
|
||||
void OnStreamStopping();
|
||||
void OnStreamStopped();
|
||||
|
||||
void OnRecordingStarting();
|
||||
void OnRecordingStarted();
|
||||
void OnRecordingStopping();
|
||||
void OnRecordingStopped();
|
||||
|
||||
void OnReplayStarting();
|
||||
void OnReplayStarted();
|
||||
void OnReplayStopping();
|
||||
void OnReplayStopped();
|
||||
|
||||
void OnStudioModeSwitched(bool enabled);
|
||||
|
||||
void OnExit();
|
||||
|
||||
static void OnTransitionBegin(void* param, calldata_t* data);
|
||||
|
||||
static void OnSceneReordered(void* param, calldata_t* data);
|
||||
static void OnSceneItemAdd(void* param, calldata_t* data);
|
||||
static void OnSceneItemDelete(void* param, calldata_t* data);
|
||||
static void OnSceneItemVisibilityChanged(void* param, calldata_t* data);
|
||||
};
|
||||
|
||||
#endif // WSEVENTS_H
|
217
src/WSRequestHandler.cpp
Normal file
217
src/WSRequestHandler.cpp
Normal file
@ -0,0 +1,217 @@
|
||||
/**
|
||||
* obs-websocket
|
||||
* Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
* Copyright (C) 2017 Mikhail Swift <https://github.com/mikhailswift>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <obs-data.h>
|
||||
|
||||
#include "Config.h"
|
||||
#include "Utils.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
QHash<QString, void(*)(WSRequestHandler*)> WSRequestHandler::messageMap {
|
||||
{ "GetVersion", WSRequestHandler::HandleGetVersion },
|
||||
{ "GetAuthRequired", WSRequestHandler::HandleGetAuthRequired },
|
||||
{ "Authenticate", WSRequestHandler::HandleAuthenticate },
|
||||
|
||||
{ "SetHeartbeat", WSRequestHandler::HandleSetHeartbeat },
|
||||
|
||||
{ "SetFilenameFormatting", WSRequestHandler::HandleSetFilenameFormatting },
|
||||
{ "GetFilenameFormatting", WSRequestHandler::HandleGetFilenameFormatting },
|
||||
|
||||
{ "SetCurrentScene", WSRequestHandler::HandleSetCurrentScene },
|
||||
{ "GetCurrentScene", WSRequestHandler::HandleGetCurrentScene },
|
||||
{ "GetSceneList", WSRequestHandler::HandleGetSceneList },
|
||||
|
||||
{ "SetSourceRender", WSRequestHandler::HandleSetSceneItemRender }, // Retrocompat
|
||||
{ "SetSceneItemRender", WSRequestHandler::HandleSetSceneItemRender },
|
||||
{ "SetSceneItemPosition", WSRequestHandler::HandleSetSceneItemPosition },
|
||||
{ "SetSceneItemTransform", WSRequestHandler::HandleSetSceneItemTransform },
|
||||
{ "SetSceneItemCrop", WSRequestHandler::HandleSetSceneItemCrop },
|
||||
{ "GetSceneItemProperties", WSRequestHandler::HandleGetSceneItemProperties },
|
||||
{ "SetSceneItemProperties", WSRequestHandler::HandleSetSceneItemProperties },
|
||||
{ "ResetSceneItem", WSRequestHandler::HandleResetSceneItem },
|
||||
|
||||
{ "GetStreamingStatus", WSRequestHandler::HandleGetStreamingStatus },
|
||||
{ "StartStopStreaming", WSRequestHandler::HandleStartStopStreaming },
|
||||
{ "StartStopRecording", WSRequestHandler::HandleStartStopRecording },
|
||||
{ "StartStreaming", WSRequestHandler::HandleStartStreaming },
|
||||
{ "StopStreaming", WSRequestHandler::HandleStopStreaming },
|
||||
{ "StartRecording", WSRequestHandler::HandleStartRecording },
|
||||
{ "StopRecording", WSRequestHandler::HandleStopRecording },
|
||||
|
||||
{ "StartStopReplayBuffer", WSRequestHandler::HandleStartStopReplayBuffer },
|
||||
{ "StartReplayBuffer", WSRequestHandler::HandleStartReplayBuffer },
|
||||
{ "StopReplayBuffer", WSRequestHandler::HandleStopReplayBuffer },
|
||||
{ "SaveReplayBuffer", WSRequestHandler::HandleSaveReplayBuffer },
|
||||
|
||||
{ "SetRecordingFolder", WSRequestHandler::HandleSetRecordingFolder },
|
||||
{ "GetRecordingFolder", WSRequestHandler::HandleGetRecordingFolder },
|
||||
|
||||
{ "GetTransitionList", WSRequestHandler::HandleGetTransitionList },
|
||||
{ "GetCurrentTransition", WSRequestHandler::HandleGetCurrentTransition },
|
||||
{ "SetCurrentTransition", WSRequestHandler::HandleSetCurrentTransition },
|
||||
{ "SetTransitionDuration", WSRequestHandler::HandleSetTransitionDuration },
|
||||
{ "GetTransitionDuration", WSRequestHandler::HandleGetTransitionDuration },
|
||||
|
||||
{ "SetVolume", WSRequestHandler::HandleSetVolume },
|
||||
{ "GetVolume", WSRequestHandler::HandleGetVolume },
|
||||
{ "ToggleMute", WSRequestHandler::HandleToggleMute },
|
||||
{ "SetMute", WSRequestHandler::HandleSetMute },
|
||||
{ "GetMute", WSRequestHandler::HandleGetMute },
|
||||
{ "SetSyncOffset", WSRequestHandler::HandleSetSyncOffset },
|
||||
{ "GetSyncOffset", WSRequestHandler::HandleGetSyncOffset },
|
||||
{ "GetSpecialSources", WSRequestHandler::HandleGetSpecialSources },
|
||||
{ "GetSourcesList", WSRequestHandler::HandleGetSourcesList },
|
||||
{ "GetSourceTypesList", WSRequestHandler::HandleGetSourceTypesList },
|
||||
{ "GetSourceSettings", WSRequestHandler::HandleGetSourceSettings },
|
||||
{ "SetSourceSettings", WSRequestHandler::HandleSetSourceSettings },
|
||||
|
||||
{ "SetCurrentSceneCollection", WSRequestHandler::HandleSetCurrentSceneCollection },
|
||||
{ "GetCurrentSceneCollection", WSRequestHandler::HandleGetCurrentSceneCollection },
|
||||
{ "ListSceneCollections", WSRequestHandler::HandleListSceneCollections },
|
||||
|
||||
{ "SetCurrentProfile", WSRequestHandler::HandleSetCurrentProfile },
|
||||
{ "GetCurrentProfile", WSRequestHandler::HandleGetCurrentProfile },
|
||||
{ "ListProfiles", WSRequestHandler::HandleListProfiles },
|
||||
|
||||
{ "SetStreamSettings", WSRequestHandler::HandleSetStreamSettings },
|
||||
{ "GetStreamSettings", WSRequestHandler::HandleGetStreamSettings },
|
||||
{ "SaveStreamSettings", WSRequestHandler::HandleSaveStreamSettings },
|
||||
|
||||
{ "GetStudioModeStatus", WSRequestHandler::HandleGetStudioModeStatus },
|
||||
{ "GetPreviewScene", WSRequestHandler::HandleGetPreviewScene },
|
||||
{ "SetPreviewScene", WSRequestHandler::HandleSetPreviewScene },
|
||||
{ "TransitionToProgram", WSRequestHandler::HandleTransitionToProgram },
|
||||
{ "EnableStudioMode", WSRequestHandler::HandleEnableStudioMode },
|
||||
{ "DisableStudioMode", WSRequestHandler::HandleDisableStudioMode },
|
||||
{ "ToggleStudioMode", WSRequestHandler::HandleToggleStudioMode },
|
||||
|
||||
{ "SetTextGDIPlusProperties", WSRequestHandler::HandleSetTextGDIPlusProperties },
|
||||
{ "GetTextGDIPlusProperties", WSRequestHandler::HandleGetTextGDIPlusProperties },
|
||||
|
||||
{ "GetBrowserSourceProperties", WSRequestHandler::HandleGetBrowserSourceProperties },
|
||||
{ "SetBrowserSourceProperties", WSRequestHandler::HandleSetBrowserSourceProperties }
|
||||
};
|
||||
|
||||
QSet<QString> WSRequestHandler::authNotRequired {
|
||||
"GetVersion",
|
||||
"GetAuthRequired",
|
||||
"Authenticate"
|
||||
};
|
||||
|
||||
WSRequestHandler::WSRequestHandler(QWebSocket* client) :
|
||||
_messageId(0),
|
||||
_requestType(""),
|
||||
data(nullptr),
|
||||
_client(client)
|
||||
{
|
||||
}
|
||||
|
||||
void WSRequestHandler::processIncomingMessage(QString textMessage) {
|
||||
QByteArray msgData = textMessage.toUtf8();
|
||||
const char* msg = msgData.constData();
|
||||
|
||||
data = obs_data_create_from_json(msg);
|
||||
if (!data) {
|
||||
if (!msg)
|
||||
msg = "<null pointer>";
|
||||
|
||||
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg);
|
||||
SendErrorResponse("invalid JSON payload");
|
||||
return;
|
||||
}
|
||||
|
||||
if (Config::Current()->DebugEnabled) {
|
||||
blog(LOG_DEBUG, "Request >> '%s'", msg);
|
||||
}
|
||||
|
||||
if (!hasField("request-type")
|
||||
|| !hasField("message-id"))
|
||||
{
|
||||
SendErrorResponse("missing request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
_requestType = obs_data_get_string(data, "request-type");
|
||||
_messageId = obs_data_get_string(data, "message-id");
|
||||
|
||||
if (Config::Current()->AuthRequired
|
||||
&& (_client->property(PROP_AUTHENTICATED).toBool() == false)
|
||||
&& (authNotRequired.find(_requestType) == authNotRequired.end()))
|
||||
{
|
||||
SendErrorResponse("Not Authenticated");
|
||||
return;
|
||||
}
|
||||
|
||||
void (*handlerFunc)(WSRequestHandler*) = (messageMap[_requestType]);
|
||||
|
||||
if (handlerFunc != nullptr)
|
||||
handlerFunc(this);
|
||||
else
|
||||
SendErrorResponse("invalid request type");
|
||||
}
|
||||
|
||||
WSRequestHandler::~WSRequestHandler() {
|
||||
}
|
||||
|
||||
void WSRequestHandler::SendOKResponse(obs_data_t* additionalFields) {
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_string(response, "status", "ok");
|
||||
obs_data_set_string(response, "message-id", _messageId);
|
||||
|
||||
if (additionalFields)
|
||||
obs_data_apply(response, additionalFields);
|
||||
|
||||
SendResponse(response);
|
||||
}
|
||||
|
||||
void WSRequestHandler::SendErrorResponse(const char* errorMessage) {
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_string(response, "status", "error");
|
||||
obs_data_set_string(response, "error", errorMessage);
|
||||
obs_data_set_string(response, "message-id", _messageId);
|
||||
|
||||
SendResponse(response);
|
||||
}
|
||||
|
||||
void WSRequestHandler::SendErrorResponse(obs_data_t* additionalFields) {
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_string(response, "status", "error");
|
||||
obs_data_set_string(response, "message-id", _messageId);
|
||||
|
||||
if (additionalFields)
|
||||
obs_data_set_obj(response, "error", additionalFields);
|
||||
|
||||
SendResponse(response);
|
||||
}
|
||||
|
||||
void WSRequestHandler::SendResponse(obs_data_t* response) {
|
||||
QString json = obs_data_get_json(response);
|
||||
_client->sendTextMessage(json);
|
||||
|
||||
if (Config::Current()->DebugEnabled)
|
||||
blog(LOG_DEBUG, "Response << '%s'", json.toUtf8().constData());
|
||||
}
|
||||
|
||||
bool WSRequestHandler::hasField(QString name) {
|
||||
if (!data || name.isEmpty() || name.isNull())
|
||||
return false;
|
||||
|
||||
return obs_data_has_user_value(data, name.toUtf8());
|
||||
}
|
139
src/WSRequestHandler.h
Normal file
139
src/WSRequestHandler.h
Normal file
@ -0,0 +1,139 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
Copyright (C) 2017 Mikhail Swift <https://github.com/mikhailswift>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#ifndef WSREQUESTHANDLER_H
|
||||
#define WSREQUESTHANDLER_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QSet>
|
||||
#include <QWebSocket>
|
||||
#include <QWebSocketServer>
|
||||
|
||||
#include <obs.hpp>
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
#include "obs-websocket.h"
|
||||
|
||||
class WSRequestHandler : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WSRequestHandler(QWebSocket* client);
|
||||
~WSRequestHandler();
|
||||
void processIncomingMessage(QString textMessage);
|
||||
bool hasField(QString name);
|
||||
|
||||
private:
|
||||
QWebSocket* _client;
|
||||
const char* _messageId;
|
||||
const char* _requestType;
|
||||
OBSDataAutoRelease data;
|
||||
|
||||
void SendOKResponse(obs_data_t* additionalFields = NULL);
|
||||
void SendErrorResponse(const char* errorMessage);
|
||||
void SendErrorResponse(obs_data_t* additionalFields = NULL);
|
||||
void SendResponse(obs_data_t* response);
|
||||
|
||||
static QHash<QString, void(*)(WSRequestHandler*)> messageMap;
|
||||
static QSet<QString> authNotRequired;
|
||||
|
||||
static void HandleGetVersion(WSRequestHandler* req);
|
||||
static void HandleGetAuthRequired(WSRequestHandler* req);
|
||||
static void HandleAuthenticate(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetHeartbeat(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetFilenameFormatting(WSRequestHandler* req);
|
||||
static void HandleGetFilenameFormatting(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetCurrentScene(WSRequestHandler* req);
|
||||
static void HandleGetCurrentScene(WSRequestHandler* req);
|
||||
static void HandleGetSceneList(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetSceneItemRender(WSRequestHandler* req);
|
||||
static void HandleSetSceneItemPosition(WSRequestHandler* req);
|
||||
static void HandleSetSceneItemTransform(WSRequestHandler* req);
|
||||
static void HandleSetSceneItemCrop(WSRequestHandler* req);
|
||||
static void HandleGetSceneItemProperties(WSRequestHandler* req);
|
||||
static void HandleSetSceneItemProperties(WSRequestHandler* req);
|
||||
static void HandleResetSceneItem(WSRequestHandler* req);
|
||||
|
||||
static void HandleGetStreamingStatus(WSRequestHandler* req);
|
||||
static void HandleStartStopStreaming(WSRequestHandler* req);
|
||||
static void HandleStartStopRecording(WSRequestHandler* req);
|
||||
static void HandleStartStreaming(WSRequestHandler* req);
|
||||
static void HandleStopStreaming(WSRequestHandler* req);
|
||||
static void HandleStartRecording(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 HandleGetRecordingFolder(WSRequestHandler* req);
|
||||
|
||||
static void HandleGetTransitionList(WSRequestHandler* req);
|
||||
static void HandleGetCurrentTransition(WSRequestHandler* req);
|
||||
static void HandleSetCurrentTransition(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetVolume(WSRequestHandler* req);
|
||||
static void HandleGetVolume(WSRequestHandler* req);
|
||||
static void HandleToggleMute(WSRequestHandler* req);
|
||||
static void HandleSetMute(WSRequestHandler* req);
|
||||
static void HandleGetMute(WSRequestHandler* req);
|
||||
static void HandleSetSyncOffset(WSRequestHandler* req);
|
||||
static void HandleGetSyncOffset(WSRequestHandler* req);
|
||||
static void HandleGetSpecialSources(WSRequestHandler* req);
|
||||
static void HandleGetSourcesList(WSRequestHandler* req);
|
||||
static void HandleGetSourceTypesList(WSRequestHandler* req);
|
||||
static void HandleGetSourceSettings(WSRequestHandler* req);
|
||||
static void HandleSetSourceSettings(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetCurrentSceneCollection(WSRequestHandler* req);
|
||||
static void HandleGetCurrentSceneCollection(WSRequestHandler* req);
|
||||
static void HandleListSceneCollections(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetCurrentProfile(WSRequestHandler* req);
|
||||
static void HandleGetCurrentProfile(WSRequestHandler* req);
|
||||
static void HandleListProfiles(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetStreamSettings(WSRequestHandler* req);
|
||||
static void HandleGetStreamSettings(WSRequestHandler* req);
|
||||
static void HandleSaveStreamSettings(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetTransitionDuration(WSRequestHandler* req);
|
||||
static void HandleGetTransitionDuration(WSRequestHandler* req);
|
||||
|
||||
static void HandleGetStudioModeStatus(WSRequestHandler* req);
|
||||
static void HandleGetPreviewScene(WSRequestHandler* req);
|
||||
static void HandleSetPreviewScene(WSRequestHandler* req);
|
||||
static void HandleTransitionToProgram(WSRequestHandler* req);
|
||||
static void HandleEnableStudioMode(WSRequestHandler* req);
|
||||
static void HandleDisableStudioMode(WSRequestHandler* req);
|
||||
static void HandleToggleStudioMode(WSRequestHandler* req);
|
||||
|
||||
static void HandleSetTextGDIPlusProperties(WSRequestHandler* req);
|
||||
static void HandleGetTextGDIPlusProperties(WSRequestHandler* req);
|
||||
static void HandleSetBrowserSourceProperties(WSRequestHandler* req);
|
||||
static void HandleGetBrowserSourceProperties(WSRequestHandler* req);
|
||||
};
|
||||
|
||||
#endif // WSPROTOCOL_H
|
168
src/WSRequestHandler_General.cpp
Normal file
168
src/WSRequestHandler_General.cpp
Normal file
@ -0,0 +1,168 @@
|
||||
#include <QString>
|
||||
|
||||
#include "Config.h"
|
||||
#include "Utils.h"
|
||||
#include "WSEvents.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
/**
|
||||
* Returns the latest version of the plugin and the API.
|
||||
*
|
||||
* @return {double} `version` OBSRemote compatible API version. Fixed to 1.1 for retrocompatibility.
|
||||
* @return {String} `obs-websocket-version` obs-websocket plugin version.
|
||||
* @return {String} `obs-studio-version` OBS Studio program version.
|
||||
* @return {String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3").
|
||||
*
|
||||
* @api requests
|
||||
* @name GetVersion
|
||||
* @category general
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSRequestHandler::HandleGetVersion(WSRequestHandler* req) {
|
||||
QString obsVersion = Utils::OBSVersionString();
|
||||
|
||||
QList<QString> names = req->messageMap.keys();
|
||||
names.sort(Qt::CaseInsensitive);
|
||||
|
||||
// (Palakis) OBS' data arrays only support object arrays, so I improvised.
|
||||
QString requests;
|
||||
requests += names.takeFirst();
|
||||
for (QString reqName : names) {
|
||||
requests += ("," + reqName);
|
||||
}
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION);
|
||||
obs_data_set_string(data, "obs-studio-version", obsVersion.toUtf8());
|
||||
obs_data_set_string(data, "available-requests", requests.toUtf8());
|
||||
|
||||
req->SendOKResponse(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the client if authentication is required. If so, returns authentication parameters `challenge`
|
||||
* and `salt` (see "Authentication" for more information).
|
||||
*
|
||||
* @return {boolean} `authRequired` Indicates whether authentication is required.
|
||||
* @return {String (optional)} `challenge`
|
||||
* @return {String (optional)} `salt`
|
||||
*
|
||||
* @api requests
|
||||
* @name GetAuthRequired
|
||||
* @category general
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSRequestHandler::HandleGetAuthRequired(WSRequestHandler* req) {
|
||||
bool authRequired = Config::Current()->AuthRequired;
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_bool(data, "authRequired", authRequired);
|
||||
|
||||
if (authRequired) {
|
||||
obs_data_set_string(data, "challenge",
|
||||
Config::Current()->SessionChallenge.toUtf8());
|
||||
obs_data_set_string(data, "salt",
|
||||
Config::Current()->Salt.toUtf8());
|
||||
}
|
||||
|
||||
req->SendOKResponse(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to authenticate the client to the server.
|
||||
*
|
||||
* @param {String} `auth` Response to the auth challenge (see "Authentication" for more information).
|
||||
*
|
||||
* @api requests
|
||||
* @name Authenticate
|
||||
* @category general
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSRequestHandler::HandleAuthenticate(WSRequestHandler* req) {
|
||||
if (!req->hasField("auth")) {
|
||||
req->SendErrorResponse("missing request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
QString auth = obs_data_get_string(req->data, "auth");
|
||||
if (auth.isEmpty()) {
|
||||
req->SendErrorResponse("auth not specified!");
|
||||
return;
|
||||
}
|
||||
|
||||
if ((req->_client->property(PROP_AUTHENTICATED).toBool() == false)
|
||||
&& Config::Current()->CheckAuth(auth))
|
||||
{
|
||||
req->_client->setProperty(PROP_AUTHENTICATED, true);
|
||||
req->SendOKResponse();
|
||||
} else {
|
||||
req->SendErrorResponse("Authentication Failed.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable sending of the Heartbeat event
|
||||
*
|
||||
* @param {boolean} `enable` Starts/Stops emitting heartbeat messages
|
||||
*
|
||||
* @api requests
|
||||
* @name SetHeartbeat
|
||||
* @category general
|
||||
* @since 4.3.0
|
||||
*/
|
||||
void WSRequestHandler::HandleSetHeartbeat(WSRequestHandler* req) {
|
||||
if (!req->hasField("enable")) {
|
||||
req->SendErrorResponse("Heartbeat <enable> parameter missing");
|
||||
return;
|
||||
}
|
||||
|
||||
WSEvents::Instance->HeartbeatIsActive =
|
||||
obs_data_get_bool(req->data, "enable");
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_bool(response, "enable",
|
||||
WSEvents::Instance->HeartbeatIsActive);
|
||||
req->SendOKResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the filename formatting string
|
||||
*
|
||||
* @param {String} `filename-formatting` Filename formatting string to set.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetFilenameFormatting
|
||||
* @category general
|
||||
* @since 4.3.0
|
||||
*/
|
||||
void WSRequestHandler::HandleSetFilenameFormatting(WSRequestHandler* req) {
|
||||
if (!req->hasField("filename-formatting")) {
|
||||
req->SendErrorResponse("<filename-formatting> parameter missing");
|
||||
return;
|
||||
}
|
||||
|
||||
QString filenameFormatting = obs_data_get_string(req->data, "filename-formatting");
|
||||
if (!filenameFormatting.isEmpty()) {
|
||||
Utils::SetFilenameFormatting(filenameFormatting.toUtf8());
|
||||
req->SendOKResponse();
|
||||
} else {
|
||||
req->SendErrorResponse("invalid request parameters");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filename formatting string
|
||||
*
|
||||
* @return {String} `filename-formatting` Current filename formatting string.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetFilenameFormatting
|
||||
* @category general
|
||||
* @since 4.3.0
|
||||
*/
|
||||
void WSRequestHandler::HandleGetFilenameFormatting(WSRequestHandler* req) {
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_string(response, "filename-formatting", Utils::GetFilenameFormatting());
|
||||
req->SendOKResponse(response);
|
||||
}
|
70
src/WSRequestHandler_Profiles.cpp
Normal file
70
src/WSRequestHandler_Profiles.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
#include <QString>
|
||||
#include "Utils.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
/**
|
||||
* Set the currently active profile.
|
||||
*
|
||||
* @param {String} `profile-name` Name of the desired profile.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetCurrentProfile
|
||||
* @category profiles
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSRequestHandler::HandleSetCurrentProfile(WSRequestHandler* req) {
|
||||
if (!req->hasField("profile-name")) {
|
||||
req->SendErrorResponse("missing request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
QString profileName = obs_data_get_string(req->data, "profile-name");
|
||||
if (!profileName.isEmpty()) {
|
||||
// TODO : check if profile exists
|
||||
obs_frontend_set_current_profile(profileName.toUtf8());
|
||||
req->SendOKResponse();
|
||||
} else {
|
||||
req->SendErrorResponse("invalid request parameters");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the current profile.
|
||||
*
|
||||
* @return {String} `profile-name` Name of the currently active profile.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetCurrentProfile
|
||||
* @category profiles
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSRequestHandler::HandleGetCurrentProfile(WSRequestHandler* req) {
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_string(response, "profile-name",
|
||||
obs_frontend_get_current_profile());
|
||||
|
||||
req->SendOKResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of available profiles.
|
||||
*
|
||||
* @return {Object|Array} `profiles` List of available profiles.
|
||||
*
|
||||
* @api requests
|
||||
* @name ListProfiles
|
||||
* @category profiles
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSRequestHandler::HandleListProfiles(WSRequestHandler* req) {
|
||||
char** profiles = obs_frontend_get_profiles();
|
||||
OBSDataArrayAutoRelease list =
|
||||
Utils::StringListToArray(profiles, "profile-name");
|
||||
bfree(profiles);
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_array(response, "profiles", list);
|
||||
|
||||
req->SendOKResponse(response);
|
||||
}
|
100
src/WSRequestHandler_Recording.cpp
Normal file
100
src/WSRequestHandler_Recording.cpp
Normal file
@ -0,0 +1,100 @@
|
||||
#include <QString>
|
||||
#include "Utils.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
/**
|
||||
* Toggle recording on or off.
|
||||
*
|
||||
* @api requests
|
||||
* @name StartStopRecording
|
||||
* @category recording
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSRequestHandler::HandleStartStopRecording(WSRequestHandler* req) {
|
||||
if (obs_frontend_recording_active())
|
||||
obs_frontend_recording_stop();
|
||||
else
|
||||
obs_frontend_recording_start();
|
||||
|
||||
req->SendOKResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start recording.
|
||||
* Will return an `error` if recording is already active.
|
||||
*
|
||||
* @api requests
|
||||
* @name StartRecording
|
||||
* @category recording
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
|
||||
if (obs_frontend_recording_active() == false) {
|
||||
obs_frontend_recording_start();
|
||||
req->SendOKResponse();
|
||||
} else {
|
||||
req->SendErrorResponse("recording already active");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop recording.
|
||||
* Will return an `error` if recording is not active.
|
||||
*
|
||||
* @api requests
|
||||
* @name StopRecording
|
||||
* @category recording
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleStopRecording(WSRequestHandler* req) {
|
||||
if (obs_frontend_recording_active() == true) {
|
||||
obs_frontend_recording_stop();
|
||||
req->SendOKResponse();
|
||||
} else {
|
||||
req->SendErrorResponse("recording not active");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the current recording folder.
|
||||
*
|
||||
* @param {String} `rec-folder` Path of the recording folder.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetRecordingFolder
|
||||
* @category recording
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleSetRecordingFolder(WSRequestHandler* req) {
|
||||
if (!req->hasField("rec-folder")) {
|
||||
req->SendErrorResponse("missing request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
const char* newRecFolder = obs_data_get_string(req->data, "rec-folder");
|
||||
bool success = Utils::SetRecordingFolder(newRecFolder);
|
||||
if (success)
|
||||
req->SendOKResponse();
|
||||
else
|
||||
req->SendErrorResponse("invalid request parameters");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path of the current recording folder.
|
||||
*
|
||||
* @return {String} `rec-folder` Path of the recording folder.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetRecordingFolder
|
||||
* @category recording
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleGetRecordingFolder(WSRequestHandler* req) {
|
||||
const char* recFolder = Utils::GetRecordingFolder();
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_string(response, "rec-folder", recFolder);
|
||||
|
||||
req->SendOKResponse(response);
|
||||
}
|
92
src/WSRequestHandler_ReplayBuffer.cpp
Normal file
92
src/WSRequestHandler_ReplayBuffer.cpp
Normal file
@ -0,0 +1,92 @@
|
||||
#include <QString>
|
||||
#include "Utils.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
/**
|
||||
* Toggle the Replay Buffer on/off.
|
||||
*
|
||||
* @api requests
|
||||
* @name StartStopReplayBuffer
|
||||
* @category replay buffer
|
||||
* @since 4.2.0
|
||||
*/
|
||||
void WSRequestHandler::HandleStartStopReplayBuffer(WSRequestHandler* req) {
|
||||
if (obs_frontend_replay_buffer_active()) {
|
||||
obs_frontend_replay_buffer_stop();
|
||||
} else {
|
||||
Utils::StartReplayBuffer();
|
||||
}
|
||||
req->SendOKResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start recording into the Replay Buffer.
|
||||
* Will return an `error` if the Replay Buffer is already active or if the
|
||||
* "Save Replay Buffer" hotkey is not set in OBS' settings.
|
||||
* Setting this hotkey is mandatory, even when triggering saves only
|
||||
* through obs-websocket.
|
||||
*
|
||||
* @api requests
|
||||
* @name StartReplayBuffer
|
||||
* @category replay buffer
|
||||
* @since 4.2.0
|
||||
*/
|
||||
void WSRequestHandler::HandleStartReplayBuffer(WSRequestHandler* req) {
|
||||
if (!Utils::ReplayBufferEnabled()) {
|
||||
req->SendErrorResponse("replay buffer disabled in settings");
|
||||
return;
|
||||
}
|
||||
|
||||
if (obs_frontend_replay_buffer_active() == true) {
|
||||
req->SendErrorResponse("replay buffer already active");
|
||||
return;
|
||||
}
|
||||
|
||||
Utils::StartReplayBuffer();
|
||||
req->SendOKResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop recording into the Replay Buffer.
|
||||
* Will return an `error` if the Replay Buffer is not active.
|
||||
*
|
||||
* @api requests
|
||||
* @name StopReplayBuffer
|
||||
* @category replay buffer
|
||||
* @since 4.2.0
|
||||
*/
|
||||
void WSRequestHandler::HandleStopReplayBuffer(WSRequestHandler* req) {
|
||||
if (obs_frontend_replay_buffer_active() == true) {
|
||||
obs_frontend_replay_buffer_stop();
|
||||
req->SendOKResponse();
|
||||
} else {
|
||||
req->SendErrorResponse("replay buffer not active");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush and save the contents of the Replay Buffer to disk. This is
|
||||
* basically the same as triggering the "Save Replay Buffer" hotkey.
|
||||
* Will return an `error` if the Replay Buffer is not active.
|
||||
*
|
||||
* @api requests
|
||||
* @name SaveReplayBuffer
|
||||
* @category replay buffer
|
||||
* @since 4.2.0
|
||||
*/
|
||||
void WSRequestHandler::HandleSaveReplayBuffer(WSRequestHandler* req) {
|
||||
if (!obs_frontend_replay_buffer_active()) {
|
||||
req->SendErrorResponse("replay buffer not active");
|
||||
return;
|
||||
}
|
||||
|
||||
OBSOutputAutoRelease replayOutput = obs_frontend_get_replay_buffer_output();
|
||||
|
||||
calldata_t cd = { 0 };
|
||||
proc_handler_t* ph = obs_output_get_proc_handler(replayOutput);
|
||||
proc_handler_call(ph, "save", &cd);
|
||||
calldata_free(&cd);
|
||||
|
||||
req->SendOKResponse();
|
||||
}
|
71
src/WSRequestHandler_SceneCollections.cpp
Normal file
71
src/WSRequestHandler_SceneCollections.cpp
Normal file
@ -0,0 +1,71 @@
|
||||
#include <QString>
|
||||
#include "Utils.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
/**
|
||||
* Change the active scene collection.
|
||||
*
|
||||
* @param {String} `sc-name` Name of the desired scene collection.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetCurrentSceneCollection
|
||||
* @category scene collections
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSRequestHandler::HandleSetCurrentSceneCollection(WSRequestHandler* req) {
|
||||
if (!req->hasField("sc-name")) {
|
||||
req->SendErrorResponse("missing request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
QString sceneCollection = obs_data_get_string(req->data, "sc-name");
|
||||
if (!sceneCollection.isEmpty()) {
|
||||
// TODO : Check if specified profile exists and if changing is allowed
|
||||
obs_frontend_set_current_scene_collection(sceneCollection.toUtf8());
|
||||
req->SendOKResponse();
|
||||
} else {
|
||||
req->SendErrorResponse("invalid request parameters");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the current scene collection.
|
||||
*
|
||||
* @return {String} `sc-name` Name of the currently active scene collection.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetCurrentSceneCollection
|
||||
* @category scene collections
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSRequestHandler::HandleGetCurrentSceneCollection(WSRequestHandler* req) {
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_string(response, "sc-name",
|
||||
obs_frontend_get_current_scene_collection());
|
||||
|
||||
req->SendOKResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* List available scene collections
|
||||
*
|
||||
* @return {Object|Array} `scene-collections` Scene collections list
|
||||
* @return {String} `scene-collections.*.`
|
||||
*
|
||||
* @api requests
|
||||
* @name ListSceneCollections
|
||||
* @category scene collections
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSRequestHandler::HandleListSceneCollections(WSRequestHandler* req) {
|
||||
char** sceneCollections = obs_frontend_get_scene_collections();
|
||||
OBSDataArrayAutoRelease list =
|
||||
Utils::StringListToArray(sceneCollections, "sc-name");
|
||||
bfree(sceneCollections);
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_array(response, "scene-collections", list);
|
||||
|
||||
req->SendOKResponse(response);
|
||||
}
|
568
src/WSRequestHandler_SceneItems.cpp
Normal file
568
src/WSRequestHandler_SceneItems.cpp
Normal file
@ -0,0 +1,568 @@
|
||||
#include <QString>
|
||||
#include "Utils.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
/**
|
||||
* Gets the scene specific properties of the specified source item.
|
||||
*
|
||||
* @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.
|
||||
* @param {String} `item` The name of the source.
|
||||
*
|
||||
* @return {String} `name` The name of the source.
|
||||
* @return {int} `position.x` The x position of the source from the left.
|
||||
* @return {int} `position.y` The y position of the source from the top.
|
||||
* @return {int} `position.alignment` The point on the source that the item is manipulated from.
|
||||
* @return {double} `rotation` The clockwise rotation of the item in degrees around the point of alignment.
|
||||
* @return {double} `scale.x` The x-scale factor of the source.
|
||||
* @return {double} `scale.y` The y-scale factor of the source.
|
||||
* @return {int} `crop.top` The number of pixels cropped off the top of the source before scaling.
|
||||
* @return {int} `crop.right` The number of pixels cropped off the right of the source before scaling.
|
||||
* @return {int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling.
|
||||
* @return {int} `crop.left` The number of pixels cropped off the left of the source before scaling.
|
||||
* @return {bool} `visible` If the source is visible.
|
||||
* @return {String} `bounds.type` Type of bounding box.
|
||||
* @return {int} `bounds.alignment` Alignment of the bounding box.
|
||||
* @return {double} `bounds.x` Width of the bounding box.
|
||||
* @return {double} `bounds.y` Height of the bounding box.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetSceneItemProperties
|
||||
* @category scene items
|
||||
* @since 4.3.0
|
||||
*/
|
||||
void WSRequestHandler::HandleGetSceneItemProperties(WSRequestHandler* req) {
|
||||
if (!req->hasField("item")) {
|
||||
req->SendErrorResponse("missing request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
QString itemName = obs_data_get_string(req->data, "item");
|
||||
if (itemName.isEmpty()) {
|
||||
req->SendErrorResponse("invalid request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
req->SendErrorResponse("requested scene doesn't exist");
|
||||
return;
|
||||
}
|
||||
|
||||
OBSSceneItemAutoRelease sceneItem =
|
||||
Utils::GetSceneItemFromName(scene, itemName);
|
||||
if (!sceneItem) {
|
||||
req->SendErrorResponse("specified scene item doesn't exist");
|
||||
return;
|
||||
}
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_string(data, "name", itemName.toUtf8());
|
||||
|
||||
OBSDataAutoRelease posData = obs_data_create();
|
||||
vec2 pos;
|
||||
obs_sceneitem_get_pos(sceneItem, &pos);
|
||||
obs_data_set_double(posData, "x", pos.x);
|
||||
obs_data_set_double(posData, "y", pos.y);
|
||||
obs_data_set_int(posData, "alignment", obs_sceneitem_get_alignment(sceneItem));
|
||||
obs_data_set_obj(data, "position", posData);
|
||||
|
||||
obs_data_set_double(data, "rotation", obs_sceneitem_get_rot(sceneItem));
|
||||
|
||||
OBSDataAutoRelease scaleData = obs_data_create();
|
||||
vec2 scale;
|
||||
obs_sceneitem_get_scale(sceneItem, &scale);
|
||||
obs_data_set_double(scaleData, "x", scale.x);
|
||||
obs_data_set_double(scaleData, "y", scale.y);
|
||||
obs_data_set_obj(data, "scale", scaleData);
|
||||
|
||||
OBSDataAutoRelease cropData = obs_data_create();
|
||||
obs_sceneitem_crop crop;
|
||||
obs_sceneitem_get_crop(sceneItem, &crop);
|
||||
obs_data_set_int(cropData, "left", crop.left);
|
||||
obs_data_set_int(cropData, "top", crop.top);
|
||||
obs_data_set_int(cropData, "right", crop.right);
|
||||
obs_data_set_int(cropData, "bottom", crop.bottom);
|
||||
obs_data_set_obj(data, "crop", cropData);
|
||||
|
||||
obs_data_set_bool(data, "visible", obs_sceneitem_visible(sceneItem));
|
||||
|
||||
OBSDataAutoRelease boundsData = obs_data_create();
|
||||
obs_bounds_type boundsType = obs_sceneitem_get_bounds_type(sceneItem);
|
||||
if (boundsType == OBS_BOUNDS_NONE) {
|
||||
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_NONE");
|
||||
}
|
||||
else {
|
||||
switch (boundsType) {
|
||||
case OBS_BOUNDS_STRETCH: {
|
||||
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_STRETCH");
|
||||
break;
|
||||
}
|
||||
case OBS_BOUNDS_SCALE_INNER: {
|
||||
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_INNER");
|
||||
break;
|
||||
}
|
||||
case OBS_BOUNDS_SCALE_OUTER: {
|
||||
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_OUTER");
|
||||
break;
|
||||
}
|
||||
case OBS_BOUNDS_SCALE_TO_WIDTH: {
|
||||
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_TO_WIDTH");
|
||||
break;
|
||||
}
|
||||
case OBS_BOUNDS_SCALE_TO_HEIGHT: {
|
||||
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_SCALE_TO_HEIGHT");
|
||||
break;
|
||||
}
|
||||
case OBS_BOUNDS_MAX_ONLY: {
|
||||
obs_data_set_string(boundsData, "type", "OBS_BOUNDS_MAX_ONLY");
|
||||
break;
|
||||
}
|
||||
}
|
||||
obs_data_set_int(boundsData, "alignment", obs_sceneitem_get_bounds_alignment(sceneItem));
|
||||
vec2 bounds;
|
||||
obs_sceneitem_get_bounds(sceneItem, &bounds);
|
||||
obs_data_set_double(boundsData, "x", bounds.x);
|
||||
obs_data_set_double(boundsData, "y", bounds.y);
|
||||
}
|
||||
obs_data_set_obj(data, "bounds", boundsData);
|
||||
|
||||
req->SendOKResponse(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the scene specific properties of a source. Unspecified properties will remain unchanged.
|
||||
*
|
||||
* @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.
|
||||
* @param {String} `item` The name of the source.
|
||||
* @param {int} `position.x` The new x position of the source.
|
||||
* @param {int} `position.y` The new y position of the source.
|
||||
* @param {int} `position.alignment` The new alignment of the source.
|
||||
* @param {double} `rotation` The new clockwise rotation of the item in degrees.
|
||||
* @param {double} `scale.x` The new x scale of the item.
|
||||
* @param {double} `scale.y` The new y scale of the item.
|
||||
* @param {int} `crop.top` The new amount of pixels cropped off the top of the source before scaling.
|
||||
* @param {int} `crop.bottom` The new amount of pixels cropped off the bottom of the source before scaling.
|
||||
* @param {int} `crop.left` The new amount of pixels cropped off the left of the source before scaling.
|
||||
* @param {int} `crop.right` The new amount of pixels cropped off the right of the source before scaling.
|
||||
* @param {bool} `visible` The new visibility of the source. 'true' shows source, 'false' hides source.
|
||||
* @param {String} `bounds.type` The new bounds type of the source.
|
||||
* @param {int} `bounds.alignment` The new alignment of the bounding box. (0-2, 4-6, 8-10)
|
||||
* @param {double} `bounds.x` The new width of the bounding box.
|
||||
* @param {double} `bounds.y` The new height of the bounding box.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetSceneItemProperties
|
||||
* @category scene items
|
||||
* @since 4.3.0
|
||||
*/
|
||||
void WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler* req) {
|
||||
if (!req->hasField("item")) {
|
||||
req->SendErrorResponse("missing request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
QString itemName = obs_data_get_string(req->data, "item");
|
||||
if (itemName.isEmpty()) {
|
||||
req->SendErrorResponse("invalid request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
req->SendErrorResponse("requested scene doesn't exist");
|
||||
return;
|
||||
}
|
||||
|
||||
OBSSceneItemAutoRelease sceneItem =
|
||||
Utils::GetSceneItemFromName(scene, itemName);
|
||||
if (!sceneItem) {
|
||||
req->SendErrorResponse("specified scene item doesn't exist");
|
||||
return;
|
||||
}
|
||||
|
||||
bool badRequest = false;
|
||||
OBSDataAutoRelease errorMessage = obs_data_create();
|
||||
|
||||
if (req->hasField("position")) {
|
||||
vec2 oldPosition;
|
||||
OBSDataAutoRelease positionError = obs_data_create();
|
||||
obs_sceneitem_get_pos(sceneItem, &oldPosition);
|
||||
OBSDataAutoRelease reqPosition = obs_data_get_obj(req->data, "position");
|
||||
vec2 newPosition = oldPosition;
|
||||
if (obs_data_has_user_value(reqPosition, "x")) {
|
||||
newPosition.x = obs_data_get_int(reqPosition, "x");
|
||||
}
|
||||
if (obs_data_has_user_value(reqPosition, "y")) {
|
||||
newPosition.y = obs_data_get_int(reqPosition, "y");
|
||||
}
|
||||
if (obs_data_has_user_value(reqPosition, "alignment")) {
|
||||
const uint32_t alignment = obs_data_get_int(reqPosition, "alignment");
|
||||
if (Utils::IsValidAlignment(alignment)) {
|
||||
obs_sceneitem_set_alignment(sceneItem, alignment);
|
||||
}
|
||||
else {
|
||||
badRequest = true;
|
||||
obs_data_set_string(positionError, "alignment", "invalid");
|
||||
obs_data_set_obj(errorMessage, "position", positionError);
|
||||
}
|
||||
}
|
||||
obs_sceneitem_set_pos(sceneItem, &newPosition);
|
||||
}
|
||||
|
||||
if (req->hasField("rotation")) {
|
||||
obs_sceneitem_set_rot(sceneItem, (float)obs_data_get_double(req->data, "rotation"));
|
||||
}
|
||||
|
||||
if (req->hasField("scale")) {
|
||||
vec2 oldScale;
|
||||
obs_sceneitem_get_scale(sceneItem, &oldScale);
|
||||
OBSDataAutoRelease reqScale = obs_data_get_obj(req->data, "scale");
|
||||
vec2 newScale = oldScale;
|
||||
if (obs_data_has_user_value(reqScale, "x")) {
|
||||
newScale.x = obs_data_get_double(reqScale, "x");
|
||||
}
|
||||
if (obs_data_has_user_value(reqScale, "y")) {
|
||||
newScale.y = obs_data_get_double(reqScale, "y");
|
||||
}
|
||||
obs_sceneitem_set_scale(sceneItem, &newScale);
|
||||
}
|
||||
|
||||
if (req->hasField("crop")) {
|
||||
obs_sceneitem_crop oldCrop;
|
||||
obs_sceneitem_get_crop(sceneItem, &oldCrop);
|
||||
OBSDataAutoRelease reqCrop = obs_data_get_obj(req->data, "crop");
|
||||
obs_sceneitem_crop newCrop = oldCrop;
|
||||
if (obs_data_has_user_value(reqCrop, "top")) {
|
||||
newCrop.top = obs_data_get_int(reqCrop, "top");
|
||||
}
|
||||
if (obs_data_has_user_value(reqCrop, "right")) {
|
||||
newCrop.right = obs_data_get_int(reqCrop, "right");
|
||||
}
|
||||
if (obs_data_has_user_value(reqCrop, "bottom")) {
|
||||
newCrop.bottom = obs_data_get_int(reqCrop, "bottom");
|
||||
}
|
||||
if (obs_data_has_user_value(reqCrop, "left")) {
|
||||
newCrop.left = obs_data_get_int(reqCrop, "left");
|
||||
}
|
||||
obs_sceneitem_set_crop(sceneItem, &newCrop);
|
||||
}
|
||||
|
||||
if (req->hasField("visible")) {
|
||||
obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(req->data, "visible"));
|
||||
}
|
||||
|
||||
if (req->hasField("bounds")) {
|
||||
bool badBounds = false;
|
||||
OBSDataAutoRelease boundsError = obs_data_create();
|
||||
OBSDataAutoRelease reqBounds = obs_data_get_obj(req->data, "bounds");
|
||||
if (obs_data_has_user_value(reqBounds, "type")) {
|
||||
const char* newBoundsType = obs_data_get_string(reqBounds, "type");
|
||||
if (newBoundsType == "OBS_BOUNDS_NONE") {
|
||||
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_NONE);
|
||||
}
|
||||
else if (newBoundsType == "OBS_BOUNDS_STRETCH") {
|
||||
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_STRETCH);
|
||||
}
|
||||
else if (newBoundsType == "OBS_BOUNDS_SCALE_INNER") {
|
||||
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_INNER);
|
||||
}
|
||||
else if (newBoundsType == "OBS_BOUNDS_SCALE_OUTER") {
|
||||
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_OUTER);
|
||||
}
|
||||
else if (newBoundsType == "OBS_BOUNDS_SCALE_TO_WIDTH") {
|
||||
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_TO_WIDTH);
|
||||
}
|
||||
else if (newBoundsType == "OBS_BOUNDS_SCALE_TO_HEIGHT") {
|
||||
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_SCALE_TO_HEIGHT);
|
||||
}
|
||||
else if (newBoundsType == "OBS_BOUNDS_MAX_ONLY") {
|
||||
obs_sceneitem_set_bounds_type(sceneItem, OBS_BOUNDS_MAX_ONLY);
|
||||
}
|
||||
else {
|
||||
badRequest = badBounds = true;
|
||||
obs_data_set_string(boundsError, "type", "invalid");
|
||||
}
|
||||
}
|
||||
vec2 oldBounds;
|
||||
obs_sceneitem_get_bounds(sceneItem, &oldBounds);
|
||||
vec2 newBounds = oldBounds;
|
||||
if (obs_data_has_user_value(reqBounds, "x")) {
|
||||
newBounds.x = obs_data_get_double(reqBounds, "x");
|
||||
}
|
||||
if (obs_data_has_user_value(reqBounds, "y")) {
|
||||
newBounds.y = obs_data_get_double(reqBounds, "y");
|
||||
}
|
||||
obs_sceneitem_set_bounds(sceneItem, &newBounds);
|
||||
if (obs_data_has_user_value(reqBounds, "alignment")) {
|
||||
const uint32_t bounds_alignment = obs_data_get_int(reqBounds, "alignment");
|
||||
if (Utils::IsValidAlignment(bounds_alignment)) {
|
||||
obs_sceneitem_set_bounds_alignment(sceneItem, bounds_alignment);
|
||||
}
|
||||
else {
|
||||
badRequest = badBounds = true;
|
||||
obs_data_set_string(boundsError, "alignment", "invalid");
|
||||
}
|
||||
}
|
||||
if (badBounds) {
|
||||
obs_data_set_obj(errorMessage, "bounds", boundsError);
|
||||
}
|
||||
}
|
||||
|
||||
if (badRequest) {
|
||||
req->SendErrorResponse(errorMessage);
|
||||
}
|
||||
else {
|
||||
req->SendOKResponse();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset a scene item.
|
||||
*
|
||||
* @param {String (optional)} `scene-name` Name of the scene the source belogns to. Defaults to the current scene.
|
||||
* @param {String} `item` Name of the source item.
|
||||
*
|
||||
* @api requests
|
||||
* @name ResetSceneItem
|
||||
* @category scene items
|
||||
* @since 4.2.0
|
||||
*/
|
||||
void WSRequestHandler::HandleResetSceneItem(WSRequestHandler* req) {
|
||||
// TODO: remove this request, or refactor it to ResetSource
|
||||
|
||||
if (!req->hasField("item")) {
|
||||
req->SendErrorResponse("missing request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
const char* itemName = obs_data_get_string(req->data, "item");
|
||||
if (!itemName) {
|
||||
req->SendErrorResponse("invalid request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
const char* sceneName = obs_data_get_string(req->data, "scene-name");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
req->SendErrorResponse("requested scene doesn't exist");
|
||||
return;
|
||||
}
|
||||
|
||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||
if (sceneItem) {
|
||||
OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem);
|
||||
|
||||
OBSDataAutoRelease settings = obs_source_get_settings(sceneItemSource);
|
||||
obs_source_update(sceneItemSource, settings);
|
||||
|
||||
req->SendOKResponse();
|
||||
}
|
||||
else {
|
||||
req->SendErrorResponse("specified scene item doesn't exist");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or hide a specified source item in a specified scene.
|
||||
*
|
||||
* @param {String} `source` Scene item name in the specified scene.
|
||||
* @param {boolean} `render` true = shown ; false = hidden
|
||||
* @param {String (optional)} `scene-name` Name of the scene where the source resides. Defaults to the currently active scene.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetSceneItemRender
|
||||
* @category scene items
|
||||
* @since 0.3
|
||||
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||
*/
|
||||
void WSRequestHandler::HandleSetSceneItemRender(WSRequestHandler* req) {
|
||||
if (!req->hasField("source") ||
|
||||
!req->hasField("render"))
|
||||
{
|
||||
req->SendErrorResponse("missing request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
const char* itemName = obs_data_get_string(req->data, "source");
|
||||
bool isVisible = obs_data_get_bool(req->data, "render");
|
||||
|
||||
if (!itemName) {
|
||||
req->SendErrorResponse("invalid request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
const char* sceneName = obs_data_get_string(req->data, "scene-name");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
req->SendErrorResponse("requested scene doesn't exist");
|
||||
return;
|
||||
}
|
||||
|
||||
OBSSceneItemAutoRelease sceneItem =
|
||||
Utils::GetSceneItemFromName(scene, itemName);
|
||||
if (sceneItem) {
|
||||
obs_sceneitem_set_visible(sceneItem, isVisible);
|
||||
req->SendOKResponse();
|
||||
}
|
||||
else {
|
||||
req->SendErrorResponse("specified scene item doesn't exist");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the coordinates of a specified source item.
|
||||
*
|
||||
* @param {String (optional)} `scene-name` The name of the scene that the source item belongs to. Defaults to the current scene.
|
||||
* @param {String} `item` The name of the source item.
|
||||
* @param {double} `x` X coordinate.
|
||||
* @param {double} `y` Y coordinate.
|
||||
|
||||
*
|
||||
* @api requests
|
||||
* @name SetSceneItemPosition
|
||||
* @category scene items
|
||||
* @since 4.0.0
|
||||
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||
*/
|
||||
void WSRequestHandler::HandleSetSceneItemPosition(WSRequestHandler* req) {
|
||||
if (!req->hasField("item") ||
|
||||
!req->hasField("x") || !req->hasField("y")) {
|
||||
req->SendErrorResponse("missing request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
QString itemName = obs_data_get_string(req->data, "item");
|
||||
if (itemName.isEmpty()) {
|
||||
req->SendErrorResponse("invalid request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
req->SendErrorResponse("requested scene could not be found");
|
||||
return;
|
||||
}
|
||||
|
||||
OBSSceneItem sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||
if (sceneItem) {
|
||||
vec2 item_position = { 0 };
|
||||
item_position.x = obs_data_get_double(req->data, "x");
|
||||
item_position.y = obs_data_get_double(req->data, "y");
|
||||
obs_sceneitem_set_pos(sceneItem, &item_position);
|
||||
|
||||
req->SendOKResponse();
|
||||
}
|
||||
else {
|
||||
req->SendErrorResponse("specified scene item doesn't exist");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the transform of the specified source item.
|
||||
*
|
||||
* @param {String (optional)} `scene-name` The name of the scene that the source item belongs to. Defaults to the current scene.
|
||||
* @param {String} `item` The name of the source item.
|
||||
* @param {double} `x-scale` Width scale factor.
|
||||
* @param {double} `y-scale` Height scale factor.
|
||||
* @param {double} `rotation` Source item rotation (in degrees).
|
||||
*
|
||||
* @api requests
|
||||
* @name SetSceneItemTransform
|
||||
* @category scene items
|
||||
* @since 4.0.0
|
||||
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||
*/
|
||||
void WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler* req) {
|
||||
if (!req->hasField("item") ||
|
||||
!req->hasField("x-scale") ||
|
||||
!req->hasField("y-scale") ||
|
||||
!req->hasField("rotation"))
|
||||
{
|
||||
req->SendErrorResponse("missing request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
QString itemName = obs_data_get_string(req->data, "item");
|
||||
if (itemName.isEmpty()) {
|
||||
req->SendErrorResponse("invalid request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
req->SendErrorResponse("requested scene doesn't exist");
|
||||
return;
|
||||
}
|
||||
|
||||
vec2 scale;
|
||||
scale.x = obs_data_get_double(req->data, "x-scale");
|
||||
scale.y = obs_data_get_double(req->data, "y-scale");
|
||||
float rotation = obs_data_get_double(req->data, "rotation");
|
||||
|
||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||
if (sceneItem) {
|
||||
obs_sceneitem_set_scale(sceneItem, &scale);
|
||||
obs_sceneitem_set_rot(sceneItem, rotation);
|
||||
req->SendOKResponse();
|
||||
}
|
||||
else {
|
||||
req->SendErrorResponse("specified scene item doesn't exist");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the crop coordinates of the specified source item.
|
||||
*
|
||||
* @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.
|
||||
* @param {String} `item` The name of the source.
|
||||
* @param {int} `top` Pixel position of the top of the source item.
|
||||
* @param {int} `bottom` Pixel position of the bottom of the source item.
|
||||
* @param {int} `left` Pixel position of the left of the source item.
|
||||
* @param {int} `right` Pixel position of the right of the source item.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetSceneItemCrop
|
||||
* @category scene items
|
||||
* @since 4.1.0
|
||||
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||
*/
|
||||
void WSRequestHandler::HandleSetSceneItemCrop(WSRequestHandler* req) {
|
||||
if (!req->hasField("item")) {
|
||||
req->SendErrorResponse("missing request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
QString itemName = obs_data_get_string(req->data, "item");
|
||||
if (itemName.isEmpty()) {
|
||||
req->SendErrorResponse("invalid request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
req->SendErrorResponse("requested scene doesn't exist");
|
||||
return;
|
||||
}
|
||||
|
||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||
if (sceneItem) {
|
||||
struct obs_sceneitem_crop crop = { 0 };
|
||||
crop.top = obs_data_get_int(req->data, "top");
|
||||
crop.bottom = obs_data_get_int(req->data, "bottom");
|
||||
crop.left = obs_data_get_int(req->data, "left");
|
||||
crop.right = obs_data_get_int(req->data, "right");
|
||||
|
||||
obs_sceneitem_set_crop(sceneItem, &crop);
|
||||
|
||||
req->SendOKResponse();
|
||||
}
|
||||
else {
|
||||
req->SendErrorResponse("specified scene item doesn't exist");
|
||||
}
|
||||
}
|
76
src/WSRequestHandler_Scenes.cpp
Normal file
76
src/WSRequestHandler_Scenes.cpp
Normal file
@ -0,0 +1,76 @@
|
||||
#include <QString>
|
||||
#include "Utils.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
/**
|
||||
* Switch to the specified scene.
|
||||
*
|
||||
* @param {String} `scene-name` Name of the scene to switch to.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetCurrentScene
|
||||
* @category scenes
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSRequestHandler::HandleSetCurrentScene(WSRequestHandler* req) {
|
||||
if (!req->hasField("scene-name")) {
|
||||
req->SendErrorResponse("missing request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
const char* sceneName = obs_data_get_string(req->data, "scene-name");
|
||||
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName);
|
||||
|
||||
if (source) {
|
||||
obs_frontend_set_current_scene(source);
|
||||
req->SendOKResponse();
|
||||
} else {
|
||||
req->SendErrorResponse("requested scene does not exist");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current scene's name and source items.
|
||||
*
|
||||
* @return {String} `name` Name of the currently active scene.
|
||||
* @return {Source|Array} `sources` Ordered list of the current scene's source items.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetCurrentScene
|
||||
* @category scenes
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSRequestHandler::HandleGetCurrentScene(WSRequestHandler* req) {
|
||||
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
|
||||
OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(currentScene);
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_string(data, "name", obs_source_get_name(currentScene));
|
||||
obs_data_set_array(data, "sources", sceneItems);
|
||||
|
||||
req->SendOKResponse(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of scenes in the currently active profile.
|
||||
*
|
||||
* @return {String} `current-scene` Name of the currently active scene.
|
||||
* @return {Scene|Array} `scenes` Ordered list of the current profile's scenes (See `[GetCurrentScene](#getcurrentscene)` for more information).
|
||||
*
|
||||
* @api requests
|
||||
* @name GetSceneList
|
||||
* @category scenes
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSRequestHandler::HandleGetSceneList(WSRequestHandler* req) {
|
||||
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
|
||||
OBSDataArrayAutoRelease scenes = Utils::GetScenes();
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_string(data, "current-scene",
|
||||
obs_source_get_name(currentScene));
|
||||
obs_data_set_array(data, "scenes", scenes);
|
||||
|
||||
req->SendOKResponse(data);
|
||||
}
|
1003
src/WSRequestHandler_Sources.cpp
Normal file
1003
src/WSRequestHandler_Sources.cpp
Normal file
File diff suppressed because it is too large
Load Diff
288
src/WSRequestHandler_Streaming.cpp
Normal file
288
src/WSRequestHandler_Streaming.cpp
Normal file
@ -0,0 +1,288 @@
|
||||
#include <QString>
|
||||
#include "Utils.h"
|
||||
#include "WSEvents.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
#define STREAM_SERVICE_ID "websocket_custom_service"
|
||||
|
||||
/**
|
||||
* Get current streaming and recording status.
|
||||
*
|
||||
* @return {boolean} `streaming` Current streaming status.
|
||||
* @return {boolean} `recording` Current recording status.
|
||||
* @return {String (optional)} `stream-timecode` Time elapsed since streaming started (only present if currently streaming).
|
||||
* @return {String (optional)} `rec-timecode` Time elapsed since recording started (only present if currently recording).
|
||||
* @return {boolean} `preview-only` Always false. Retrocompatibility with OBSRemote.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetStreamingStatus
|
||||
* @category streaming
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler* req) {
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_bool(data, "streaming", obs_frontend_streaming_active());
|
||||
obs_data_set_bool(data, "recording", obs_frontend_recording_active());
|
||||
obs_data_set_bool(data, "preview-only", false);
|
||||
|
||||
const char* tc = nullptr;
|
||||
if (obs_frontend_streaming_active()) {
|
||||
tc = WSEvents::Instance->GetStreamingTimecode();
|
||||
obs_data_set_string(data, "stream-timecode", tc);
|
||||
bfree((void*)tc);
|
||||
}
|
||||
|
||||
if (obs_frontend_recording_active()) {
|
||||
tc = WSEvents::Instance->GetRecordingTimecode();
|
||||
obs_data_set_string(data, "rec-timecode", tc);
|
||||
bfree((void*)tc);
|
||||
}
|
||||
|
||||
req->SendOKResponse(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle streaming on or off.
|
||||
*
|
||||
* @api requests
|
||||
* @name StartStopStreaming
|
||||
* @category streaming
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSRequestHandler::HandleStartStopStreaming(WSRequestHandler* req) {
|
||||
if (obs_frontend_streaming_active())
|
||||
HandleStopStreaming(req);
|
||||
else
|
||||
HandleStartStreaming(req);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start streaming.
|
||||
* Will return an `error` if streaming is already active.
|
||||
*
|
||||
* @param {Object (optional)} `stream` Special stream configuration. Please note: these won't be saved to OBS' configuration.
|
||||
* @param {String (optional)} `stream.type` If specified ensures the type of 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 when starting the stream.
|
||||
* @param {Object (optional)} `stream.metadata` 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 streaming. May be any String, Numeric, or Boolean field.
|
||||
* @param {Object (optional)} `stream.settings` Settings for the stream.
|
||||
* @param {String (optional)} `stream.settings.server` The publish URL.
|
||||
* @param {String (optional)} `stream.settings.key` The publish key of the stream.
|
||||
* @param {boolean (optional)} `stream.settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.
|
||||
* @param {String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use-auth` is not set to `true`.
|
||||
* @param {String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use-auth` is not set to `true`.
|
||||
*
|
||||
* @api requests
|
||||
* @name StartStreaming
|
||||
* @category streaming
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleStartStreaming(WSRequestHandler* req) {
|
||||
if (obs_frontend_streaming_active() == false) {
|
||||
OBSService configuredService = obs_frontend_get_streaming_service();
|
||||
OBSService newService = nullptr;
|
||||
|
||||
// TODO: fix service memory leak
|
||||
|
||||
if (req->hasField("stream")) {
|
||||
OBSDataAutoRelease streamData = obs_data_get_obj(req->data, "stream");
|
||||
OBSDataAutoRelease newSettings = obs_data_get_obj(streamData, "settings");
|
||||
OBSDataAutoRelease newMetadata = obs_data_get_obj(streamData, "metadata");
|
||||
|
||||
OBSDataAutoRelease csHotkeys =
|
||||
obs_hotkeys_save_service(configuredService);
|
||||
|
||||
QString currentType = obs_service_get_type(configuredService);
|
||||
QString newType = obs_data_get_string(streamData, "type");
|
||||
if (newType.isEmpty() || newType.isNull()) {
|
||||
newType = currentType;
|
||||
}
|
||||
|
||||
//Supporting adding metadata parameters to key query string
|
||||
QString query = Utils::ParseDataToQueryString(newMetadata);
|
||||
if (!query.isEmpty()
|
||||
&& obs_data_has_user_value(newSettings, "key"))
|
||||
{
|
||||
const char* key = obs_data_get_string(newSettings, "key");
|
||||
int keylen = strlen(key);
|
||||
|
||||
bool hasQuestionMark = false;
|
||||
for (int i = 0; i < keylen; i++) {
|
||||
if (key[i] == '?') {
|
||||
hasQuestionMark = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasQuestionMark) {
|
||||
query.prepend('&');
|
||||
} else {
|
||||
query.prepend('?');
|
||||
}
|
||||
|
||||
query.prepend(key);
|
||||
obs_data_set_string(newSettings, "key", query.toUtf8());
|
||||
}
|
||||
|
||||
if (newType == currentType) {
|
||||
// Service type doesn't change: apply settings to current service
|
||||
|
||||
// By doing this, you can send a request to the websocket
|
||||
// that only contains settings you want to change, instead of
|
||||
// having to do a get and then change them
|
||||
|
||||
OBSDataAutoRelease currentSettings = obs_service_get_settings(configuredService);
|
||||
OBSDataAutoRelease updatedSettings = obs_data_create();
|
||||
|
||||
obs_data_apply(updatedSettings, currentSettings); //first apply the existing settings
|
||||
obs_data_apply(updatedSettings, newSettings); //then apply the settings from the request should they exist
|
||||
|
||||
newService = obs_service_create(
|
||||
newType.toUtf8(), STREAM_SERVICE_ID,
|
||||
updatedSettings, csHotkeys);
|
||||
}
|
||||
else {
|
||||
// Service type changed: override service settings
|
||||
newService = obs_service_create(
|
||||
newType.toUtf8(), STREAM_SERVICE_ID,
|
||||
newSettings, csHotkeys);
|
||||
}
|
||||
|
||||
obs_frontend_set_streaming_service(newService);
|
||||
}
|
||||
|
||||
obs_frontend_streaming_start();
|
||||
|
||||
// Stream settings provided in StartStreaming are not persisted to disk
|
||||
if (newService != nullptr) {
|
||||
obs_frontend_set_streaming_service(configuredService);
|
||||
}
|
||||
|
||||
req->SendOKResponse();
|
||||
} else {
|
||||
req->SendErrorResponse("streaming already active");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop streaming.
|
||||
* Will return an `error` if streaming is not active.
|
||||
*
|
||||
* @api requests
|
||||
* @name StopStreaming
|
||||
* @category streaming
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleStopStreaming(WSRequestHandler* req) {
|
||||
if (obs_frontend_streaming_active() == true) {
|
||||
obs_frontend_streaming_stop();
|
||||
req->SendOKResponse();
|
||||
} else {
|
||||
req->SendErrorResponse("streaming not active");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (the same as GetStreamSettings).
|
||||
*
|
||||
* @param {String} `type` The type of streaming service configuration, usually `rtmp_custom` or `rtmp_common`.
|
||||
* @param {Object} `settings` The actual settings of the stream.
|
||||
* @param {String (optional)} `settings.server` The publish URL.
|
||||
* @param {String (optional)} `settings.key` The publish key.
|
||||
* @param {boolean (optional)} `settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.
|
||||
* @param {String (optional)} `settings.username` The username for the streaming service.
|
||||
* @param {String (optional)} `settings.password` The password for the streaming service.
|
||||
* @param {boolean} `save` Persist the settings to disk.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetStreamSettings
|
||||
* @category streaming
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleSetStreamSettings(WSRequestHandler* req) {
|
||||
OBSService service = obs_frontend_get_streaming_service();
|
||||
|
||||
OBSDataAutoRelease requestSettings = obs_data_get_obj(req->data, "settings");
|
||||
if (!requestSettings) {
|
||||
req->SendErrorResponse("'settings' are required'");
|
||||
return;
|
||||
}
|
||||
|
||||
QString serviceType = obs_service_get_type(service);
|
||||
QString requestedType = obs_data_get_string(req->data, "type");
|
||||
|
||||
if (requestedType != nullptr && requestedType != serviceType) {
|
||||
OBSDataAutoRelease hotkeys = obs_hotkeys_save_service(service);
|
||||
service = obs_service_create(
|
||||
requestedType.toUtf8(), STREAM_SERVICE_ID, requestSettings, hotkeys);
|
||||
} else {
|
||||
// If type isn't changing, we should overlay the settings we got
|
||||
// to the existing settings. By doing so, you can send a request that
|
||||
// only contains the settings you want to change, instead of having to
|
||||
// do a get and then change them
|
||||
|
||||
OBSDataAutoRelease existingSettings = obs_service_get_settings(service);
|
||||
OBSDataAutoRelease newSettings = obs_data_create();
|
||||
|
||||
// Apply existing settings
|
||||
obs_data_apply(newSettings, existingSettings);
|
||||
// Then apply the settings from the request
|
||||
obs_data_apply(newSettings, requestSettings);
|
||||
|
||||
obs_service_update(service, newSettings);
|
||||
}
|
||||
|
||||
//if save is specified we should immediately save the streaming service
|
||||
if (obs_data_get_bool(req->data, "save")) {
|
||||
obs_frontend_save_streaming_service();
|
||||
}
|
||||
|
||||
OBSDataAutoRelease serviceSettings = obs_service_get_settings(service);
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_string(response, "type", requestedType.toUtf8());
|
||||
obs_data_set_obj(response, "settings", serviceSettings);
|
||||
|
||||
req->SendOKResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current streaming server settings.
|
||||
*
|
||||
* @return {String} `type` The type of streaming service configuration. Possible values: 'rtmp_custom' or 'rtmp_common'.
|
||||
* @return {Object} `settings` Stream settings object.
|
||||
* @return {String} `settings.server` The publish URL.
|
||||
* @return {String} `settings.key` The publish key of the stream.
|
||||
* @return {boolean} `settings.use-auth` Indicates whether audentication should be used when connecting to the streaming server.
|
||||
* @return {String} `settings.username` The username to use when accessing the streaming server. Only present if `use-auth` is `true`.
|
||||
* @return {String} `settings.password` The password to use when accessing the streaming server. Only present if `use-auth` is `true`.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetStreamSettings
|
||||
* @category streaming
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleGetStreamSettings(WSRequestHandler* req) {
|
||||
OBSService service = obs_frontend_get_streaming_service();
|
||||
|
||||
const char* serviceType = obs_service_get_type(service);
|
||||
OBSDataAutoRelease settings = obs_service_get_settings(service);
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_string(response, "type", serviceType);
|
||||
obs_data_set_obj(response, "settings", settings);
|
||||
|
||||
req->SendOKResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current streaming server settings to disk.
|
||||
*
|
||||
* @api requests
|
||||
* @name SaveStreamSettings
|
||||
* @category streaming
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleSaveStreamSettings(WSRequestHandler* req) {
|
||||
obs_frontend_save_streaming_service();
|
||||
req->SendOKResponse();
|
||||
}
|
173
src/WSRequestHandler_StudioMode.cpp
Normal file
173
src/WSRequestHandler_StudioMode.cpp
Normal file
@ -0,0 +1,173 @@
|
||||
#include <QString>
|
||||
#include "Utils.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
/**
|
||||
* Indicates if Studio Mode is currently enabled.
|
||||
*
|
||||
* @return {boolean} `studio-mode` Indicates if Studio Mode is enabled.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetStudioModeStatus
|
||||
* @category studio mode
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleGetStudioModeStatus(WSRequestHandler* req) {
|
||||
bool previewActive = obs_frontend_preview_program_mode_active();
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_bool(response, "studio-mode", previewActive);
|
||||
|
||||
req->SendOKResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the currently previewed scene and its list of sources.
|
||||
* Will return an `error` if Studio Mode is not enabled.
|
||||
*
|
||||
* @return {String} `name` The name of the active preview scene.
|
||||
* @return {Source|Array} `sources`
|
||||
*
|
||||
* @api requests
|
||||
* @name GetPreviewScene
|
||||
* @category studio mode
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleGetPreviewScene(WSRequestHandler* req) {
|
||||
if (!obs_frontend_preview_program_mode_active()) {
|
||||
req->SendErrorResponse("studio mode not enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
OBSSourceAutoRelease scene = obs_frontend_get_current_preview_scene();
|
||||
OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(scene);
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_string(data, "name", obs_source_get_name(scene));
|
||||
obs_data_set_array(data, "sources", sceneItems);
|
||||
|
||||
req->SendOKResponse(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active preview scene.
|
||||
* Will return an `error` if Studio Mode is not enabled.
|
||||
*
|
||||
* @param {String} `scene-name` The name of the scene to preview.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetPreviewScene
|
||||
* @category studio mode
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleSetPreviewScene(WSRequestHandler* req) {
|
||||
if (!obs_frontend_preview_program_mode_active()) {
|
||||
req->SendErrorResponse("studio mode not enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!req->hasField("scene-name")) {
|
||||
req->SendErrorResponse("missing request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
const char* scene_name = obs_data_get_string(req->data, "scene-name");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(scene_name);
|
||||
|
||||
if (scene) {
|
||||
obs_frontend_set_current_preview_scene(scene);
|
||||
req->SendOKResponse();
|
||||
} else {
|
||||
req->SendErrorResponse("specified scene doesn't exist");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transitions the currently previewed scene to the main output.
|
||||
* Will return an `error` if Studio Mode is not enabled.
|
||||
*
|
||||
* @param {Object (optional)} `with-transition` Change the active transition before switching scenes. Defaults to the active transition.
|
||||
* @param {String} `with-transition.name` Name of the transition.
|
||||
* @param {int (optional)} `with-transition.duration` Transition duration (in milliseconds).
|
||||
*
|
||||
* @api requests
|
||||
* @name TransitionToProgram
|
||||
* @category studio mode
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleTransitionToProgram(WSRequestHandler* req) {
|
||||
if (!obs_frontend_preview_program_mode_active()) {
|
||||
req->SendErrorResponse("studio mode not enabled");
|
||||
return;
|
||||
}
|
||||
|
||||
if (req->hasField("with-transition")) {
|
||||
OBSDataAutoRelease transitionInfo =
|
||||
obs_data_get_obj(req->data, "with-transition");
|
||||
|
||||
if (obs_data_has_user_value(transitionInfo, "name")) {
|
||||
QString transitionName =
|
||||
obs_data_get_string(transitionInfo, "name");
|
||||
if (transitionName.isEmpty()) {
|
||||
req->SendErrorResponse("invalid request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
bool success = Utils::SetTransitionByName(transitionName);
|
||||
if (!success) {
|
||||
req->SendErrorResponse("specified transition doesn't exist");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (obs_data_has_user_value(transitionInfo, "duration")) {
|
||||
int transitionDuration =
|
||||
obs_data_get_int(transitionInfo, "duration");
|
||||
Utils::SetTransitionDuration(transitionDuration);
|
||||
}
|
||||
}
|
||||
|
||||
Utils::TransitionToProgram();
|
||||
req->SendOKResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enables Studio Mode.
|
||||
*
|
||||
* @api requests
|
||||
* @name EnableStudioMode
|
||||
* @category studio mode
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleEnableStudioMode(WSRequestHandler* req) {
|
||||
obs_frontend_set_preview_program_mode(true);
|
||||
req->SendOKResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables Studio Mode.
|
||||
*
|
||||
* @api requests
|
||||
* @name DisableStudioMode
|
||||
* @category studio mode
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleDisableStudioMode(WSRequestHandler* req) {
|
||||
obs_frontend_set_preview_program_mode(false);
|
||||
req->SendOKResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles Studio Mode.
|
||||
*
|
||||
* @api requests
|
||||
* @name ToggleStudioMode
|
||||
* @category studio mode
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleToggleStudioMode(WSRequestHandler* req) {
|
||||
bool previewProgramMode = obs_frontend_preview_program_mode_active();
|
||||
obs_frontend_set_preview_program_mode(!previewProgramMode);
|
||||
req->SendOKResponse();
|
||||
}
|
126
src/WSRequestHandler_Transitions.cpp
Normal file
126
src/WSRequestHandler_Transitions.cpp
Normal file
@ -0,0 +1,126 @@
|
||||
#include <QString>
|
||||
#include "Utils.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
/**
|
||||
* List of all transitions available in the frontend's dropdown menu.
|
||||
*
|
||||
* @return {String} `current-transition` Name of the currently active transition.
|
||||
* @return {Object|Array} `transitions` List of transitions.
|
||||
* @return {String} `transitions[].name` Name of the transition.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetTransitionList
|
||||
* @category transitions
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleGetTransitionList(WSRequestHandler* req) {
|
||||
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
|
||||
obs_frontend_source_list transitionList = {};
|
||||
obs_frontend_get_transitions(&transitionList);
|
||||
|
||||
OBSDataArrayAutoRelease transitions = obs_data_array_create();
|
||||
for (size_t i = 0; i < transitionList.sources.num; i++) {
|
||||
OBSSource transition = transitionList.sources.array[i];
|
||||
|
||||
OBSDataAutoRelease obj = obs_data_create();
|
||||
obs_data_set_string(obj, "name", obs_source_get_name(transition));
|
||||
obs_data_array_push_back(transitions, obj);
|
||||
}
|
||||
obs_frontend_source_list_free(&transitionList);
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_string(response, "current-transition",
|
||||
obs_source_get_name(currentTransition));
|
||||
obs_data_set_array(response, "transitions", transitions);
|
||||
|
||||
req->SendOKResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the currently selected transition in the frontend's dropdown menu.
|
||||
*
|
||||
* @return {String} `name` Name of the selected transition.
|
||||
* @return {int (optional)} `duration` Transition duration (in milliseconds) if supported by the transition.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetCurrentTransition
|
||||
* @category transitions
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler* req) {
|
||||
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_string(response, "name",
|
||||
obs_source_get_name(currentTransition));
|
||||
|
||||
if (!obs_transition_fixed(currentTransition))
|
||||
obs_data_set_int(response, "duration", Utils::GetTransitionDuration());
|
||||
|
||||
req->SendOKResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active transition.
|
||||
*
|
||||
* @param {String} `transition-name` The name of the transition.
|
||||
*
|
||||
* @api requests
|
||||
* @name SetCurrentTransition
|
||||
* @category transitions
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSRequestHandler::HandleSetCurrentTransition(WSRequestHandler* req) {
|
||||
if (!req->hasField("transition-name")) {
|
||||
req->SendErrorResponse("missing request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
QString name = obs_data_get_string(req->data, "transition-name");
|
||||
bool success = Utils::SetTransitionByName(name);
|
||||
if (success)
|
||||
req->SendOKResponse();
|
||||
else
|
||||
req->SendErrorResponse("requested transition does not exist");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the duration of the currently selected transition if supported.
|
||||
*
|
||||
* @param {int} `duration` Desired duration of the transition (in milliseconds).
|
||||
*
|
||||
* @api requests
|
||||
* @name SetTransitionDuration
|
||||
* @category transitions
|
||||
* @since 4.0.0
|
||||
*/
|
||||
void WSRequestHandler::HandleSetTransitionDuration(WSRequestHandler* req) {
|
||||
if (!req->hasField("duration")) {
|
||||
req->SendErrorResponse("missing request parameters");
|
||||
return;
|
||||
}
|
||||
|
||||
int ms = obs_data_get_int(req->data, "duration");
|
||||
Utils::SetTransitionDuration(ms);
|
||||
req->SendOKResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the duration of the currently selected transition if supported.
|
||||
*
|
||||
* @return {int} `transition-duration` Duration of the current transition (in milliseconds).
|
||||
*
|
||||
* @api requests
|
||||
* @name GetTransitionDuration
|
||||
* @category transitions
|
||||
* @since 4.1.0
|
||||
*/
|
||||
void WSRequestHandler::HandleGetTransitionDuration(WSRequestHandler* req) {
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_int(response, "transition-duration",
|
||||
Utils::GetTransitionDuration());
|
||||
|
||||
req->SendOKResponse(response);
|
||||
}
|
168
src/WSServer.cpp
Normal file
168
src/WSServer.cpp
Normal file
@ -0,0 +1,168 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <QtWebSockets/QWebSocket>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QMainWindow>
|
||||
#include <QMessageBox>
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
#include "WSServer.h"
|
||||
#include "obs-websocket.h"
|
||||
#include "Config.h"
|
||||
#include "Utils.h"
|
||||
|
||||
QT_USE_NAMESPACE
|
||||
|
||||
WSServer* WSServer::Instance = nullptr;
|
||||
|
||||
WSServer::WSServer(QObject* parent)
|
||||
: QObject(parent),
|
||||
_wsServer(Q_NULLPTR),
|
||||
_clients(),
|
||||
_clMutex(QMutex::Recursive)
|
||||
{
|
||||
_wsServer = new QWebSocketServer(
|
||||
QStringLiteral("obs-websocket"),
|
||||
QWebSocketServer::NonSecureMode);
|
||||
}
|
||||
|
||||
WSServer::~WSServer() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void WSServer::Start(quint16 port) {
|
||||
if (port == _wsServer->serverPort())
|
||||
return;
|
||||
|
||||
if(_wsServer->isListening())
|
||||
Stop();
|
||||
|
||||
bool serverStarted = _wsServer->listen(QHostAddress::Any, port);
|
||||
if (serverStarted) {
|
||||
blog(LOG_INFO, "server started successfully on TCP port %d", port);
|
||||
|
||||
connect(_wsServer, SIGNAL(newConnection()),
|
||||
this, SLOT(onNewConnection()));
|
||||
}
|
||||
else {
|
||||
QString errorString = _wsServer->errorString();
|
||||
blog(LOG_ERROR,
|
||||
"error: failed to start server on TCP port %d: %s",
|
||||
port, errorString.toUtf8().constData());
|
||||
|
||||
QMainWindow* mainWindow = (QMainWindow*)obs_frontend_get_main_window();
|
||||
|
||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||
QString title = tr("OBSWebsocket.Server.StartFailed.Title");
|
||||
QString msg = tr("OBSWebsocket.Server.StartFailed.Message").arg(port);
|
||||
obs_frontend_pop_ui_translation();
|
||||
|
||||
QMessageBox::warning(mainWindow, title, msg);
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::Stop() {
|
||||
QMutexLocker locker(&_clMutex);
|
||||
for(QWebSocket* pClient : _clients) {
|
||||
pClient->close();
|
||||
}
|
||||
locker.unlock();
|
||||
|
||||
_wsServer->close();
|
||||
|
||||
blog(LOG_INFO, "server stopped successfully");
|
||||
}
|
||||
|
||||
void WSServer::broadcast(QString message) {
|
||||
QMutexLocker locker(&_clMutex);
|
||||
for(QWebSocket* pClient : _clients) {
|
||||
if (Config::Current()->AuthRequired
|
||||
&& (pClient->property(PROP_AUTHENTICATED).toBool() == false)) {
|
||||
// Skip this client if unauthenticated
|
||||
continue;
|
||||
}
|
||||
pClient->sendTextMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::onNewConnection() {
|
||||
QWebSocket* pSocket = _wsServer->nextPendingConnection();
|
||||
if (pSocket) {
|
||||
connect(pSocket, SIGNAL(textMessageReceived(const QString&)),
|
||||
this, SLOT(onTextMessageReceived(QString)));
|
||||
connect(pSocket, SIGNAL(disconnected()),
|
||||
this, SLOT(onSocketDisconnected()));
|
||||
|
||||
pSocket->setProperty(PROP_AUTHENTICATED, false);
|
||||
|
||||
QMutexLocker locker(&_clMutex);
|
||||
_clients << pSocket;
|
||||
locker.unlock();
|
||||
|
||||
QHostAddress clientAddr = pSocket->peerAddress();
|
||||
QString clientIp = Utils::FormatIPAddress(clientAddr);
|
||||
|
||||
blog(LOG_INFO, "new client connection from %s:%d",
|
||||
clientIp.toUtf8().constData(), pSocket->peerPort());
|
||||
|
||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||
QString title = tr("OBSWebsocket.NotifyConnect.Title");
|
||||
QString msg = tr("OBSWebsocket.NotifyConnect.Message")
|
||||
.arg(Utils::FormatIPAddress(clientAddr));
|
||||
obs_frontend_pop_ui_translation();
|
||||
|
||||
Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title);
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::onTextMessageReceived(QString message) {
|
||||
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
|
||||
if (pSocket) {
|
||||
WSRequestHandler handler(pSocket);
|
||||
handler.processIncomingMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
void WSServer::onSocketDisconnected() {
|
||||
QWebSocket* pSocket = qobject_cast<QWebSocket*>(sender());
|
||||
if (pSocket) {
|
||||
pSocket->setProperty(PROP_AUTHENTICATED, false);
|
||||
|
||||
QMutexLocker locker(&_clMutex);
|
||||
_clients.removeAll(pSocket);
|
||||
locker.unlock();
|
||||
|
||||
pSocket->deleteLater();
|
||||
|
||||
QHostAddress clientAddr = pSocket->peerAddress();
|
||||
QString clientIp = Utils::FormatIPAddress(clientAddr);
|
||||
|
||||
blog(LOG_INFO, "client %s:%d disconnected",
|
||||
clientIp.toUtf8().constData(), pSocket->peerPort());
|
||||
|
||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||
QString title = tr("OBSWebsocket.NotifyDisconnect.Title");
|
||||
QString msg = tr("OBSWebsocket.NotifyDisconnect.Message")
|
||||
.arg(Utils::FormatIPAddress(clientAddr));
|
||||
obs_frontend_pop_ui_translation();
|
||||
|
||||
Utils::SysTrayNotify(msg, QSystemTrayIcon::Information, title);
|
||||
}
|
||||
}
|
@ -19,37 +19,34 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#ifndef WSSERVER_H
|
||||
#define WSSERVER_H
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QMutex>
|
||||
#include <QObject>
|
||||
#include <QList>
|
||||
#include <QMutex>
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
|
||||
QT_FORWARD_DECLARE_CLASS(QWebSocket)
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
class WSServer : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WSServer(QObject* parent = Q_NULLPTR);
|
||||
virtual ~WSServer();
|
||||
void Start(quint16 port);
|
||||
void Stop();
|
||||
void broadcast(QString message);
|
||||
static WSServer* Instance;
|
||||
|
||||
class WSServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void onNewConnection();
|
||||
void onTextMessageReceived(QString message);
|
||||
void onSocketDisconnected();
|
||||
|
||||
public:
|
||||
explicit WSServer(QObject* parent = Q_NULLPTR);
|
||||
virtual ~WSServer();
|
||||
void Start(quint16 port);
|
||||
void Stop();
|
||||
void broadcast(QString message);
|
||||
static WSServer* Instance;
|
||||
|
||||
private Q_SLOTS:
|
||||
void onNewConnection();
|
||||
void textMessageReceived(QString message);
|
||||
void socketDisconnected();
|
||||
|
||||
private:
|
||||
QWebSocketServer* _wsServer;
|
||||
QList<QWebSocket*> _clients;
|
||||
QMutex _clMutex;
|
||||
QThread* _serverThread;
|
||||
private:
|
||||
QWebSocketServer* _wsServer;
|
||||
QList<QWebSocket*> _clients;
|
||||
QMutex _clMutex;
|
||||
};
|
||||
|
||||
#endif // WSSERVER_H
|
105
src/forms/settings-dialog.cpp
Normal file
105
src/forms/settings-dialog.cpp
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
#include "../obs-websocket.h"
|
||||
#include "../Config.h"
|
||||
#include "../WSServer.h"
|
||||
#include "settings-dialog.h"
|
||||
#include "ui_settings-dialog.h"
|
||||
|
||||
#define CHANGE_ME "changeme"
|
||||
|
||||
SettingsDialog::SettingsDialog(QWidget* parent) :
|
||||
QDialog(parent, Qt::Dialog),
|
||||
ui(new Ui::SettingsDialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->authRequired, &QCheckBox::stateChanged,
|
||||
this, &SettingsDialog::AuthCheckboxChanged);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted,
|
||||
this, &SettingsDialog::FormAccepted);
|
||||
|
||||
|
||||
AuthCheckboxChanged();
|
||||
}
|
||||
|
||||
void SettingsDialog::showEvent(QShowEvent* event) {
|
||||
Config* conf = Config::Current();
|
||||
|
||||
ui->serverEnabled->setChecked(conf->ServerEnabled);
|
||||
ui->serverPort->setValue(conf->ServerPort);
|
||||
|
||||
ui->debugEnabled->setChecked(conf->DebugEnabled);
|
||||
ui->alertsEnabled->setChecked(conf->AlertsEnabled);
|
||||
|
||||
ui->authRequired->setChecked(conf->AuthRequired);
|
||||
ui->password->setText(CHANGE_ME);
|
||||
}
|
||||
|
||||
void SettingsDialog::ToggleShowHide() {
|
||||
if (!isVisible())
|
||||
setVisible(true);
|
||||
else
|
||||
setVisible(false);
|
||||
}
|
||||
|
||||
void SettingsDialog::AuthCheckboxChanged() {
|
||||
if (ui->authRequired->isChecked())
|
||||
ui->password->setEnabled(true);
|
||||
else
|
||||
ui->password->setEnabled(false);
|
||||
}
|
||||
|
||||
void SettingsDialog::FormAccepted() {
|
||||
Config* conf = Config::Current();
|
||||
|
||||
conf->ServerEnabled = ui->serverEnabled->isChecked();
|
||||
conf->ServerPort = ui->serverPort->value();
|
||||
|
||||
conf->DebugEnabled = ui->debugEnabled->isChecked();
|
||||
conf->AlertsEnabled = ui->alertsEnabled->isChecked();
|
||||
|
||||
if (ui->authRequired->isChecked()) {
|
||||
if (ui->password->text() != CHANGE_ME) {
|
||||
conf->SetPassword(ui->password->text());
|
||||
}
|
||||
|
||||
if (!Config::Current()->Secret.isEmpty())
|
||||
conf->AuthRequired = true;
|
||||
else
|
||||
conf->AuthRequired = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
conf->AuthRequired = false;
|
||||
}
|
||||
|
||||
conf->Save();
|
||||
|
||||
if (conf->ServerEnabled)
|
||||
WSServer::Instance->Start(conf->ServerPort);
|
||||
else
|
||||
WSServer::Instance->Stop();
|
||||
}
|
||||
|
||||
SettingsDialog::~SettingsDialog() {
|
||||
delete ui;
|
||||
}
|
@ -27,20 +27,20 @@ class SettingsDialog;
|
||||
|
||||
class SettingsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit SettingsDialog(QWidget* parent = 0);
|
||||
~SettingsDialog();
|
||||
void showEvent(QShowEvent* event);
|
||||
void ToggleShowHide();
|
||||
explicit SettingsDialog(QWidget* parent = 0);
|
||||
~SettingsDialog();
|
||||
void showEvent(QShowEvent* event);
|
||||
void ToggleShowHide();
|
||||
|
||||
private Q_SLOTS:
|
||||
void AuthCheckboxChanged();
|
||||
void FormAccepted();
|
||||
void AuthCheckboxChanged();
|
||||
void FormAccepted();
|
||||
|
||||
private:
|
||||
Ui::SettingsDialog* ui;
|
||||
Ui::SettingsDialog* ui;
|
||||
};
|
||||
|
||||
#endif // SETTINGSDIALOG_H
|
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>407</width>
|
||||
<height>175</height>
|
||||
<height>195</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
@ -80,6 +80,16 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QCheckBox" name="alertsEnabled">
|
||||
<property name="text">
|
||||
<string>OBSWebsocket.Settings.AlertsEnable</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QCheckBox" name="debugEnabled">
|
||||
<property name="text">
|
||||
<string>OBSWebsocket.Settings.DebugEnable</string>
|
||||
@ -113,11 +123,11 @@
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>248</x>
|
||||
<y>274</y>
|
||||
<y>294</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>294</y>
|
||||
<y>314</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
@ -129,11 +139,11 @@
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>316</x>
|
||||
<y>280</y>
|
||||
<y>300</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>286</x>
|
||||
<y>294</y>
|
||||
<y>314</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
80
src/obs-websocket.cpp
Normal file
80
src/obs-websocket.cpp
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
obs-websocket
|
||||
Copyright (C) 2016-2017 Stéphane Lepin <stephane.lepin@gmail.com>
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License along
|
||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <obs-module.h>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <QAction>
|
||||
#include <QMainWindow>
|
||||
#include <QTimer>
|
||||
|
||||
#include "obs-websocket.h"
|
||||
#include "WSServer.h"
|
||||
#include "WSEvents.h"
|
||||
#include "Config.h"
|
||||
#include "forms/settings-dialog.h"
|
||||
|
||||
void ___source_dummy_addref(obs_source_t*) {}
|
||||
void ___sceneitem_dummy_addref(obs_sceneitem_t*) {}
|
||||
void ___data_dummy_addref(obs_data_t*) {}
|
||||
void ___data_array_dummy_addref(obs_data_array_t*) {}
|
||||
void ___output_dummy_addref(obs_output_t*) {}
|
||||
|
||||
OBS_DECLARE_MODULE()
|
||||
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
|
||||
|
||||
SettingsDialog* settings_dialog;
|
||||
|
||||
bool obs_module_load(void) {
|
||||
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
|
||||
Config* config = Config::Current();
|
||||
config->Load();
|
||||
|
||||
WSServer::Instance = new WSServer();
|
||||
WSEvents::Instance = new WSEvents(WSServer::Instance);
|
||||
|
||||
if (config->ServerEnabled)
|
||||
WSServer::Instance->Start(config->ServerPort);
|
||||
|
||||
// UI setup
|
||||
QAction* menu_action = (QAction*)obs_frontend_add_tools_menu_qaction(
|
||||
obs_module_text("OBSWebsocket.Menu.SettingsItem"));
|
||||
|
||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||
QMainWindow* main_window = (QMainWindow*)obs_frontend_get_main_window();
|
||||
settings_dialog = new SettingsDialog(main_window);
|
||||
obs_frontend_pop_ui_translation();
|
||||
|
||||
auto menu_cb = [] {
|
||||
settings_dialog->ToggleShowHide();
|
||||
};
|
||||
menu_action->connect(menu_action, &QAction::triggered, menu_cb);
|
||||
|
||||
// Loading finished
|
||||
blog(LOG_INFO, "module loaded!");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void obs_module_unload() {
|
||||
blog(LOG_INFO, "goodbye!");
|
||||
}
|
||||
|
@ -19,9 +19,27 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#ifndef OBSWEBSOCKET_H
|
||||
#define OBSWEBSOCKET_H
|
||||
|
||||
#include <obs.hpp>
|
||||
|
||||
void ___source_dummy_addref(obs_source_t*);
|
||||
void ___sceneitem_dummy_addref(obs_sceneitem_t*);
|
||||
void ___data_dummy_addref(obs_data_t*);
|
||||
void ___data_array_dummy_addref(obs_data_array_t*);
|
||||
void ___output_dummy_addref(obs_output_t*);
|
||||
|
||||
using OBSSourceAutoRelease =
|
||||
OBSRef<obs_source_t*, ___source_dummy_addref, obs_source_release>;
|
||||
using OBSSceneItemAutoRelease =
|
||||
OBSRef<obs_sceneitem_t*, ___sceneitem_dummy_addref, obs_sceneitem_release>;
|
||||
using OBSDataAutoRelease =
|
||||
OBSRef<obs_data_t*, ___data_dummy_addref, obs_data_release>;
|
||||
using OBSDataArrayAutoRelease =
|
||||
OBSRef<obs_data_array_t*, ___data_array_dummy_addref, obs_data_array_release>;
|
||||
using OBSOutputAutoRelease =
|
||||
OBSRef<obs_output_t*, ___output_dummy_addref, obs_output_release>;
|
||||
|
||||
#define PROP_AUTHENTICATED "wsclient_authenticated"
|
||||
#define OBS_WEBSOCKET_VERSION "4.1.0"
|
||||
#define API_VERSION 1.3
|
||||
#define OBS_WEBSOCKET_VERSION "4.3.1"
|
||||
|
||||
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)
|
||||
|
Reference in New Issue
Block a user