mirror of
https://github.com/Palakis/obs-websocket.git
synced 2024-08-30 18:12:16 +00:00
Compare commits
358 Commits
Author | SHA1 | Date | |
---|---|---|---|
5d12dfa368 | |||
c4282011fa | |||
9ddc22fa94 | |||
517788e86a | |||
ed29ec528d | |||
481b26d09d | |||
da66ad6d41 | |||
1f75b305c3 | |||
0802d03d74 | |||
3970674003 | |||
19b33a8558 | |||
7bb91da1d0 | |||
e3e62c3903 | |||
631ed022ed | |||
590943ed95 | |||
e9c17c9a1d | |||
f8c8e42ae9 | |||
0b0560019a | |||
133d3fdda7 | |||
53e98dbe15 | |||
0eaa9187ea | |||
344f5bda69 | |||
33b080b3b8 | |||
1c85894472 | |||
ba4e5959b1 | |||
5534f9a248 | |||
eb206549ff | |||
0852f32b05 | |||
00da210365 | |||
bce6c86e60 | |||
bed6a1b1e2 | |||
497443f012 | |||
75a06afab0 | |||
56371a7d80 | |||
2810787156 | |||
6b53cb59a5 | |||
71392613b2 | |||
ff21f5b357 | |||
98db248776 | |||
ed4872b94c | |||
8c5c6958cf | |||
f9c81f99f2 | |||
88c72cd80a | |||
728ea16701 | |||
a3bc9f768a | |||
8d88bc19ed | |||
333ffa0e89 | |||
4ded810ba9 | |||
b2aa54f3f8 | |||
97836cc5eb | |||
b1df0dca97 | |||
4a6816575a | |||
31e2937b8d | |||
0f434004a8 | |||
ba75c45cee | |||
47492c3fa2 | |||
819917c4bf | |||
1ce0fd643c | |||
a7d02a79a9 | |||
645cbf9888 | |||
b2d39ab2d7 | |||
5843521cf1 | |||
4fbc45b40b | |||
2719da3685 | |||
f61bc809dd | |||
19f9593ac1 | |||
e277cae799 | |||
05baf6b8ac | |||
758441d65d | |||
631a008fa0 | |||
54e79936e5 | |||
a833822eae | |||
cfa8d8d180 | |||
b692756260 | |||
dbc62ad63a | |||
2ac97dea35 | |||
9451814299 | |||
5e01283018 | |||
f7512c3cc0 | |||
f4997375e9 | |||
fbad54795e | |||
2ca85d05f0 | |||
67e89e79be | |||
e4676668bb | |||
3604995c2f | |||
8b731f3ba4 | |||
83f702fbab | |||
fbebf1c7d3 | |||
be14947668 | |||
0fcf770043 | |||
51ec3ede1f | |||
4e2302936f | |||
a6c6a42669 | |||
c486bc64c4 | |||
3709ea1a95 | |||
be7fa79327 | |||
c1660f8ca2 | |||
5e41eaf3dd | |||
898e761988 | |||
846d52ebe5 | |||
77ac6a7a26 | |||
f23ba0c513 | |||
6a733bbb13 | |||
540e1f562f | |||
bfcd16ea28 | |||
3a8703de87 | |||
247ca71bf9 | |||
88d39ab47a | |||
a5af45fb31 | |||
dc04cb54ab | |||
c9fa82f073 | |||
c418cbf4ef | |||
3450c6b9a3 | |||
1ada33a4f0 | |||
a669852694 | |||
a5b6ea6c4c | |||
fe217ef5df | |||
e0a25dbf48 | |||
86191aff7f | |||
6bed39e4fa | |||
2a7aa432a9 | |||
4e19e41460 | |||
061f5d5099 | |||
796bdebd90 | |||
010102da25 | |||
d8f2aeb004 | |||
5173338949 | |||
a8b00b026e | |||
d4db0bdfe5 | |||
b26cd901ca | |||
1e2fc783e5 | |||
8667333416 | |||
5088d7dd86 | |||
e78f26e9c0 | |||
be10227829 | |||
ef47c172c5 | |||
053faa1661 | |||
6d63779c70 | |||
62ad1a483c | |||
f4f760a231 | |||
0eaef1ac3b | |||
27245cca1a | |||
afcc11af57 | |||
9fc41e4245 | |||
a0e0910117 | |||
8a7d7a41a4 | |||
45d5f4a760 | |||
101b795c0a | |||
8311bd3fd9 | |||
ec1926f7a6 | |||
e1c92956f8 | |||
9233d4f6dc | |||
f2f5661625 | |||
4338cf57e6 | |||
a0dce77a2f | |||
ca93882870 | |||
22fbb4262c | |||
2845fd07a7 | |||
c02f40beb6 | |||
79a7b444a2 | |||
9c11bbc7a1 | |||
e1cb57563a | |||
1057d765f7 | |||
30db1a8a63 | |||
c7b49b28c2 | |||
40ea7cfe15 | |||
b3b2ae267a | |||
88176f4d2b | |||
6917ac48a3 | |||
c326d65ad3 | |||
e1a2f0c0f4 | |||
6953bc6105 | |||
209555d36d | |||
e65958f33e | |||
02fea8938a | |||
f5277e4931 | |||
d78ed32df5 | |||
8447395482 | |||
915a7b6e15 | |||
b70c39789e | |||
865cb4857b | |||
774abd66fa | |||
e52d86e6f9 | |||
3c0b5b0b43 | |||
46068573c5 | |||
06c29b7810 | |||
631452567d | |||
6c34f00cfe | |||
208c7f18b2 | |||
5e393ed3c4 | |||
8b16abd370 | |||
94c232d78e | |||
4b41c52522 | |||
6bfe533a20 | |||
f0b8aad4b1 | |||
796cf36a7f | |||
7706d66754 | |||
d992536914 | |||
286b76672c | |||
f0d1b5a1e4 | |||
71ad8930d8 | |||
0f6cbb0c5c | |||
78584b3194 | |||
c0eb4d652b | |||
edbb28038f | |||
3b7e9b4eba | |||
6d0d07abfe | |||
e9f90b4990 | |||
d25b65c124 | |||
5864864123 | |||
2f244ae37e | |||
f83317f7c8 | |||
973b4b9f70 | |||
9389ceaf4f | |||
928dc30810 | |||
c9d6d10995 | |||
9323e1cf59 | |||
93a456ca82 | |||
f6693d5587 | |||
fd09a0ce54 | |||
50d3a2600e | |||
7e1b05933f | |||
03c4fdc607 | |||
0485cf51ff | |||
ba9201831b | |||
f660036a25 | |||
f09cbfddbc | |||
a12f1dcb13 | |||
d2bb1ddc29 | |||
528d81106e | |||
f0bed24742 | |||
5b54d8135e | |||
31559051f6 | |||
e6dbc9bfdf | |||
d1ee9d83b5 | |||
ffa6371e3d | |||
ee6e241144 | |||
bdf1023b16 | |||
9a8f248a75 | |||
6845cda146 | |||
4488b2b7a5 | |||
7dd8cb5d8d | |||
ebf631a15f | |||
3770b75a78 | |||
5d81b61325 | |||
a8e5171b40 | |||
a3e0abb6ce | |||
c51f20eb99 | |||
be897c4df2 | |||
11b948fb69 | |||
070660848b | |||
61af0ec9c6 | |||
897f115363 | |||
b8fcf0355c | |||
5bdff87e3f | |||
31991a3567 | |||
25c369d422 | |||
8d396b1518 | |||
ba327b746a | |||
7d11f912c9 | |||
919cdfb5a9 | |||
da9dd6f775 | |||
8b841f026e | |||
5342b39640 | |||
fc08add504 | |||
20379ac61e | |||
af40aa59ab | |||
f9afc5597a | |||
40178e4661 | |||
ad56abd6f5 | |||
9073a09d84 | |||
2a4e6fce3f | |||
0a3a407217 | |||
9ca9cf80df | |||
6c881a5da7 | |||
5322acd58c | |||
ce489e53c6 | |||
0c95e509dc | |||
46a006ed54 | |||
0cc9378d05 | |||
f3edb2ee68 | |||
a539f23194 | |||
6b6dec79c5 | |||
d7f5c042bf | |||
2fe9812b26 | |||
1c15c3c7a3 | |||
1ee5857139 | |||
2f9c2b0d65 | |||
6e5161f43b | |||
d492d74a64 | |||
26506731c5 | |||
b0c03d4f47 | |||
3bc8348c32 | |||
a034fbba83 | |||
73b5261c49 | |||
cee0cbebc2 | |||
9723147429 | |||
dca385ae87 | |||
71f792944c | |||
6041c4acd2 | |||
0c7529705e | |||
63f1ea1cec | |||
4bbaa75f41 | |||
5fe9314b74 | |||
bfd26493ab | |||
5d74d5d03e | |||
3142e097a5 | |||
b25d8d8201 | |||
7e68cd016a | |||
9e4fc9eb73 | |||
fb0d13a171 | |||
f097b36c2e | |||
57ce3cf80b | |||
c1da62391f | |||
d8882ce979 | |||
9c1871494e | |||
ac26c9114a | |||
fb0a57003c | |||
cda22233a6 | |||
add307577d | |||
4af46ac2b9 | |||
8d39752bda | |||
e3ff9c013e | |||
635870ba4b | |||
a827afb05b | |||
0195e13bdc | |||
76d8b688fd | |||
b4857e2c79 | |||
ac2ae90e8a | |||
0b5cb76b5d | |||
6eed9606ae | |||
29679ff94c | |||
65a1f8b746 | |||
48182d35bd | |||
b526ed47be | |||
2225dec2a9 | |||
011458d642 | |||
e32fff3f61 | |||
0b42e3d0f3 | |||
2b746d1353 | |||
6fed6f9f7d | |||
480945073a | |||
ef6df94838 | |||
fb05848426 | |||
e5be6b96fb | |||
2d706a245a | |||
1abc4f491b | |||
b21a45b43e | |||
585f8450e1 | |||
d3401db5fe | |||
45dc6447bf | |||
409c270ba2 | |||
47b485819f | |||
fd32f7f435 | |||
c4b07b1a7b | |||
2622eb4663 | |||
74b3dc2831 | |||
84629f6e63 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,6 +4,7 @@
|
|||||||
/build32/
|
/build32/
|
||||||
/build64/
|
/build64/
|
||||||
/release/
|
/release/
|
||||||
|
/package/
|
||||||
/installer/Output/
|
/installer/Output/
|
||||||
|
.idea
|
||||||
.vscode
|
.vscode
|
49
.travis.yml
49
.travis.yml
@ -1,49 +0,0 @@
|
|||||||
language: cpp
|
|
||||||
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
# AWS key ID
|
|
||||||
- secure: pAiNUGVbjP12BfnWPk0FFTkbnk4Tocvv88XiT3rzRqkQaD7/iyEogLBfHM4nOEgFiIMHbC41aE83w5JgRNPwn6mTgoQBOglzqq1tGuXfqPyV2VStk8beji1evubGoVjjPaoPTFyIdQc5GGxdHyogI/ed9Hb3ccyykYvjyolj9XoCiW42QHx60AHGwl+So+dEa8xydj9SLRPlZ/AitmI/cPVN3YotA7s37BLFiab54enxk7T4rwpR1nU0HVfoCpn5F4wZYxRq+LlSVFzC8vVE9cpDSLS5kjrZIZaT18tYG1/untCj+wqMIZbghaJXLtPSRW2YPHcJTz8q1YSXnJ19+0uiAIMAqaVv0kD5BAM97byYDBW+b9H6SYFkb/Pw/qcK9amMzMBjDPFpYFkl9Q2kzhsNs3HsZf/flSZjtrkQJiP3SOi/KvKzVK9X4Wym6hYZWHgmMTTYFrvr6BYnf2GkpfKNjm1d2kc0NNrq4d5H4NOEQB8MP+QH+o+BPeM6d9dthrUc1Pw+BXzOAr85CN4qtpPGoAl/Dbfgd6eu/88E2LpUufW2VFAOPWjykSOqzSN3orh7AaWuE34VFEnQ+2y3uIE8AKoyXzJv6zYkyNnNewKZeGe2kKYNwLn5UxQA9JEj7a+tvVevk4xBSkkjFAvjSG2z8/F1FXNbEfoLX1Hz/bU=
|
|
||||||
# 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:
|
|
||||||
- docker
|
|
||||||
before_install:
|
|
||||||
- docker run -d --name xenial -v $(dirname $(pwd)):/root -v /home/travis/package:/package
|
|
||||||
-e TRAVIS_BRANCH="$TRAVIS_BRANCH" -e TRAVIS_TAG="$TRAVIS_TAG" -w /root nimmis/ubuntu:16.04
|
|
||||||
- docker exec -it xenial /root/obs-websocket/CI/install-dependencies-xenial.sh
|
|
||||||
script:
|
|
||||||
- docker exec -it xenial /root/obs-websocket/CI/build-xenial.sh
|
|
||||||
after_success:
|
|
||||||
- docker exec -it xenial /root/obs-websocket/CI/package-xenial.sh
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
- provider: s3
|
|
||||||
region: eu-central-1
|
|
||||||
bucket: obs-websocket-linux-builds
|
|
||||||
access_key_id: "$AWS_ID"
|
|
||||||
secret_access_key: "$AWS_SECRET"
|
|
||||||
local_dir: /home/travis/package
|
|
||||||
skip_cleanup: true
|
|
||||||
acl: public_read
|
|
||||||
on:
|
|
||||||
repo: Palakis/obs-websocket
|
|
||||||
condition:
|
|
||||||
- "$TRAVIS_OS_NAME = linux"
|
|
||||||
- "-d /home/travis/package"
|
|
||||||
all_branches: true
|
|
@ -3,7 +3,7 @@
|
|||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
You'll need [Qt 5.10.x](https://download.qt.io/official_releases/qt/5.10/),
|
You'll need [Qt 5.10.x](https://download.qt.io/official_releases/qt/5.10/),
|
||||||
[CMake](https://cmake.org/download/), [Boost](https://www.boost.org/) and a working [OBS Studio development environment](https://obsproject.com/wiki/install-instructions) installed on your
|
[CMake](https://cmake.org/download/) and a working [OBS Studio development environment](https://obsproject.com/wiki/install-instructions) installed on your
|
||||||
computer.
|
computer.
|
||||||
|
|
||||||
## Windows
|
## Windows
|
||||||
@ -24,7 +24,7 @@ sudo apt-get install libboost-all-dev
|
|||||||
git clone --recursive https://github.com/Palakis/obs-websocket.git
|
git clone --recursive https://github.com/Palakis/obs-websocket.git
|
||||||
cd obs-websocket
|
cd obs-websocket
|
||||||
mkdir build && cd build
|
mkdir build && cd build
|
||||||
cmake -DLIBOBS_INCLUDE_DIR="<path to the libobs sub-folder in obs-studio's source code>" -DCMAKE_INSTALL_PREFIX=/usr ..
|
cmake -DLIBOBS_INCLUDE_DIR="<path to the libobs sub-folder in obs-studio's source code>" -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=true ..
|
||||||
make -j4
|
make -j4
|
||||||
sudo make install
|
sudo make install
|
||||||
```
|
```
|
||||||
|
6
CI/build-ubuntu.sh
Executable file
6
CI/build-ubuntu.sh
Executable file
@ -0,0 +1,6 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=true ..
|
||||||
|
make -j4
|
@ -1,8 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -ex
|
|
||||||
|
|
||||||
cd /root/obs-websocket
|
|
||||||
|
|
||||||
mkdir build && cd build
|
|
||||||
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
|
|
||||||
make -j4
|
|
6
CI/download-obs-deps.cmd
Normal file
6
CI/download-obs-deps.cmd
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
if not exist %DepsBasePath% (
|
||||||
|
curl -o %DepsBasePath%.zip -kLO https://obsproject.com/downloads/dependencies2017.zip -f --retry 5 -C -
|
||||||
|
7z x %DepsBasePath%.zip -o%DepsBasePath%
|
||||||
|
) else (
|
||||||
|
echo "OBS dependencies are already there. Download skipped."
|
||||||
|
)
|
@ -4,6 +4,9 @@ echo "-- Generating documentation."
|
|||||||
echo "-- Node version: $(node -v)"
|
echo "-- Node version: $(node -v)"
|
||||||
echo "-- NPM version: $(npm -v)"
|
echo "-- NPM version: $(npm -v)"
|
||||||
|
|
||||||
|
git fetch origin
|
||||||
|
git checkout ${CHECKOUT_REF/refs\/heads\//}
|
||||||
|
|
||||||
cd docs
|
cd docs
|
||||||
npm install
|
npm install
|
||||||
npm run build
|
npm run build
|
||||||
@ -15,19 +18,14 @@ if git diff --quiet; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "4.x-current" ]; 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)"
|
REMOTE_URL="$(git config remote.origin.url)"
|
||||||
TARGET_REPO=${REMOTE_URL/https:\/\/github.com\//github.com/}
|
TARGET_REPO=${REMOTE_URL/https:\/\/github.com\//github.com/}
|
||||||
GITHUB_REPO=https://${GH_TOKEN:-git}@${TARGET_REPO}
|
GITHUB_REPO=https://${GH_TOKEN:-git}@${TARGET_REPO}
|
||||||
|
|
||||||
git config user.name "Travis CI"
|
git config user.name "Azure CI"
|
||||||
git config user.email "$COMMIT_AUTHOR_EMAIL"
|
git config user.email "$COMMIT_AUTHOR_EMAIL"
|
||||||
|
|
||||||
git add ./generated
|
git add ./generated
|
||||||
git pull
|
git pull
|
||||||
git commit -m "docs(travis): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]"
|
git commit -m "docs(ci): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]"
|
||||||
git push -q $GITHUB_REPO HEAD:$TRAVIS_BRANCH
|
git push -q $GITHUB_REPO
|
||||||
|
@ -34,7 +34,6 @@ git checkout $OBSLatestTag
|
|||||||
mkdir build && cd build
|
mkdir build && cd build
|
||||||
echo "[obs-websocket] Building obs-studio.."
|
echo "[obs-websocket] Building obs-studio.."
|
||||||
cmake .. \
|
cmake .. \
|
||||||
-DBUILD_CAPTIONS=true \
|
|
||||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 \
|
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 \
|
||||||
-DDISABLE_PLUGINS=true \
|
-DDISABLE_PLUGINS=true \
|
||||||
-DENABLE_SCRIPTING=0 \
|
-DENABLE_SCRIPTING=0 \
|
||||||
|
@ -1,129 +0,0 @@
|
|||||||
@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 https://github.com/obsproject/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 If the needed obs-studio libs for this build_config do not exist,
|
|
||||||
REM set the build flag.
|
|
||||||
if not exist C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib (
|
|
||||||
echo obs-studio\build32\libobs\%build_config%\obs.lib does not exist
|
|
||||||
set BuildOBS=true
|
|
||||||
)
|
|
||||||
if not exist C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib (
|
|
||||||
echo obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib does not exist
|
|
||||||
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 14 2015" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
|
||||||
echo:
|
|
||||||
echo:
|
|
||||||
echo Running cmake for obs-studio %OBSLatestTag% 64-bit...
|
|
||||||
cd ../build64
|
|
||||||
cmake -G "Visual Studio 14 2015 Win64" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -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.
|
|
||||||
)
|
|
19
CI/install-dependencies-ubuntu.sh
Executable file
19
CI/install-dependencies-ubuntu.sh
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
sudo add-apt-repository -y ppa:obsproject/obs-studio
|
||||||
|
sudo apt-get -qq update
|
||||||
|
|
||||||
|
sudo apt-get install -y \
|
||||||
|
libc-dev-bin \
|
||||||
|
libc6-dev git \
|
||||||
|
build-essential \
|
||||||
|
checkinstall \
|
||||||
|
cmake \
|
||||||
|
obs-studio \
|
||||||
|
qtbase5-dev
|
||||||
|
|
||||||
|
# Dirty hack
|
||||||
|
sudo wget -O /usr/include/obs/obs-frontend-api.h https://raw.githubusercontent.com/obsproject/obs-studio/25.0.0/UI/obs-frontend-api/obs-frontend-api.h
|
||||||
|
|
||||||
|
sudo ldconfig
|
@ -1,19 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
set -ex
|
|
||||||
|
|
||||||
add-apt-repository -y ppa:obsproject/obs-studio
|
|
||||||
apt-get -qq update
|
|
||||||
|
|
||||||
apt-get install -y \
|
|
||||||
libc-dev-bin \
|
|
||||||
libc6-dev git \
|
|
||||||
build-essential \
|
|
||||||
checkinstall \
|
|
||||||
cmake \
|
|
||||||
obs-studio \
|
|
||||||
qtbase5-dev
|
|
||||||
|
|
||||||
# Dirty hack
|
|
||||||
wget -O /usr/include/obs/obs-frontend-api.h https://raw.githubusercontent.com/obsproject/obs-studio/master/UI/obs-frontend-api/obs-frontend-api.h
|
|
||||||
|
|
||||||
ldconfig
|
|
8
CI/install-qt-win.cmd
Normal file
8
CI/install-qt-win.cmd
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
if not exist %QtBaseDir% (
|
||||||
|
curl -kLO https://cdn-fastly.obsproject.com/downloads/Qt_5.10.1.7z -f --retry 5 -z Qt_5.10.1.7z
|
||||||
|
7z x Qt_5.10.1.7z -o%QtBaseDir%
|
||||||
|
) else (
|
||||||
|
echo "Qt is already installed. Download skipped."
|
||||||
|
)
|
||||||
|
|
||||||
|
dir %QtBaseDir%
|
@ -1,6 +0,0 @@
|
|||||||
@echo off
|
|
||||||
|
|
||||||
REM Set default values to use AppVeyor's built-in Qt.
|
|
||||||
set QTDIR32=C:\Qt\5.10.1\msvc2015
|
|
||||||
set QTDIR64=C:\Qt\5.10.1\msvc2015_64
|
|
||||||
set QTCompileVersion=5.10.1
|
|
@ -514,11 +514,11 @@
|
|||||||
<key>CONCLUSION_ACTION</key>
|
<key>CONCLUSION_ACTION</key>
|
||||||
<integer>0</integer>
|
<integer>0</integer>
|
||||||
<key>IDENTIFIER</key>
|
<key>IDENTIFIER</key>
|
||||||
<string>fr.palakis.obswebsocket</string>
|
<string>fr.palakis.obs-websocket</string>
|
||||||
<key>OVERWRITE_PERMISSIONS</key>
|
<key>OVERWRITE_PERMISSIONS</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>VERSION</key>
|
<key>VERSION</key>
|
||||||
<string>4.6.0</string>
|
<string>4.8.0</string>
|
||||||
</dict>
|
</dict>
|
||||||
<key>PROJECT_COMMENTS</key>
|
<key>PROJECT_COMMENTS</key>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
@ -12,29 +12,79 @@ fi
|
|||||||
echo "[obs-websocket] Preparing package build"
|
echo "[obs-websocket] Preparing package build"
|
||||||
export QT_CELLAR_PREFIX="$(/usr/bin/find /usr/local/Cellar/qt -d 1 | sort -t '.' -k 1,1n -k 2,2n -k 3,3n | tail -n 1)"
|
export QT_CELLAR_PREFIX="$(/usr/bin/find /usr/local/Cellar/qt -d 1 | sort -t '.' -k 1,1n -k 2,2n -k 3,3n | tail -n 1)"
|
||||||
|
|
||||||
export GIT_HASH=$(git rev-parse --short HEAD)
|
GIT_HASH=$(git rev-parse --short HEAD)
|
||||||
export GIT_BRANCH_OR_TAG=$(git name-rev --name-only HEAD | awk -F/ '{print $NF}')
|
GIT_BRANCH_OR_TAG=$(git name-rev --name-only HEAD | awk -F/ '{print $NF}')
|
||||||
|
|
||||||
export VERSION="$GIT_HASH-$GIT_BRANCH_OR_TAG"
|
VERSION="$GIT_HASH-$GIT_BRANCH_OR_TAG"
|
||||||
export LATEST_VERSION="$GIT_BRANCH_OR_TAG"
|
|
||||||
|
|
||||||
export FILENAME="obs-websocket-$VERSION.pkg"
|
FILENAME_UNSIGNED="obs-websocket-$VERSION-Unsigned.pkg"
|
||||||
export LATEST_FILENAME="obs-websocket-latest-$LATEST_VERSION.pkg"
|
FILENAME="obs-websocket-$VERSION.pkg"
|
||||||
|
|
||||||
echo "[obs-websocket] Modifying obs-websocket.so"
|
echo "[obs-websocket] Modifying obs-websocket.so"
|
||||||
install_name_tool \
|
install_name_tool \
|
||||||
-change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets \
|
-change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets \
|
||||||
-change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @rpath/QtGui \
|
@executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets \
|
||||||
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
|
-change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui \
|
||||||
|
@executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui \
|
||||||
|
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore \
|
||||||
|
@executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore \
|
||||||
./build/obs-websocket.so
|
./build/obs-websocket.so
|
||||||
|
|
||||||
# Check if replacement worked
|
# Check if replacement worked
|
||||||
echo "[obs-websocket] Dependencies for obs-websocket"
|
echo "[obs-websocket] Dependencies for obs-websocket"
|
||||||
otool -L ./build/obs-websocket.so
|
otool -L ./build/obs-websocket.so
|
||||||
|
|
||||||
|
if [[ "$RELEASE_MODE" == "True" ]]; then
|
||||||
|
echo "[obs-websocket] Signing plugin binary: obs-websocket.so"
|
||||||
|
codesign --sign "$CODE_SIGNING_IDENTITY" ./build/obs-websocket.so
|
||||||
|
else
|
||||||
|
echo "[obs-websocket] Skipped plugin codesigning"
|
||||||
|
fi
|
||||||
|
|
||||||
echo "[obs-websocket] Actual package build"
|
echo "[obs-websocket] Actual package build"
|
||||||
packagesbuild ./CI/macos/obs-websocket.pkgproj
|
packagesbuild ./CI/macos/obs-websocket.pkgproj
|
||||||
|
|
||||||
echo "[obs-websocket] Renaming obs-websocket.pkg to $FILENAME"
|
echo "[obs-websocket] Renaming obs-websocket.pkg to $FILENAME"
|
||||||
mv ./release/obs-websocket.pkg ./release/$FILENAME
|
mv ./release/obs-websocket.pkg ./release/$FILENAME_UNSIGNED
|
||||||
cp ./release/$FILENAME ./release/$LATEST_FILENAME
|
|
||||||
|
if [[ "$RELEASE_MODE" == "True" ]]; then
|
||||||
|
echo "[obs-websocket] Signing installer: $FILENAME"
|
||||||
|
productsign \
|
||||||
|
--sign "$INSTALLER_SIGNING_IDENTITY" \
|
||||||
|
./release/$FILENAME_UNSIGNED \
|
||||||
|
./release/$FILENAME
|
||||||
|
rm ./release/$FILENAME_UNSIGNED
|
||||||
|
|
||||||
|
echo "[obs-websocket] Submitting installer $FILENAME for notarization"
|
||||||
|
zip -r ./release/$FILENAME.zip ./release/$FILENAME
|
||||||
|
UPLOAD_RESULT=$(xcrun altool \
|
||||||
|
--notarize-app \
|
||||||
|
--primary-bundle-id "fr.palakis.obs-websocket" \
|
||||||
|
--username "$AC_USERNAME" \
|
||||||
|
--password "$AC_PASSWORD" \
|
||||||
|
--asc-provider "$AC_PROVIDER_SHORTNAME" \
|
||||||
|
--file "./release/$FILENAME.zip")
|
||||||
|
rm ./release/$FILENAME.zip
|
||||||
|
|
||||||
|
REQUEST_UUID=$(echo $UPLOAD_RESULT | awk -F ' = ' '/RequestUUID/ {print $2}')
|
||||||
|
echo "Request UUID: $REQUEST_UUID"
|
||||||
|
|
||||||
|
echo "[obs-websocket] Wait for notarization result"
|
||||||
|
# Pieces of code borrowed from rednoah/notarized-app
|
||||||
|
while sleep 30 && date; do
|
||||||
|
CHECK_RESULT=$(xcrun altool \
|
||||||
|
--notarization-info "$REQUEST_UUID" \
|
||||||
|
--username "$AC_USERNAME" \
|
||||||
|
--password "$AC_PASSWORD" \
|
||||||
|
--asc-provider "$AC_PROVIDER_SHORTNAME")
|
||||||
|
echo $CHECK_RESULT
|
||||||
|
|
||||||
|
if ! grep -q "Status: in progress" <<< "$CHECK_RESULT"; then
|
||||||
|
echo "[obs-websocket] Staple ticket to installer: $FILENAME"
|
||||||
|
xcrun stapler staple ./release/$FILENAME
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "[obs-websocket] Skipped installer codesigning and notarization"
|
||||||
|
fi
|
23
CI/package-ubuntu.sh
Executable file
23
CI/package-ubuntu.sh
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
export GIT_HASH=$(git rev-parse --short HEAD)
|
||||||
|
export PKG_VERSION="1-$GIT_HASH-$BRANCH_SHORT_NAME-git"
|
||||||
|
|
||||||
|
if [[ "$BRANCH_FULL_NAME" =~ "^refs/tags/" ]]; then
|
||||||
|
export PKG_VERSION="$BRANCH_SHORT_NAME"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd ./build
|
||||||
|
|
||||||
|
PAGER="cat" sudo checkinstall -y --type=debian --fstrans=no --nodoc \
|
||||||
|
--backup=no --deldoc=yes --install=no \
|
||||||
|
--pkgname=obs-websocket --pkgversion="$PKG_VERSION" \
|
||||||
|
--pkglicense="GPLv2.0" --maintainer="stephane.lepin@gmail.com" \
|
||||||
|
--pkggroup="video" \
|
||||||
|
--pkgsource="https://github.com/Palakis/obs-websocket" \
|
||||||
|
--requires="obs-studio \(\>= 25.0.7\), libqt5core5a, libqt5widgets5, qt5-image-formats-plugins" \
|
||||||
|
--pakdir="../package"
|
||||||
|
|
||||||
|
sudo chmod ao+r ../package/*
|
12
CI/package-windows.cmd
Normal file
12
CI/package-windows.cmd
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
mkdir package
|
||||||
|
cd package
|
||||||
|
|
||||||
|
git rev-parse --short HEAD > package-version.txt
|
||||||
|
set /p PackageVersion=<package-version.txt
|
||||||
|
del package-version.txt
|
||||||
|
|
||||||
|
REM Package ZIP archive
|
||||||
|
7z a "obs-websocket-%PackageVersion%-Windows.zip" "..\release\*"
|
||||||
|
|
||||||
|
REM Build installer
|
||||||
|
iscc ..\installer\installer.iss /O. /F"obs-websocket-%PackageVersion%-Windows-Installer"
|
@ -1,24 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
cd /root/obs-websocket
|
|
||||||
|
|
||||||
export GIT_HASH=$(git rev-parse --short HEAD)
|
|
||||||
export PKG_VERSION="1-$GIT_HASH-$TRAVIS_BRANCH-git"
|
|
||||||
|
|
||||||
if [ -n "${TRAVIS_TAG}" ]; then
|
|
||||||
export PKG_VERSION="$TRAVIS_TAG"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd /root/obs-websocket/build
|
|
||||||
|
|
||||||
PAGER=cat checkinstall -y --type=debian --fstrans=no --nodoc \
|
|
||||||
--backup=no --deldoc=yes --install=no \
|
|
||||||
--pkgname=obs-websocket --pkgversion="$PKG_VERSION" \
|
|
||||||
--pkglicense="GPLv2.0" --maintainer="contact@slepin.fr" \
|
|
||||||
--pkggroup="video" \
|
|
||||||
--pkgsource="https://github.com/Palakis/obs-websocket" \
|
|
||||||
--pakdir="/package"
|
|
||||||
|
|
||||||
chmod ao+r /package/*
|
|
37
CI/prepare-obs-windows.cmd
Normal file
37
CI/prepare-obs-windows.cmd
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
@echo off
|
||||||
|
SETLOCAL EnableDelayedExpansion
|
||||||
|
|
||||||
|
REM If obs-studio directory does not exist, clone the git repo
|
||||||
|
if not exist %OBSPath% (
|
||||||
|
echo obs-studio directory does not exist
|
||||||
|
git clone https://github.com/obsproject/obs-studio %OBSPath%
|
||||||
|
cd /D %OBSPath%\
|
||||||
|
git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBSPath%\obs-studio-latest-tag.txt"
|
||||||
|
set /p OBSLatestTag=<"%OBSPath%\obs-studio-latest-tag.txt"
|
||||||
|
)
|
||||||
|
|
||||||
|
REM Prepare OBS Studio builds
|
||||||
|
|
||||||
|
echo Running CMake...
|
||||||
|
cd /D %OBSPath%
|
||||||
|
echo git checkout %OBSLatestTag%
|
||||||
|
git checkout %OBSLatestTag%
|
||||||
|
echo:
|
||||||
|
|
||||||
|
if not exist build32 mkdir build32
|
||||||
|
if not exist build64 mkdir build64
|
||||||
|
|
||||||
|
echo Running cmake for obs-studio %OBSLatestTag% 32-bit...
|
||||||
|
cd build32
|
||||||
|
cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR32%" -DDepsPath="%DepsPath32%" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
||||||
|
echo:
|
||||||
|
echo:
|
||||||
|
|
||||||
|
echo Running cmake for obs-studio %OBSLatestTag% 64-bit...
|
||||||
|
cd ..\build64
|
||||||
|
cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR64%" -DDepsPath="%DepsPath64%" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
||||||
|
echo:
|
||||||
|
echo:
|
||||||
|
|
||||||
|
dir "%OBSPath%\libobs"
|
7
CI/prepare-windows.cmd
Normal file
7
CI/prepare-windows.cmd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
mkdir build32
|
||||||
|
mkdir build64
|
||||||
|
|
||||||
|
cd build32
|
||||||
|
cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR32%" -DLibObs_DIR="%OBSPath%\build32\libobs" -DLIBOBS_INCLUDE_DIR="%OBSPath%\libobs" -DLIBOBS_LIB="%OBSPath%\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="%OBSPath%\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
|
||||||
|
cd ..\build64
|
||||||
|
cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR64%" -DLibObs_DIR="%OBSPath%\build64\libobs" -DLIBOBS_INCLUDE_DIR="%OBSPath%\libobs" -DLIBOBS_LIB="%OBSPath%\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="%OBSPath%\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
|
@ -1,30 +1,22 @@
|
|||||||
cmake_minimum_required(VERSION 3.2)
|
cmake_minimum_required(VERSION 3.5)
|
||||||
project(obs-websocket)
|
project(obs-websocket VERSION 4.8.0)
|
||||||
|
|
||||||
set(CMAKE_PREFIX_PATH "${QTDIR}")
|
set(CMAKE_PREFIX_PATH "${QTDIR}")
|
||||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
set(CMAKE_AUTOUIC ON)
|
set(CMAKE_AUTOUIC ON)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
if (WIN32 OR APPLE)
|
|
||||||
include(external/FindLibObs.cmake)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_definitions(-DASIO_STANDALONE)
|
add_definitions(-DASIO_STANDALONE)
|
||||||
|
|
||||||
if (UNIX)
|
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (WIN32 OR APPLE)
|
if (WIN32 OR APPLE)
|
||||||
include(external/FindLibObs.cmake)
|
include(external/FindLibObs.cmake)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(LibObs REQUIRED)
|
find_package(LibObs REQUIRED)
|
||||||
find_package(Qt5Core REQUIRED)
|
find_package(Qt5 REQUIRED COMPONENTS Core Widgets)
|
||||||
find_package(Qt5Widgets REQUIRED)
|
|
||||||
|
|
||||||
set(obs-websocket_SOURCES
|
set(obs-websocket_SOURCES
|
||||||
src/obs-websocket.cpp
|
src/obs-websocket.cpp
|
||||||
@ -42,9 +34,14 @@ set(obs-websocket_SOURCES
|
|||||||
src/WSRequestHandler_Streaming.cpp
|
src/WSRequestHandler_Streaming.cpp
|
||||||
src/WSRequestHandler_StudioMode.cpp
|
src/WSRequestHandler_StudioMode.cpp
|
||||||
src/WSRequestHandler_Transitions.cpp
|
src/WSRequestHandler_Transitions.cpp
|
||||||
|
src/WSRequestHandler_Outputs.cpp
|
||||||
src/WSEvents.cpp
|
src/WSEvents.cpp
|
||||||
src/Config.cpp
|
src/Config.cpp
|
||||||
src/Utils.cpp
|
src/Utils.cpp
|
||||||
|
src/rpc/RpcRequest.cpp
|
||||||
|
src/rpc/RpcResponse.cpp
|
||||||
|
src/rpc/RpcEvent.cpp
|
||||||
|
src/protocol/OBSRemoteProtocol.cpp
|
||||||
src/forms/settings-dialog.cpp)
|
src/forms/settings-dialog.cpp)
|
||||||
|
|
||||||
set(obs-websocket_HEADERS
|
set(obs-websocket_HEADERS
|
||||||
@ -55,6 +52,10 @@ set(obs-websocket_HEADERS
|
|||||||
src/WSEvents.h
|
src/WSEvents.h
|
||||||
src/Config.h
|
src/Config.h
|
||||||
src/Utils.h
|
src/Utils.h
|
||||||
|
src/rpc/RpcRequest.h
|
||||||
|
src/rpc/RpcResponse.h
|
||||||
|
src/rpc/RpcEvent.h
|
||||||
|
src/protocol/OBSRemoteProtocol.h
|
||||||
src/forms/settings-dialog.h)
|
src/forms/settings-dialog.h)
|
||||||
|
|
||||||
# --- Platform-independent build settings ---
|
# --- Platform-independent build settings ---
|
||||||
@ -83,6 +84,11 @@ if(WIN32)
|
|||||||
message(FATAL_ERROR "Could not find OBS Frontend API's library !")
|
message(FATAL_ERROR "Could not find OBS Frontend API's library !")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
# Enable Multicore Builds and disable FH4 (to not depend on VCRUNTIME140_1.DLL)
|
||||||
|
add_definitions(/MP /d2FH4-)
|
||||||
|
endif()
|
||||||
|
|
||||||
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
|
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
|
||||||
|
|
||||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||||
@ -119,6 +125,16 @@ if(WIN32)
|
|||||||
"$<TARGET_FILE:obs-websocket>"
|
"$<TARGET_FILE:obs-websocket>"
|
||||||
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
|
"${RELEASE_DIR}/obs-plugins/${ARCH_NAME}")
|
||||||
|
|
||||||
|
# In Release mode, copy Qt image format plugins
|
||||||
|
COMMAND if $<CONFIG:Release>==1 (
|
||||||
|
"${CMAKE_COMMAND}" -E copy
|
||||||
|
"${QTDIR}/plugins/imageformats/qjpeg.dll"
|
||||||
|
"${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats/qjpeg.dll")
|
||||||
|
COMMAND if $<CONFIG:RelWithDebInfo>==1 (
|
||||||
|
"${CMAKE_COMMAND}" -E copy
|
||||||
|
"${QTDIR}/plugins/imageformats/qjpeg.dll"
|
||||||
|
"${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats/qjpeg.dll")
|
||||||
|
|
||||||
# If config is RelWithDebInfo, package release files
|
# If config is RelWithDebInfo, package release files
|
||||||
COMMAND if $<CONFIG:RelWithDebInfo>==1 (
|
COMMAND if $<CONFIG:RelWithDebInfo>==1 (
|
||||||
"${CMAKE_COMMAND}" -E make_directory
|
"${CMAKE_COMMAND}" -E make_directory
|
||||||
@ -164,20 +180,22 @@ endif()
|
|||||||
|
|
||||||
# --- Linux-specific build settings and tasks ---
|
# --- Linux-specific build settings and tasks ---
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
|
||||||
set_target_properties(obs-websocket PROPERTIES PREFIX "")
|
set_target_properties(obs-websocket PROPERTIES PREFIX "")
|
||||||
target_link_libraries(obs-websocket obs-frontend-api)
|
target_link_libraries(obs-websocket obs-frontend-api)
|
||||||
|
|
||||||
file(GLOB locale_files data/locale/*.ini)
|
file(GLOB locale_files data/locale/*.ini)
|
||||||
execute_process(COMMAND uname -m COMMAND tr -d '\n' OUTPUT_VARIABLE UNAME_MACHINE)
|
|
||||||
|
|
||||||
|
if(${USE_UBUNTU_FIX})
|
||||||
|
install(TARGETS obs-websocket
|
||||||
|
LIBRARY DESTINATION "/usr/lib/obs-plugins")
|
||||||
|
endif()
|
||||||
install(TARGETS obs-websocket
|
install(TARGETS obs-websocket
|
||||||
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/obs-plugins")
|
LIBRARY DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}/obs-plugins")
|
||||||
# Dirty fix for Ubuntu
|
|
||||||
install(TARGETS obs-websocket
|
|
||||||
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/${UNAME_MACHINE}-linux-gnu/obs-plugins")
|
|
||||||
|
|
||||||
install(FILES ${locale_files}
|
install(FILES ${locale_files}
|
||||||
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/obs/obs-plugins/obs-websocket/locale")
|
DESTINATION "${CMAKE_INSTALL_FULL_DATAROOTDIR}/obs/obs-plugins/obs-websocket/locale")
|
||||||
endif()
|
endif()
|
||||||
# --- End of section ---
|
# --- End of section ---
|
||||||
|
|
||||||
|
29
README.md
29
README.md
@ -1,22 +1,26 @@
|
|||||||
obs-websocket
|
obs-websocket
|
||||||
==============
|
==============
|
||||||
Remote control of OBS Studio made easy.
|
|
||||||
|
WebSockets API for OBS Studio.
|
||||||
|
|
||||||
Follow the main author on Twitter for news & updates : [@LePalakis](https://twitter.com/LePalakis)
|
Follow the main author on Twitter for news & updates : [@LePalakis](https://twitter.com/LePalakis)
|
||||||
|
|
||||||
[](https://ci.appveyor.com/project/Palakis/obs-websocket/history) [](https://travis-ci.org/Palakis/obs-websocket)
|
[](https://dev.azure.com/Palakis/obs-websocket/_build/latest?definitionId=2&branchName=4.x-current)
|
||||||
|
|
||||||
## Downloads
|
## Downloads
|
||||||
Binaries for Windows and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section.
|
|
||||||
|
Binaries for Windows, MacOS, and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section.
|
||||||
|
|
||||||
## Using obs-websocket
|
## Using obs-websocket
|
||||||
|
|
||||||
A web client and frontend made by [t2t2](https://github.com/t2t2/obs-tablet-remote) (compatible with tablets and other touch interfaces) is available here : http://t2t2.github.io/obs-tablet-remote/
|
A web client and frontend made by [t2t2](https://github.com/t2t2/obs-tablet-remote) (compatible with tablets and other touch interfaces) is available here : http://t2t2.github.io/obs-tablet-remote/
|
||||||
|
|
||||||
It is **highly recommended** to protect obs-websocket with a password against unauthorized control. To do this, open the "Websocket server settings" dialog under OBS' "Tools" menu. In the settings dialogs, you can enable or disable authentication and set a password for it.
|
It is **highly recommended** to protect obs-websocket with a password against unauthorized control. To do this, open the "Websocket server settings" dialog under OBS' "Tools" menu. In the settings dialogs, you can enable or disable authentication and set a password for it.
|
||||||
|
|
||||||
### Possible use cases
|
### Possible use cases
|
||||||
|
|
||||||
- Remote control OBS from a phone or tablet on the same local network
|
- Remote control OBS from a phone or tablet on the same local network
|
||||||
- Change your stream overlay/graphics based on the current scene (like the AGDQ overlay does)
|
- Change your stream overlay/graphics based on the current scene
|
||||||
- Automate scene switching with a third-party program (e.g. : auto-pilot, foot pedal, ...)
|
- Automate scene switching with a third-party program (e.g. : auto-pilot, foot pedal, ...)
|
||||||
|
|
||||||
### For developers
|
### For developers
|
||||||
@ -29,6 +33,10 @@ Here's a list of available language APIs for obs-websocket :
|
|||||||
- C#/VB.NET: [obs-websocket-dotnet](https://github.com/Palakis/obs-websocket-dotnet)
|
- C#/VB.NET: [obs-websocket-dotnet](https://github.com/Palakis/obs-websocket-dotnet)
|
||||||
- Python 2 and 3: [obs-websocket-py](https://github.com/Elektordi/obs-websocket-py) by Guillaume Genty a.k.a Elektordi
|
- Python 2 and 3: [obs-websocket-py](https://github.com/Elektordi/obs-websocket-py) by Guillaume Genty a.k.a Elektordi
|
||||||
- Python 3.5+ with asyncio: [obs-ws-rc](https://github.com/KirillMysnik/obs-ws-rc) by Kirill Mysnik
|
- Python 3.5+ with asyncio: [obs-ws-rc](https://github.com/KirillMysnik/obs-ws-rc) by Kirill Mysnik
|
||||||
|
- Python 3.6+ with asyncio: [simpleobsws](https://github.com/IRLToolkit/simpleobsws) by tt2468
|
||||||
|
- Java 8+: [obs-websocket-java](https://github.com/Twasi/websocket-obs-java) by TwasiNET
|
||||||
|
- Golang: [go-obs-websocket](https://github.com/christopher-dG/go-obs-websocket) by Chris de Graaf
|
||||||
|
- HTTP API: [obs-websocket-http](https://github.com/IRLToolkit/obs-websocket-http) by tt2468
|
||||||
|
|
||||||
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 `stephane /dot/ lepin /at/ gmail /dot/ com` !
|
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 `stephane /dot/ lepin /at/ gmail /dot/ com` !
|
||||||
|
|
||||||
@ -40,26 +48,21 @@ See the [build instructions](BUILDING.md).
|
|||||||
|
|
||||||
### Branches
|
### Branches
|
||||||
|
|
||||||
The two main development branches are:
|
Development happens on `4.x-current`
|
||||||
|
|
||||||
- `4.x-current`: actively-maintained codebase for 4.x releases. Backwards-compatible (unless stated otherwise) with existing clients until 5.0.
|
|
||||||
- `5.x`: upcoming 5.0 version
|
|
||||||
|
|
||||||
**New features and fixes must be based off and contributed to `4.x-current`**, as obs-websocket 5.0 is not in active development yet.
|
|
||||||
|
|
||||||
### Pull Requests
|
### Pull Requests
|
||||||
|
|
||||||
Pull Requests must never be based off your fork's main branch (in our case, `4.x-current` or `5.x`). Start your work in a new branch
|
Pull Requests must never be based off your fork's main branch (in this case, `4.x-current`). Start your work in a new branch
|
||||||
based on the main one (e.g.: `cool-new-feature`, `fix-palakis-mistakes`, ...) and open a Pull Request once you feel ready to show your work.
|
based on the main one (e.g.: `cool-new-feature`, `fix-palakis-mistakes`, ...) and open a Pull Request once you feel ready to show your work.
|
||||||
|
|
||||||
If your Pull Request is not ready to merge yet, tag it with the `work in progress` label. You can also use the `help needed` label if you have questions, need a hand or want to ask for input.
|
**If your Pull Request is not ready to merge yet, create it as a Draft Pull Request** (open the little arrow menu next to the "Create pull request" button, then select "Create draft pull request").
|
||||||
|
|
||||||
### Code style & formatting
|
### Code style & formatting
|
||||||
|
|
||||||
Source code is indented with tabs, with spaces allowed for alignment.
|
Source code is indented with tabs, with spaces allowed for alignment.
|
||||||
|
|
||||||
Regarding protocol changes: new and updated request types / events must always come with accompanying documentation comments (see existing protocol elements for examples).
|
Regarding protocol changes: new and updated request types / events must always come with accompanying documentation comments (see existing protocol elements for examples).
|
||||||
These are using to automatically generate the [protocol specification](docs/generated/protocol.md).
|
These are required to automatically generate the [protocol specification document](docs/generated/protocol.md).
|
||||||
|
|
||||||
Among other recommendations: favor return-early code and avoid wrapping huge portions of code in conditionals. As an example, this:
|
Among other recommendations: favor return-early code and avoid wrapping huge portions of code in conditionals. As an example, this:
|
||||||
|
|
||||||
|
40
appveyor.yml
40
appveyor.yml
@ -1,40 +0,0 @@
|
|||||||
environment:
|
|
||||||
CURL_VERSION: 7.39.0
|
|
||||||
|
|
||||||
install:
|
|
||||||
- git submodule update --init --recursive
|
|
||||||
- cd C:\projects\
|
|
||||||
- if not exist dependencies2015.zip curl -kLO https://obsproject.com/downloads/dependencies2015.zip -f --retry 5 -C -
|
|
||||||
- 7z x dependencies2015.zip -odependencies2015
|
|
||||||
- set DepsPath32=%CD%\dependencies2015\win32
|
|
||||||
- set DepsPath64=%CD%\dependencies2015\win64
|
|
||||||
- call C:\projects\obs-websocket\CI\install-setup-qt.cmd
|
|
||||||
- set build_config=RelWithDebInfo
|
|
||||||
- call C:\projects\obs-websocket\CI\install-build-obs.cmd
|
|
||||||
- cd C:\projects\obs-websocket\
|
|
||||||
- mkdir build32
|
|
||||||
- mkdir build64
|
|
||||||
- cd ./build32
|
|
||||||
- cmake -G "Visual Studio 14 2015" -DQTDIR="%QTDIR32%" -DLibObs_DIR="C:\projects\obs-studio\build32\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
|
|
||||||
- cd ../build64
|
|
||||||
- cmake -G "Visual Studio 14 2015 Win64" -DQTDIR="%QTDIR64%" -DLibObs_DIR="C:\projects\obs-studio\build64\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" ..
|
|
||||||
|
|
||||||
build_script:
|
|
||||||
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build32\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
|
||||||
- call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build64\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
|
|
||||||
|
|
||||||
before_deploy:
|
|
||||||
- 7z a "C:\projects\obs-websocket\build.zip" C:\projects\obs-websocket\release\*
|
|
||||||
- set PATH=%PATH%;"C:\\Program Files (x86)\\Inno Setup 5"
|
|
||||||
- iscc "C:\projects\obs-websocket\installer\installer.iss"
|
|
||||||
|
|
||||||
deploy_script:
|
|
||||||
- ps: Push-AppveyorArtifact "C:\projects\obs-websocket\build.zip" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1)-Windows.zip"
|
|
||||||
- ps: Push-AppveyorArtifact "C:\projects\obs-websocket\installer\Output\obs-websocket-Windows-Installer.exe" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1)-Windows-Installer.exe"
|
|
||||||
|
|
||||||
test: off
|
|
||||||
|
|
||||||
cache:
|
|
||||||
- C:\projects\dependencies2015.zip
|
|
||||||
- C:\projects\obs-studio-last-tag-built.txt
|
|
||||||
- C:\projects\obs-studio\
|
|
@ -1,23 +1,183 @@
|
|||||||
pool:
|
variables:
|
||||||
vmImage: 'macOS-10.13'
|
isReleaseMode: ${{ startsWith(variables['Build.SourceBranch'], 'refs/tags/') }}
|
||||||
|
|
||||||
steps:
|
trigger:
|
||||||
- checkout: self
|
branches:
|
||||||
submodules: true
|
include:
|
||||||
|
- master
|
||||||
|
tags:
|
||||||
|
include:
|
||||||
|
- '*'
|
||||||
|
|
||||||
- script: ./CI/install-dependencies-macos.sh
|
jobs:
|
||||||
displayName: 'Install Dependencies'
|
- job: 'GenerateDocs'
|
||||||
|
condition: |
|
||||||
|
or(
|
||||||
|
eq(variables['Build.SourceBranch'], 'refs/heads/4.x-current'),
|
||||||
|
eq(variables['Build.SourceBranch'], 'refs/heads/master')
|
||||||
|
)
|
||||||
|
pool:
|
||||||
|
vmImage: 'ubuntu-18.04'
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
submodules: false
|
||||||
|
|
||||||
- script: ./CI/install-build-obs-macos.sh
|
- script: ./CI/generate-docs.sh
|
||||||
displayName: 'Build OBS'
|
displayName: 'Generate docs'
|
||||||
|
env:
|
||||||
|
CHECKOUT_REF: $(Build.SourceBranch)
|
||||||
|
GH_TOKEN: $(GithubToken)
|
||||||
|
|
||||||
- script: ./CI/build-macos.sh
|
- job: 'Build_Windows'
|
||||||
displayName: 'Build obs-websocket'
|
pool:
|
||||||
|
vmImage: 'windows-2019'
|
||||||
|
variables:
|
||||||
|
build_config: RelWithDebInfo
|
||||||
|
DepsBasePath: 'D:\obsdependencies'
|
||||||
|
DepsPath32: '$(DepsBasePath)\win32'
|
||||||
|
DepsPath64: '$(DepsBasePath)\win64'
|
||||||
|
QtBaseDir: 'D:\QtDep'
|
||||||
|
QTDIR32: '$(QtBaseDir)\5.10.1\msvc2017'
|
||||||
|
QTDIR64: '$(QtBaseDir)\5.10.1\msvc2017_64'
|
||||||
|
OBSPath: 'D:\obs-studio'
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
submodules: true
|
||||||
|
|
||||||
- script: ./CI/package-macos.sh
|
- script: ./CI/install-qt-win.cmd
|
||||||
displayName: 'Package'
|
displayName: 'Install Qt'
|
||||||
|
env:
|
||||||
|
QtBaseDir: $(QtBaseDir)
|
||||||
|
|
||||||
- task: PublishBuildArtifacts@1
|
- task: Cache@2
|
||||||
inputs:
|
displayName: Restore cached OBS Studio dependencies
|
||||||
pathtoPublish: './release'
|
inputs:
|
||||||
artifactName: 'build'
|
key: 'obsdeps | "$(Agent.OS)"'
|
||||||
|
restoreKeys: |
|
||||||
|
obsdeps | "$(Agent.OS)"
|
||||||
|
path: $(DepsBasePath)
|
||||||
|
|
||||||
|
- script: ./CI/download-obs-deps.cmd
|
||||||
|
displayName: 'Download OBS Studio dependencies'
|
||||||
|
|
||||||
|
- task: Cache@2
|
||||||
|
displayName: Restore cached OBS Studio builds
|
||||||
|
inputs:
|
||||||
|
key: 'obs | "$(Agent.OS)"'
|
||||||
|
restoreKeys: |
|
||||||
|
obs | "$(Agent.OS)"
|
||||||
|
path: $(OBSPath)
|
||||||
|
|
||||||
|
- script: ./CI/prepare-obs-windows.cmd
|
||||||
|
displayName: 'Checkout & CMake OBS Studio'
|
||||||
|
env:
|
||||||
|
build_config: $(build_config)
|
||||||
|
DepsPath32: $(DepsPath32)
|
||||||
|
DepsPath64: $(DepsPath64)
|
||||||
|
QTDIR32: $(QTDIR32)
|
||||||
|
QTDIR64: $(QTDIR64)
|
||||||
|
OBSPath: $(OBSPath)
|
||||||
|
|
||||||
|
- task: MSBuild@1
|
||||||
|
displayName: 'Build OBS Studio 32-bit'
|
||||||
|
inputs:
|
||||||
|
msbuildArguments: '/m /p:Configuration=$(build_config)'
|
||||||
|
solution: '$(OBSPath)\build32\obs-studio.sln'
|
||||||
|
|
||||||
|
- task: MSBuild@1
|
||||||
|
displayName: 'Build OBS Studio 64-bit'
|
||||||
|
inputs:
|
||||||
|
msbuildArguments: '/m /p:Configuration=$(build_config)'
|
||||||
|
solution: '$(OBSPath)\build64\obs-studio.sln'
|
||||||
|
|
||||||
|
- script: ./CI/prepare-windows.cmd
|
||||||
|
displayName: 'CMake obs-websocket'
|
||||||
|
env:
|
||||||
|
build_config: $(build_config)
|
||||||
|
QTDIR32: $(QTDIR32)
|
||||||
|
QTDIR64: $(QTDIR64)
|
||||||
|
OBSPath: $(OBSPath)
|
||||||
|
|
||||||
|
- task: MSBuild@1
|
||||||
|
displayName: 'Build obs-websocket 32-bit'
|
||||||
|
inputs:
|
||||||
|
msbuildArguments: '/m /p:Configuration=$(build_config)'
|
||||||
|
solution: '.\build32\obs-websocket.sln'
|
||||||
|
|
||||||
|
- task: MSBuild@1
|
||||||
|
displayName: 'Build obs-websocket 64-bit'
|
||||||
|
inputs:
|
||||||
|
msbuildArguments: '/m /p:Configuration=$(build_config)'
|
||||||
|
solution: '.\build64\obs-websocket.sln'
|
||||||
|
|
||||||
|
- script: ./CI/package-windows.cmd
|
||||||
|
displayName: 'Package obs-websocket'
|
||||||
|
|
||||||
|
- task: PublishBuildArtifacts@1
|
||||||
|
displayName: 'Upload package artifacts'
|
||||||
|
inputs:
|
||||||
|
pathtoPublish: './package'
|
||||||
|
artifactName: 'windows_build'
|
||||||
|
|
||||||
|
- job: 'Build_Linux'
|
||||||
|
pool:
|
||||||
|
vmImage: 'ubuntu-18.04'
|
||||||
|
variables:
|
||||||
|
BUILD_REASON: $(Build.Reason)
|
||||||
|
BRANCH_SHORT_NAME: $(Build.SourceBranchName)
|
||||||
|
BRANCH_FULL_NAME: $(Build.SourceBranch)
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- script: ./CI/install-dependencies-ubuntu.sh
|
||||||
|
displayName: 'Install dependencies'
|
||||||
|
|
||||||
|
- script: ./CI/build-ubuntu.sh
|
||||||
|
displayName: 'Build obs-websocket'
|
||||||
|
|
||||||
|
- script: ./CI/package-ubuntu.sh
|
||||||
|
displayName: 'Package obs-websocket'
|
||||||
|
|
||||||
|
- task: PublishBuildArtifacts@1
|
||||||
|
inputs:
|
||||||
|
pathtoPublish: './package'
|
||||||
|
artifactName: 'deb_build'
|
||||||
|
|
||||||
|
- job: 'Build_macOS'
|
||||||
|
pool:
|
||||||
|
vmImage: 'macos-10.14'
|
||||||
|
steps:
|
||||||
|
- checkout: self
|
||||||
|
submodules: true
|
||||||
|
|
||||||
|
- script: ./CI/install-dependencies-macos.sh
|
||||||
|
displayName: 'Install dependencies'
|
||||||
|
|
||||||
|
- script: ./CI/install-build-obs-macos.sh
|
||||||
|
displayName: 'Build OBS'
|
||||||
|
|
||||||
|
- script: ./CI/build-macos.sh
|
||||||
|
displayName: 'Build obs-websocket'
|
||||||
|
|
||||||
|
- task: InstallAppleCertificate@1
|
||||||
|
displayName: 'Install release signing certificates'
|
||||||
|
condition: eq(variables['isReleaseMode'], true)
|
||||||
|
inputs:
|
||||||
|
certSecureFile: 'Certificates.p12'
|
||||||
|
certPwd: $(secrets.macOS.certificatesImportPassword)
|
||||||
|
|
||||||
|
- script: ./CI/package-macos.sh
|
||||||
|
displayName: 'Package obs-websocket'
|
||||||
|
env:
|
||||||
|
RELEASE_MODE: $(isReleaseMode)
|
||||||
|
CODE_SIGNING_IDENTITY: $(secrets.macOS.codeSigningIdentity)
|
||||||
|
INSTALLER_SIGNING_IDENTITY: $(secrets.macOS.installerSigningIdentity)
|
||||||
|
AC_USERNAME: $(secrets.macOS.notarization.username)
|
||||||
|
AC_PASSWORD: $(secrets.macOS.notarization.password)
|
||||||
|
AC_PROVIDER_SHORTNAME: $(secrets.macOS.notarization.providerShortName)
|
||||||
|
|
||||||
|
- task: PublishBuildArtifacts@1
|
||||||
|
inputs:
|
||||||
|
pathtoPublish: './release'
|
||||||
|
artifactName: 'macos_build'
|
||||||
|
@ -10,7 +10,7 @@ OBSWebsocket.NotifyConnect.Message="Client %1 connected"
|
|||||||
OBSWebsocket.NotifyDisconnect.Title="WebSocket client disconnected"
|
OBSWebsocket.NotifyDisconnect.Title="WebSocket client disconnected"
|
||||||
OBSWebsocket.NotifyDisconnect.Message="Client %1 disconnected"
|
OBSWebsocket.NotifyDisconnect.Message="Client %1 disconnected"
|
||||||
OBSWebsocket.Server.StartFailed.Title="WebSockets Server failure"
|
OBSWebsocket.Server.StartFailed.Title="WebSockets Server failure"
|
||||||
OBSWebsocket.Server.StartFailed.Message="The WebSockets 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."
|
OBSWebsocket.Server.StartFailed.Message="The WebSockets 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 - Error message: %2"
|
||||||
OBSWebsocket.ProfileChanged.Started="WebSockets server enabled in this profile. Server started."
|
OBSWebsocket.ProfileChanged.Started="WebSockets server enabled in this profile. Server started."
|
||||||
OBSWebsocket.ProfileChanged.Stopped="WebSockets server disabled in this profile. Server stopped."
|
OBSWebsocket.ProfileChanged.Stopped="WebSockets server disabled in this profile. Server stopped."
|
||||||
OBSWebsocket.ProfileChanged.Restarted="WebSockets server port changed in this profile. Server restarted."
|
OBSWebsocket.ProfileChanged.Restarted="WebSockets server port changed in this profile. Server restarted."
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
|||||||
# obs-websocket 4.6.0 protocol reference
|
# obs-websocket 4.8.0 protocol reference
|
||||||
|
|
||||||
# General Introduction
|
# General Introduction
|
||||||
Messages are exchanged between the client and the server as JSON objects.
|
Messages are exchanged between the client and the server as JSON objects.
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!
|
||||||
|
|
||||||
#define MyAppName "obs-websocket"
|
#define MyAppName "obs-websocket"
|
||||||
#define MyAppVersion "4.6.0"
|
#define MyAppVersion "4.8.0"
|
||||||
#define MyAppPublisher "Stephane Lepin"
|
#define MyAppPublisher "Stephane Lepin"
|
||||||
#define MyAppURL "http://github.com/Palakis/obs-websocket"
|
#define MyAppURL "http://github.com/Palakis/obs-websocket"
|
||||||
|
|
||||||
|
254
src/Utils.cpp
254
src/Utils.cpp
@ -16,12 +16,15 @@ You should have received a copy of the GNU General Public License along
|
|||||||
with this program. If not, see <https://www.gnu.org/licenses/>
|
with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
#include <QtWidgets/QMainWindow>
|
#include <QtWidgets/QMainWindow>
|
||||||
#include <QtCore/QDir>
|
#include <QtCore/QDir>
|
||||||
#include <QtCore/QUrl>
|
#include <QtCore/QUrl>
|
||||||
|
|
||||||
#include <obs-frontend-api.h>
|
#include <obs-frontend-api.h>
|
||||||
#include <obs.hpp>
|
#include <obs.hpp>
|
||||||
|
#include <util/platform.h>
|
||||||
|
|
||||||
#include "obs-websocket.h"
|
#include "obs-websocket.h"
|
||||||
|
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
@ -101,9 +104,11 @@ obs_data_array_t* Utils::GetSceneItems(obs_source_t* source) {
|
|||||||
* @typedef {Object} `SceneItem` An OBS Scene Item.
|
* @typedef {Object} `SceneItem` An OBS Scene Item.
|
||||||
* @property {Number} `cy`
|
* @property {Number} `cy`
|
||||||
* @property {Number} `cx`
|
* @property {Number} `cx`
|
||||||
|
* @property {Number} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.
|
||||||
* @property {String} `name` The name of this Scene Item.
|
* @property {String} `name` The name of this Scene Item.
|
||||||
* @property {int} `id` Scene item ID
|
* @property {int} `id` Scene item ID
|
||||||
* @property {Boolean} `render` Whether or not this Scene Item is set to "visible".
|
* @property {Boolean} `render` Whether or not this Scene Item is set to "visible".
|
||||||
|
* @property {Boolean} `muted` Whether or not this Scene Item is muted.
|
||||||
* @property {Boolean} `locked` Whether or not this Scene Item is locked and can't be moved around
|
* @property {Boolean} `locked` Whether or not this Scene Item is locked and can't be moved around
|
||||||
* @property {Number} `source_cx`
|
* @property {Number} `source_cx`
|
||||||
* @property {Number} `source_cy`
|
* @property {Number} `source_cy`
|
||||||
@ -143,6 +148,8 @@ obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) {
|
|||||||
obs_data_set_double(data, "y", pos.y);
|
obs_data_set_double(data, "y", pos.y);
|
||||||
obs_data_set_int(data, "source_cx", (int)item_width);
|
obs_data_set_int(data, "source_cx", (int)item_width);
|
||||||
obs_data_set_int(data, "source_cy", (int)item_height);
|
obs_data_set_int(data, "source_cy", (int)item_height);
|
||||||
|
obs_data_set_bool(data, "muted", obs_source_muted(itemSource));
|
||||||
|
obs_data_set_int(data, "alignment", (int)obs_sceneitem_get_alignment(item));
|
||||||
obs_data_set_double(data, "cx", item_width * scale.x);
|
obs_data_set_double(data, "cx", item_width * scale.x);
|
||||||
obs_data_set_double(data, "cy", item_height * scale.y);
|
obs_data_set_double(data, "cy", item_height * scale.y);
|
||||||
obs_data_set_bool(data, "render", obs_sceneitem_visible(item));
|
obs_data_set_bool(data, "render", obs_sceneitem_visible(item));
|
||||||
@ -173,23 +180,11 @@ obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_sceneitem_t* Utils::GetSceneItemFromItem(obs_source_t* source, obs_data_t* item) {
|
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_scene_t* scene, QString name) {
|
||||||
OBSSceneItem sceneItem;
|
if (!scene) {
|
||||||
if (obs_data_has_user_value(item, "id")) {
|
return nullptr;
|
||||||
sceneItem = GetSceneItemFromId(source, obs_data_get_int(item, "id"));
|
}
|
||||||
if (obs_data_has_user_value(item, "name") &&
|
|
||||||
(QString)obs_source_get_name(obs_sceneitem_get_source(sceneItem)) !=
|
|
||||||
(QString)obs_data_get_string(item, "name")) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (obs_data_has_user_value(item, "name")) {
|
|
||||||
sceneItem = GetSceneItemFromName(source, obs_data_get_string(item, "name"));
|
|
||||||
}
|
|
||||||
return sceneItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, QString name) {
|
|
||||||
struct current_search {
|
struct current_search {
|
||||||
QString query;
|
QString query;
|
||||||
obs_sceneitem_t* result;
|
obs_sceneitem_t* result;
|
||||||
@ -199,11 +194,6 @@ obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, QString name)
|
|||||||
current_search search;
|
current_search search;
|
||||||
search.query = name;
|
search.query = name;
|
||||||
search.result = nullptr;
|
search.result = nullptr;
|
||||||
search.enumCallback = nullptr;
|
|
||||||
|
|
||||||
OBSScene scene = obs_scene_from_source(source);
|
|
||||||
if (!scene)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
search.enumCallback = [](
|
search.enumCallback = [](
|
||||||
obs_scene_t* scene,
|
obs_scene_t* scene,
|
||||||
@ -236,10 +226,13 @@ obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, QString name)
|
|||||||
return search.result;
|
return search.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO refactor this to unify it with GetSceneItemFromName
|
obs_sceneitem_t* Utils::GetSceneItemFromId(obs_scene_t* scene, int64_t id) {
|
||||||
obs_sceneitem_t* Utils::GetSceneItemFromId(obs_source_t* source, size_t id) {
|
if (!scene) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
struct current_search {
|
struct current_search {
|
||||||
size_t query;
|
int query;
|
||||||
obs_sceneitem_t* result;
|
obs_sceneitem_t* result;
|
||||||
bool (*enumCallback)(obs_scene_t*, obs_sceneitem_t*, void*);
|
bool (*enumCallback)(obs_scene_t*, obs_sceneitem_t*, void*);
|
||||||
};
|
};
|
||||||
@ -247,21 +240,16 @@ obs_sceneitem_t* Utils::GetSceneItemFromId(obs_source_t* source, size_t id) {
|
|||||||
current_search search;
|
current_search search;
|
||||||
search.query = id;
|
search.query = id;
|
||||||
search.result = nullptr;
|
search.result = nullptr;
|
||||||
search.enumCallback = nullptr;
|
|
||||||
|
|
||||||
OBSScene scene = obs_scene_from_source(source);
|
|
||||||
if (!scene)
|
|
||||||
return nullptr;
|
|
||||||
|
|
||||||
search.enumCallback = [](
|
search.enumCallback = [](
|
||||||
obs_scene_t* scene,
|
obs_scene_t* scene,
|
||||||
obs_sceneitem_t* currentItem,
|
obs_sceneitem_t* currentItem,
|
||||||
void* param)
|
void* param)
|
||||||
{
|
{
|
||||||
current_search* search = reinterpret_cast<current_search*>(param);
|
current_search* search = reinterpret_cast<current_search*>(param);
|
||||||
|
|
||||||
if (obs_sceneitem_is_group(currentItem)) {
|
if (obs_sceneitem_is_group(currentItem)) {
|
||||||
obs_sceneitem_group_enum_items(currentItem, search->enumCallback, param);
|
obs_sceneitem_group_enum_items(currentItem, search->enumCallback, search);
|
||||||
if (search->result) {
|
if (search->result) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -281,6 +269,49 @@ obs_sceneitem_t* Utils::GetSceneItemFromId(obs_source_t* source, size_t id) {
|
|||||||
return search.result;
|
return search.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obs_sceneitem_t* Utils::GetSceneItemFromItem(obs_scene_t* scene, obs_data_t* itemInfo) {
|
||||||
|
if (!scene) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataItemAutoRelease idInfoItem = obs_data_item_byname(itemInfo, "id");
|
||||||
|
int id = obs_data_item_get_int(idInfoItem);
|
||||||
|
|
||||||
|
OBSDataItemAutoRelease nameInfoItem = obs_data_item_byname(itemInfo, "name");
|
||||||
|
const char* name = obs_data_item_get_string(nameInfoItem);
|
||||||
|
|
||||||
|
if (idInfoItem) {
|
||||||
|
obs_sceneitem_t* sceneItem = GetSceneItemFromId(scene, id);
|
||||||
|
obs_source_t* sceneItemSource = obs_sceneitem_get_source(sceneItem);
|
||||||
|
|
||||||
|
QString sceneItemName = obs_source_get_name(sceneItemSource);
|
||||||
|
if (nameInfoItem && (QString(name) != sceneItemName)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sceneItem;
|
||||||
|
} else if (nameInfoItem) {
|
||||||
|
return GetSceneItemFromName(scene, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_sceneitem_t* Utils::GetSceneItemFromRequestField(obs_scene_t* scene, obs_data_item_t* dataItem)
|
||||||
|
{
|
||||||
|
enum obs_data_type dataType = obs_data_item_gettype(dataItem);
|
||||||
|
|
||||||
|
if (dataType == OBS_DATA_OBJECT) {
|
||||||
|
OBSDataAutoRelease itemData = obs_data_item_get_obj(dataItem);
|
||||||
|
return GetSceneItemFromItem(scene, itemData);
|
||||||
|
} else if (dataType == OBS_DATA_STRING) {
|
||||||
|
QString name = obs_data_item_get_string(dataItem);
|
||||||
|
return GetSceneItemFromName(scene, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
bool Utils::IsValidAlignment(const uint32_t alignment) {
|
bool Utils::IsValidAlignment(const uint32_t alignment) {
|
||||||
switch (alignment) {
|
switch (alignment) {
|
||||||
case OBS_ALIGN_CENTER:
|
case OBS_ALIGN_CENTER:
|
||||||
@ -319,17 +350,19 @@ obs_source_t* Utils::GetTransitionFromName(QString searchName) {
|
|||||||
return foundTransition;
|
return foundTransition;
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_source_t* Utils::GetSceneFromNameOrCurrent(QString sceneName) {
|
obs_scene_t* Utils::GetSceneFromNameOrCurrent(QString sceneName) {
|
||||||
// Both obs_frontend_get_current_scene() and obs_get_source_by_name()
|
// 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
|
// increase the returned source's refcount
|
||||||
obs_source_t* scene = nullptr;
|
OBSSourceAutoRelease sceneSource = nullptr;
|
||||||
|
|
||||||
if (sceneName.isEmpty() || sceneName.isNull())
|
if (sceneName.isEmpty() || sceneName.isNull()) {
|
||||||
scene = obs_frontend_get_current_scene();
|
sceneSource = obs_frontend_get_current_scene();
|
||||||
else
|
}
|
||||||
scene = obs_get_source_by_name(sceneName.toUtf8());
|
else {
|
||||||
|
sceneSource = obs_get_source_by_name(sceneName.toUtf8());
|
||||||
|
}
|
||||||
|
|
||||||
return scene;
|
return obs_scene_from_source(sceneSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_data_array_t* Utils::GetScenes() {
|
obs_data_array_t* Utils::GetScenes() {
|
||||||
@ -362,18 +395,37 @@ QSpinBox* Utils::GetTransitionDurationControl() {
|
|||||||
return window->findChild<QSpinBox*>("transitionDuration");
|
return window->findChild<QSpinBox*>("transitionDuration");
|
||||||
}
|
}
|
||||||
|
|
||||||
int Utils::GetTransitionDuration() {
|
int Utils::GetTransitionDuration(obs_source_t* transition) {
|
||||||
QSpinBox* control = GetTransitionDurationControl();
|
if (!transition || obs_source_get_type(transition) != OBS_SOURCE_TYPE_TRANSITION) {
|
||||||
if (control)
|
|
||||||
return control->value();
|
|
||||||
else
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Utils::SetTransitionDuration(int ms) {
|
QString transitionKind = obs_source_get_id(transition);
|
||||||
QSpinBox* control = GetTransitionDurationControl();
|
if (transitionKind == "cut_transition") {
|
||||||
if (control && ms >= 0)
|
// If this is a Cut transition, return 0
|
||||||
control->setValue(ms);
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obs_transition_fixed(transition)) {
|
||||||
|
// If this transition has a fixed duration (such as a Stinger),
|
||||||
|
// we don't currently have a way of retrieving that number.
|
||||||
|
// For now, return -1 to indicate that we don't know the actual duration.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition);
|
||||||
|
OBSDataAutoRelease destinationSettings = obs_source_get_private_settings(destinationScene);
|
||||||
|
|
||||||
|
// Detect if transition is the global transition or a transition override.
|
||||||
|
// Fetching the duration is different depending on the case.
|
||||||
|
obs_data_item_t* transitionDurationItem = obs_data_item_byname(destinationSettings, "transition_duration");
|
||||||
|
int duration = (
|
||||||
|
transitionDurationItem
|
||||||
|
? obs_data_item_get_int(transitionDurationItem)
|
||||||
|
: obs_frontend_get_transition_duration()
|
||||||
|
);
|
||||||
|
|
||||||
|
return duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Utils::SetTransitionByName(QString transitionName) {
|
bool Utils::SetTransitionByName(QString transitionName) {
|
||||||
@ -387,38 +439,35 @@ bool Utils::SetTransitionByName(QString transitionName) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QPushButton* Utils::GetPreviewModeButtonControl() {
|
obs_data_t* Utils::GetTransitionData(obs_source_t* transition) {
|
||||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
int duration = Utils::GetTransitionDuration(transition);
|
||||||
return main->findChild<QPushButton*>("modeSwitch");
|
if (duration < 0) {
|
||||||
}
|
blog(LOG_WARNING, "GetTransitionData: duration is negative !");
|
||||||
|
}
|
||||||
|
|
||||||
QLayout* Utils::GetPreviewLayout() {
|
OBSSourceAutoRelease sourceScene = obs_transition_get_source(transition, OBS_TRANSITION_SOURCE_A);
|
||||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition);
|
||||||
return main->findChild<QLayout*>("previewLayout");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Utils::TransitionToProgram() {
|
obs_data_t* transitionData = obs_data_create();
|
||||||
if (!obs_frontend_preview_program_mode_active())
|
obs_data_set_string(transitionData, "name", obs_source_get_name(transition));
|
||||||
return;
|
obs_data_set_string(transitionData, "type", obs_source_get_id(transition));
|
||||||
|
obs_data_set_int(transitionData, "duration", duration);
|
||||||
|
|
||||||
// WARNING : if the layout created in OBS' CreateProgramOptions() changes
|
// When a transition starts and while it is running, SOURCE_A is the source scene
|
||||||
// then this won't work as expected
|
// and SOURCE_B is the destination scene.
|
||||||
|
// Before the transition_end event is triggered on a transition, the destination scene
|
||||||
|
// goes into SOURCE_A and SOURCE_B becomes null. This means that, in transition_stop
|
||||||
|
// we don't know what was the source scene
|
||||||
|
// TODO fix this in libobs
|
||||||
|
|
||||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
bool isTransitionEndEvent = (sourceScene == destinationScene);
|
||||||
|
if (!isTransitionEndEvent) {
|
||||||
|
obs_data_set_string(transitionData, "from-scene", obs_source_get_name(sourceScene));
|
||||||
|
}
|
||||||
|
|
||||||
// The program options widget is the second item in the left-to-right layout
|
obs_data_set_string(transitionData, "to-scene", obs_source_get_name(destinationScene));
|
||||||
QWidget* programOptions = GetPreviewLayout()->itemAt(1)->widget();
|
|
||||||
|
|
||||||
// The "Transition" button lies in the mainButtonLayout
|
return transitionData;
|
||||||
// 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() {
|
QString Utils::OBSVersionString() {
|
||||||
@ -588,7 +637,7 @@ void Utils::StartReplayBuffer() {
|
|||||||
obs_output_t* rpOutput = obs_frontend_get_replay_buffer_output();
|
obs_output_t* rpOutput = obs_frontend_get_replay_buffer_output();
|
||||||
OBSData outputHotkeys = obs_hotkeys_save_output(rpOutput);
|
OBSData outputHotkeys = obs_hotkeys_save_output(rpOutput);
|
||||||
|
|
||||||
OBSData dummyBinding = obs_data_create();
|
OBSDataAutoRelease dummyBinding = obs_data_create();
|
||||||
obs_data_set_bool(dummyBinding, "control", true);
|
obs_data_set_bool(dummyBinding, "control", true);
|
||||||
obs_data_set_bool(dummyBinding, "alt", true);
|
obs_data_set_bool(dummyBinding, "alt", true);
|
||||||
obs_data_set_bool(dummyBinding, "shift", true);
|
obs_data_set_bool(dummyBinding, "shift", true);
|
||||||
@ -744,6 +793,19 @@ obs_data_t* Utils::GetSceneItemPropertiesData(obs_sceneitem_t* sceneItem) {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obs_data_t* Utils::GetSourceFilterInfo(obs_source_t* filter, bool includeSettings)
|
||||||
|
{
|
||||||
|
obs_data_t* data = obs_data_create();
|
||||||
|
obs_data_set_bool(data, "enabled", obs_source_enabled(filter));
|
||||||
|
obs_data_set_string(data, "type", obs_source_get_id(filter));
|
||||||
|
obs_data_set_string(data, "name", obs_source_get_name(filter));
|
||||||
|
if (includeSettings) {
|
||||||
|
OBSDataAutoRelease settings = obs_source_get_settings(filter);
|
||||||
|
obs_data_set_obj(data, "settings", settings);
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
obs_data_array_t* Utils::GetSourceFiltersList(obs_source_t* source, bool includeSettings)
|
obs_data_array_t* Utils::GetSourceFiltersList(obs_source_t* source, bool includeSettings)
|
||||||
{
|
{
|
||||||
struct enum_params {
|
struct enum_params {
|
||||||
@ -764,14 +826,36 @@ obs_data_array_t* Utils::GetSourceFiltersList(obs_source_t* source, bool include
|
|||||||
{
|
{
|
||||||
auto enumParams = reinterpret_cast<struct enum_params*>(param);
|
auto enumParams = reinterpret_cast<struct enum_params*>(param);
|
||||||
|
|
||||||
OBSDataAutoRelease filter = obs_data_create();
|
OBSDataAutoRelease filterData = Utils::GetSourceFilterInfo(child, enumParams->includeSettings);
|
||||||
obs_data_set_string(filter, "type", obs_source_get_id(child));
|
obs_data_array_push_back(enumParams->filters, filterData);
|
||||||
obs_data_set_string(filter, "name", obs_source_get_name(child));
|
|
||||||
if (enumParams->includeSettings) {
|
|
||||||
obs_data_set_obj(filter, "settings", obs_source_get_settings(child));
|
|
||||||
}
|
|
||||||
obs_data_array_push_back(enumParams->filters, filter);
|
|
||||||
}, &enumParams);
|
}, &enumParams);
|
||||||
|
|
||||||
return enumParams.filters;
|
return enumParams.filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void getPauseRecordingFunctions(RecordingPausedFunction* recPausedFuncPtr, PauseRecordingFunction* pauseRecFuncPtr)
|
||||||
|
{
|
||||||
|
void* frontendApi = os_dlopen("obs-frontend-api");
|
||||||
|
|
||||||
|
if (recPausedFuncPtr) {
|
||||||
|
*recPausedFuncPtr = (RecordingPausedFunction)os_dlsym(frontendApi, "obs_frontend_recording_paused");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pauseRecFuncPtr) {
|
||||||
|
*pauseRecFuncPtr = (PauseRecordingFunction)os_dlsym(frontendApi, "obs_frontend_recording_pause");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Utils::nsToTimestamp(uint64_t ns)
|
||||||
|
{
|
||||||
|
uint64_t ms = ns / 1000000ULL;
|
||||||
|
uint64_t secs = ms / 1000ULL;
|
||||||
|
uint64_t minutes = secs / 60ULL;
|
||||||
|
|
||||||
|
uint64_t hoursPart = minutes / 60ULL;
|
||||||
|
uint64_t minutesPart = minutes % 60ULL;
|
||||||
|
uint64_t secsPart = secs % 60ULL;
|
||||||
|
uint64_t msPart = ms % 1000ULL;
|
||||||
|
|
||||||
|
return QString::asprintf("%02" PRIu64 ":%02" PRIu64 ":%02" PRIu64 ".%03" PRIu64, hoursPart, minutesPart, secsPart, msPart);
|
||||||
|
}
|
||||||
|
81
src/Utils.h
81
src/Utils.h
@ -31,55 +31,58 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
|||||||
#include <obs-module.h>
|
#include <obs-module.h>
|
||||||
#include <util/config-file.h>
|
#include <util/config-file.h>
|
||||||
|
|
||||||
class Utils {
|
typedef void(*PauseRecordingFunction)(bool);
|
||||||
public:
|
typedef bool(*RecordingPausedFunction)();
|
||||||
static obs_data_array_t* StringListToArray(char** strings, const 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_sceneitem_t* GetSceneItemFromId(obs_source_t* source, size_t id);
|
|
||||||
static obs_sceneitem_t* GetSceneItemFromItem(obs_source_t* source, obs_data_t* item);
|
|
||||||
static obs_source_t* GetTransitionFromName(QString transitionName);
|
|
||||||
static obs_source_t* GetSceneFromNameOrCurrent(QString sceneName);
|
|
||||||
static obs_data_t* GetSceneItemPropertiesData(obs_sceneitem_t* item);
|
|
||||||
|
|
||||||
static obs_data_array_t* GetSourceFiltersList(obs_source_t* source, bool includeSettings);
|
namespace Utils {
|
||||||
|
obs_data_array_t* StringListToArray(char** strings, const char* key);
|
||||||
|
obs_data_array_t* GetSceneItems(obs_source_t* source);
|
||||||
|
obs_data_t* GetSceneItemData(obs_sceneitem_t* item);
|
||||||
|
|
||||||
static bool IsValidAlignment(const uint32_t alignment);
|
// These functions support nested lookup into groups
|
||||||
|
obs_sceneitem_t* GetSceneItemFromName(obs_scene_t* scene, QString name);
|
||||||
|
obs_sceneitem_t* GetSceneItemFromId(obs_scene_t* scene, int64_t id);
|
||||||
|
obs_sceneitem_t* GetSceneItemFromItem(obs_scene_t* scene, obs_data_t* item);
|
||||||
|
obs_sceneitem_t* GetSceneItemFromRequestField(obs_scene_t* scene, obs_data_item_t* dataItem);
|
||||||
|
|
||||||
static obs_data_array_t* GetScenes();
|
obs_scene_t* GetSceneFromNameOrCurrent(QString sceneName);
|
||||||
static obs_data_t* GetSceneData(obs_source_t* source);
|
obs_data_t* GetSceneItemPropertiesData(obs_sceneitem_t* item);
|
||||||
|
|
||||||
|
obs_data_t* GetSourceFilterInfo(obs_source_t* filter, bool includeSettings);
|
||||||
|
obs_data_array_t* GetSourceFiltersList(obs_source_t* source, bool includeSettings);
|
||||||
|
|
||||||
|
bool IsValidAlignment(const uint32_t alignment);
|
||||||
|
|
||||||
|
obs_data_array_t* GetScenes();
|
||||||
|
obs_data_t* GetSceneData(obs_source_t* source);
|
||||||
|
|
||||||
// TODO contribute a proper frontend API method for this to OBS and remove this hack
|
// TODO contribute a proper frontend API method for this to OBS and remove this hack
|
||||||
static QSpinBox* GetTransitionDurationControl();
|
QSpinBox* GetTransitionDurationControl();
|
||||||
static int GetTransitionDuration();
|
int GetTransitionDuration(obs_source_t* transition);
|
||||||
static void SetTransitionDuration(int ms);
|
obs_source_t* GetTransitionFromName(QString transitionName);
|
||||||
|
bool SetTransitionByName(QString transitionName);
|
||||||
|
obs_data_t* GetTransitionData(obs_source_t* transition);
|
||||||
|
|
||||||
static bool SetTransitionByName(QString transitionName);
|
QString OBSVersionString();
|
||||||
|
|
||||||
static QPushButton* GetPreviewModeButtonControl();
|
QSystemTrayIcon* GetTrayIcon();
|
||||||
static QLayout* GetPreviewLayout();
|
void SysTrayNotify(
|
||||||
|
|
||||||
// TODO contribute a proper frontend API method for this to OBS and remove this hack
|
|
||||||
static void TransitionToProgram();
|
|
||||||
|
|
||||||
static QString OBSVersionString();
|
|
||||||
|
|
||||||
static QSystemTrayIcon* GetTrayIcon();
|
|
||||||
static void SysTrayNotify(
|
|
||||||
QString text,
|
QString text,
|
||||||
QSystemTrayIcon::MessageIcon n,
|
QSystemTrayIcon::MessageIcon n,
|
||||||
QString title = QString("obs-websocket"));
|
QString title = QString("obs-websocket"));
|
||||||
|
|
||||||
static const char* GetRecordingFolder();
|
const char* GetRecordingFolder();
|
||||||
static bool SetRecordingFolder(const char* path);
|
bool SetRecordingFolder(const char* path);
|
||||||
|
|
||||||
static QString ParseDataToQueryString(obs_data_t* data);
|
QString ParseDataToQueryString(obs_data_t* data);
|
||||||
static obs_hotkey_t* FindHotkeyByName(QString name);
|
obs_hotkey_t* FindHotkeyByName(QString name);
|
||||||
static bool ReplayBufferEnabled();
|
|
||||||
static void StartReplayBuffer();
|
bool ReplayBufferEnabled();
|
||||||
static bool IsRPHotkeySet();
|
void StartReplayBuffer();
|
||||||
static const char* GetFilenameFormatting();
|
bool IsRPHotkeySet();
|
||||||
static bool SetFilenameFormatting(const char* filenameFormatting);
|
|
||||||
|
const char* GetFilenameFormatting();
|
||||||
|
bool SetFilenameFormatting(const char* filenameFormatting);
|
||||||
|
|
||||||
|
QString nsToTimestamp(uint64_t ns);
|
||||||
};
|
};
|
||||||
|
602
src/WSEvents.cpp
602
src/WSEvents.cpp
@ -17,45 +17,21 @@
|
|||||||
* with this program. If not, see <https://www.gnu.org/licenses/>
|
* with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
#include <util/platform.h>
|
#include <util/platform.h>
|
||||||
|
#include <media-io/video-io.h>
|
||||||
|
|
||||||
#include <QtWidgets/QPushButton>
|
#include <QtWidgets/QPushButton>
|
||||||
|
|
||||||
#include "Config.h"
|
|
||||||
#include "Utils.h"
|
|
||||||
#include "WSEvents.h"
|
#include "WSEvents.h"
|
||||||
|
|
||||||
#include "obs-websocket.h"
|
#include "obs-websocket.h"
|
||||||
|
#include "Config.h"
|
||||||
|
#include "Utils.h"
|
||||||
|
#include "rpc/RpcEvent.h"
|
||||||
|
|
||||||
#define STATUS_INTERVAL 2000
|
#define STATUS_INTERVAL 2000
|
||||||
|
|
||||||
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, "%02lu:%02lu:%02lu.%03lu", hoursPart, minutesPart, secsPart, msPart);
|
|
||||||
|
|
||||||
return ts;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* sourceTypeToString(obs_source_type type) {
|
const char* sourceTypeToString(obs_source_type type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case OBS_SOURCE_TYPE_INPUT:
|
case OBS_SOURCE_TYPE_INPUT:
|
||||||
@ -86,7 +62,8 @@ const char* calldata_get_string(const calldata_t* data, const char* name) {
|
|||||||
WSEvents::WSEvents(WSServerPtr srv) :
|
WSEvents::WSEvents(WSServerPtr srv) :
|
||||||
_srv(srv),
|
_srv(srv),
|
||||||
_streamStarttime(0),
|
_streamStarttime(0),
|
||||||
_recStarttime(0),
|
_lastBytesSent(0),
|
||||||
|
_lastBytesSentTime(0),
|
||||||
HeartbeatIsActive(false),
|
HeartbeatIsActive(false),
|
||||||
pulse(false)
|
pulse(false)
|
||||||
{
|
{
|
||||||
@ -143,112 +120,138 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event == OBS_FRONTEND_EVENT_SCENE_CHANGED) {
|
switch (event) {
|
||||||
owner->OnSceneChange();
|
case OBS_FRONTEND_EVENT_FINISHED_LOADING:
|
||||||
}
|
owner->hookTransitionPlaybackEvents();
|
||||||
else if (event == OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED) {
|
break;
|
||||||
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->streamStatusTimer.start(STATUS_INTERVAL);
|
|
||||||
owner->StreamStatus();
|
|
||||||
|
|
||||||
owner->OnStreamStarted();
|
case OBS_FRONTEND_EVENT_SCENE_CHANGED:
|
||||||
}
|
owner->OnSceneChange();
|
||||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPING) {
|
break;
|
||||||
owner->streamStatusTimer.stop();
|
|
||||||
owner->OnStreamStopping();
|
case OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED:
|
||||||
}
|
owner->OnSceneListChange();
|
||||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPED) {
|
break;
|
||||||
owner->OnStreamStopped();
|
|
||||||
}
|
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED:
|
||||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTING) {
|
owner->hookTransitionPlaybackEvents();
|
||||||
owner->OnRecordingStarting();
|
owner->OnSceneCollectionChange();
|
||||||
}
|
break;
|
||||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTED) {
|
|
||||||
owner->OnRecordingStarted();
|
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED:
|
||||||
}
|
owner->OnSceneCollectionListChange();
|
||||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPING) {
|
break;
|
||||||
owner->OnRecordingStopping();
|
|
||||||
}
|
case OBS_FRONTEND_EVENT_TRANSITION_CHANGED:
|
||||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPED) {
|
owner->OnTransitionChange();
|
||||||
owner->OnRecordingStopped();
|
break;
|
||||||
}
|
|
||||||
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING) {
|
case OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED:
|
||||||
owner->OnReplayStarting();
|
owner->hookTransitionPlaybackEvents();
|
||||||
}
|
owner->OnTransitionListChange();
|
||||||
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED) {
|
break;
|
||||||
owner->OnReplayStarted();
|
|
||||||
}
|
case OBS_FRONTEND_EVENT_PROFILE_CHANGED:
|
||||||
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING) {
|
owner->OnProfileChange();
|
||||||
owner->OnReplayStopping();
|
break;
|
||||||
}
|
|
||||||
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED) {
|
case OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED:
|
||||||
owner->OnReplayStopped();
|
owner->OnProfileListChange();
|
||||||
}
|
break;
|
||||||
else if (event == OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED) {
|
|
||||||
owner->OnStudioModeSwitched(true);
|
case OBS_FRONTEND_EVENT_STREAMING_STARTING:
|
||||||
}
|
owner->OnStreamStarting();
|
||||||
else if (event == OBS_FRONTEND_EVENT_STUDIO_MODE_DISABLED) {
|
break;
|
||||||
owner->OnStudioModeSwitched(false);
|
|
||||||
}
|
case OBS_FRONTEND_EVENT_STREAMING_STARTED:
|
||||||
else if (event == OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED) {
|
owner->streamStatusTimer.start(STATUS_INTERVAL);
|
||||||
owner->OnPreviewSceneChanged();
|
owner->StreamStatus();
|
||||||
}
|
owner->OnStreamStarted();
|
||||||
else if (event == OBS_FRONTEND_EVENT_EXIT) {
|
break;
|
||||||
owner->OnExit();
|
|
||||||
|
case OBS_FRONTEND_EVENT_STREAMING_STOPPING:
|
||||||
|
owner->streamStatusTimer.stop();
|
||||||
|
owner->OnStreamStopping();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBS_FRONTEND_EVENT_STREAMING_STOPPED:
|
||||||
|
owner->OnStreamStopped();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBS_FRONTEND_EVENT_RECORDING_STARTING:
|
||||||
|
owner->OnRecordingStarting();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBS_FRONTEND_EVENT_RECORDING_STARTED:
|
||||||
|
owner->OnRecordingStarted();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBS_FRONTEND_EVENT_RECORDING_STOPPING:
|
||||||
|
owner->OnRecordingStopping();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBS_FRONTEND_EVENT_RECORDING_STOPPED:
|
||||||
|
owner->OnRecordingStopped();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBS_FRONTEND_EVENT_RECORDING_PAUSED:
|
||||||
|
owner->OnRecordingPaused();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBS_FRONTEND_EVENT_RECORDING_UNPAUSED:
|
||||||
|
owner->OnRecordingResumed();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING:
|
||||||
|
owner->OnReplayStarting();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED:
|
||||||
|
owner->OnReplayStarted();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING:
|
||||||
|
owner->OnReplayStopping();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED:
|
||||||
|
owner->OnReplayStopped();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED:
|
||||||
|
owner->OnStudioModeSwitched(true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBS_FRONTEND_EVENT_STUDIO_MODE_DISABLED:
|
||||||
|
owner->OnStudioModeSwitched(false);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED:
|
||||||
|
owner->OnPreviewSceneChanged();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case OBS_FRONTEND_EVENT_EXIT:
|
||||||
|
owner->unhookTransitionPlaybackEvents();
|
||||||
|
owner->OnExit();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::broadcastUpdate(const char* updateType,
|
void WSEvents::broadcastUpdate(const char* updateType,
|
||||||
obs_data_t* additionalFields = nullptr)
|
obs_data_t* additionalFields = nullptr)
|
||||||
{
|
{
|
||||||
OBSDataAutoRelease update = obs_data_create();
|
std::optional<uint64_t> streamTime;
|
||||||
obs_data_set_string(update, "update-type", updateType);
|
|
||||||
|
|
||||||
const char* ts = nullptr;
|
|
||||||
if (obs_frontend_streaming_active()) {
|
if (obs_frontend_streaming_active()) {
|
||||||
ts = nsToTimestamp(os_gettime_ns() - _streamStarttime);
|
streamTime = std::make_optional(getStreamingTime());
|
||||||
obs_data_set_string(update, "stream-timecode", ts);
|
|
||||||
bfree((void*)ts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<uint64_t> recordingTime;
|
||||||
if (obs_frontend_recording_active()) {
|
if (obs_frontend_recording_active()) {
|
||||||
ts = nsToTimestamp(os_gettime_ns() - _recStarttime);
|
recordingTime = std::make_optional(getRecordingTime());
|
||||||
obs_data_set_string(update, "rec-timecode", ts);
|
|
||||||
bfree((void*)ts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (additionalFields)
|
RpcEvent event(QString(updateType), streamTime, recordingTime, additionalFields);
|
||||||
obs_data_apply(update, additionalFields);
|
_srv->broadcast(event);
|
||||||
|
|
||||||
QString json = obs_data_get_json(update);
|
|
||||||
_srv->broadcast(json.toStdString());
|
|
||||||
|
|
||||||
if (GetConfig()->DebugEnabled) {
|
|
||||||
blog(LOG_INFO, "Update << '%s'", json.toUtf8().constData());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::connectSourceSignals(obs_source_t* source) {
|
void WSEvents::connectSourceSignals(obs_source_t* source) {
|
||||||
@ -279,13 +282,12 @@ void WSEvents::connectSourceSignals(obs_source_t* source) {
|
|||||||
signal_handler_connect(sh, "item_remove", OnSceneItemDelete, this);
|
signal_handler_connect(sh, "item_remove", OnSceneItemDelete, this);
|
||||||
signal_handler_connect(sh,
|
signal_handler_connect(sh,
|
||||||
"item_visible", OnSceneItemVisibilityChanged, this);
|
"item_visible", OnSceneItemVisibilityChanged, this);
|
||||||
|
signal_handler_connect(sh,
|
||||||
|
"item_locked", OnSceneItemLockChanged, this);
|
||||||
signal_handler_connect(sh, "item_transform", OnSceneItemTransform, this);
|
signal_handler_connect(sh, "item_transform", OnSceneItemTransform, this);
|
||||||
signal_handler_connect(sh, "item_select", OnSceneItemSelected, this);
|
signal_handler_connect(sh, "item_select", OnSceneItemSelected, this);
|
||||||
signal_handler_connect(sh, "item_deselect", OnSceneItemDeselected, this);
|
signal_handler_connect(sh, "item_deselect", OnSceneItemDeselected, this);
|
||||||
}
|
}
|
||||||
if (sourceType == OBS_SOURCE_TYPE_TRANSITION) {
|
|
||||||
signal_handler_connect(sh, "transition_start", OnTransitionBegin, this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSEvents::disconnectSourceSignals(obs_source_t* source) {
|
void WSEvents::disconnectSourceSignals(obs_source_t* source) {
|
||||||
@ -311,33 +313,98 @@ void WSEvents::disconnectSourceSignals(obs_source_t* source) {
|
|||||||
signal_handler_disconnect(sh, "item_remove", OnSceneItemDelete, this);
|
signal_handler_disconnect(sh, "item_remove", OnSceneItemDelete, this);
|
||||||
signal_handler_disconnect(sh,
|
signal_handler_disconnect(sh,
|
||||||
"item_visible", OnSceneItemVisibilityChanged, this);
|
"item_visible", OnSceneItemVisibilityChanged, this);
|
||||||
|
signal_handler_disconnect(sh,
|
||||||
|
"item_locked", OnSceneItemLockChanged, this);
|
||||||
signal_handler_disconnect(sh, "item_transform", OnSceneItemTransform, this);
|
signal_handler_disconnect(sh, "item_transform", OnSceneItemTransform, this);
|
||||||
signal_handler_disconnect(sh, "item_select", OnSceneItemSelected, this);
|
signal_handler_disconnect(sh, "item_select", OnSceneItemSelected, this);
|
||||||
signal_handler_disconnect(sh, "item_deselect", OnSceneItemDeselected, this);
|
signal_handler_disconnect(sh, "item_deselect", OnSceneItemDeselected, this);
|
||||||
|
|
||||||
signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this);
|
signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this);
|
||||||
|
signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this);
|
||||||
|
signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t WSEvents::GetStreamingTime() {
|
void WSEvents::connectFilterSignals(obs_source_t* filter) {
|
||||||
if (obs_frontend_streaming_active())
|
if (!filter) {
|
||||||
return (os_gettime_ns() - _streamStarttime);
|
return;
|
||||||
else
|
}
|
||||||
|
|
||||||
|
signal_handler_t* sh = obs_source_get_signal_handler(filter);
|
||||||
|
|
||||||
|
signal_handler_connect(sh, "enable", OnSourceFilterVisibilityChanged, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSEvents::disconnectFilterSignals(obs_source_t* filter) {
|
||||||
|
if (!filter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
signal_handler_t* sh = obs_source_get_signal_handler(filter);
|
||||||
|
|
||||||
|
signal_handler_disconnect(sh, "enable", OnSourceFilterVisibilityChanged, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSEvents::hookTransitionPlaybackEvents() {
|
||||||
|
obs_frontend_source_list transitions = {};
|
||||||
|
obs_frontend_get_transitions(&transitions);
|
||||||
|
|
||||||
|
for (uint i = 0; i < transitions.sources.num; i++) {
|
||||||
|
obs_source_t* transition = transitions.sources.array[i];
|
||||||
|
signal_handler_t* sh = obs_source_get_signal_handler(transition);
|
||||||
|
signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this);
|
||||||
|
signal_handler_connect(sh, "transition_start", OnTransitionBegin, this);
|
||||||
|
signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this);
|
||||||
|
signal_handler_connect(sh, "transition_stop", OnTransitionEnd, this);
|
||||||
|
signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this);
|
||||||
|
signal_handler_connect(sh, "transition_video_stop", OnTransitionVideoEnd, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_frontend_source_list_free(&transitions);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSEvents::unhookTransitionPlaybackEvents() {
|
||||||
|
obs_frontend_source_list transitions = {};
|
||||||
|
obs_frontend_get_transitions(&transitions);
|
||||||
|
|
||||||
|
for (uint i = 0; i < transitions.sources.num; i++) {
|
||||||
|
obs_source_t* transition = transitions.sources.array[i];
|
||||||
|
signal_handler_t* sh = obs_source_get_signal_handler(transition);
|
||||||
|
signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this);
|
||||||
|
signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this);
|
||||||
|
signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_frontend_source_list_free(&transitions);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t getOutputRunningTime(obs_output_t* output) {
|
||||||
|
if (!output || !obs_output_active(output)) {
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
video_t* video = obs_output_video(output);
|
||||||
|
uint64_t frameTimeNs = video_output_get_frame_time(video);
|
||||||
|
int totalFrames = obs_output_get_total_frames(output);
|
||||||
|
|
||||||
|
return (((uint64_t)totalFrames) * frameTimeNs);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* WSEvents::GetStreamingTimecode() {
|
uint64_t WSEvents::getStreamingTime() {
|
||||||
return nsToTimestamp(GetStreamingTime());
|
OBSOutputAutoRelease streamingOutput = obs_frontend_get_streaming_output();
|
||||||
|
return getOutputRunningTime(streamingOutput);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t WSEvents::GetRecordingTime() {
|
uint64_t WSEvents::getRecordingTime() {
|
||||||
if (obs_frontend_recording_active())
|
OBSOutputAutoRelease recordingOutput = obs_frontend_get_recording_output();
|
||||||
return (os_gettime_ns() - _recStarttime);
|
return getOutputRunningTime(recordingOutput);
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* WSEvents::GetRecordingTimecode() {
|
QString WSEvents::getStreamingTimecode() {
|
||||||
return nsToTimestamp(GetRecordingTime());
|
return Utils::nsToTimestamp(getStreamingTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WSEvents::getRecordingTimecode() {
|
||||||
|
return Utils::nsToTimestamp(getRecordingTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -490,6 +557,7 @@ void WSEvents::OnStreamStarting() {
|
|||||||
void WSEvents::OnStreamStarted() {
|
void WSEvents::OnStreamStarted() {
|
||||||
_streamStarttime = os_gettime_ns();
|
_streamStarttime = os_gettime_ns();
|
||||||
_lastBytesSent = 0;
|
_lastBytesSent = 0;
|
||||||
|
|
||||||
broadcastUpdate("StreamStarted");
|
broadcastUpdate("StreamStarted");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -506,7 +574,6 @@ void WSEvents::OnStreamStarted() {
|
|||||||
void WSEvents::OnStreamStopping() {
|
void WSEvents::OnStreamStopping() {
|
||||||
OBSDataAutoRelease data = obs_data_create();
|
OBSDataAutoRelease data = obs_data_create();
|
||||||
obs_data_set_bool(data, "preview-only", false);
|
obs_data_set_bool(data, "preview-only", false);
|
||||||
|
|
||||||
broadcastUpdate("StreamStopping", data);
|
broadcastUpdate("StreamStopping", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -520,6 +587,7 @@ void WSEvents::OnStreamStopping() {
|
|||||||
*/
|
*/
|
||||||
void WSEvents::OnStreamStopped() {
|
void WSEvents::OnStreamStopped() {
|
||||||
_streamStarttime = 0;
|
_streamStarttime = 0;
|
||||||
|
|
||||||
broadcastUpdate("StreamStopped");
|
broadcastUpdate("StreamStopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -544,7 +612,6 @@ void WSEvents::OnRecordingStarting() {
|
|||||||
* @since 0.3
|
* @since 0.3
|
||||||
*/
|
*/
|
||||||
void WSEvents::OnRecordingStarted() {
|
void WSEvents::OnRecordingStarted() {
|
||||||
_recStarttime = os_gettime_ns();
|
|
||||||
broadcastUpdate("RecordingStarted");
|
broadcastUpdate("RecordingStarted");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -569,10 +636,33 @@ void WSEvents::OnRecordingStopping() {
|
|||||||
* @since 0.3
|
* @since 0.3
|
||||||
*/
|
*/
|
||||||
void WSEvents::OnRecordingStopped() {
|
void WSEvents::OnRecordingStopped() {
|
||||||
_recStarttime = 0;
|
|
||||||
broadcastUpdate("RecordingStopped");
|
broadcastUpdate("RecordingStopped");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current recording paused
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name RecordingPaused
|
||||||
|
* @category recording
|
||||||
|
* @since 4.7.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnRecordingPaused() {
|
||||||
|
broadcastUpdate("RecordingPaused");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Current recording resumed
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name RecordingResumed
|
||||||
|
* @category recording
|
||||||
|
* @since 4.7.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnRecordingResumed() {
|
||||||
|
broadcastUpdate("RecordingResumed");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A request to start the replay buffer has been issued.
|
* A request to start the replay buffer has been issued.
|
||||||
*
|
*
|
||||||
@ -664,6 +754,7 @@ void WSEvents::OnExit() {
|
|||||||
void WSEvents::StreamStatus() {
|
void WSEvents::StreamStatus() {
|
||||||
bool streamingActive = obs_frontend_streaming_active();
|
bool streamingActive = obs_frontend_streaming_active();
|
||||||
bool recordingActive = obs_frontend_recording_active();
|
bool recordingActive = obs_frontend_recording_active();
|
||||||
|
bool recordingPaused = obs_frontend_recording_paused();
|
||||||
bool replayBufferActive = obs_frontend_replay_buffer_active();
|
bool replayBufferActive = obs_frontend_replay_buffer_active();
|
||||||
|
|
||||||
OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output();
|
OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output();
|
||||||
@ -690,9 +781,7 @@ void WSEvents::StreamStatus() {
|
|||||||
_lastBytesSent = bytesSent;
|
_lastBytesSent = bytesSent;
|
||||||
_lastBytesSentTime = bytesSentTime;
|
_lastBytesSentTime = bytesSentTime;
|
||||||
|
|
||||||
uint64_t totalStreamTime =
|
uint64_t totalStreamTime = (getStreamingTime() / 1000000000ULL);
|
||||||
(os_gettime_ns() - _streamStarttime) / 1000000000;
|
|
||||||
|
|
||||||
int totalFrames = obs_output_get_total_frames(streamOutput);
|
int totalFrames = obs_output_get_total_frames(streamOutput);
|
||||||
int droppedFrames = obs_output_get_frames_dropped(streamOutput);
|
int droppedFrames = obs_output_get_frames_dropped(streamOutput);
|
||||||
|
|
||||||
@ -702,6 +791,7 @@ void WSEvents::StreamStatus() {
|
|||||||
|
|
||||||
obs_data_set_bool(data, "streaming", streamingActive);
|
obs_data_set_bool(data, "streaming", streamingActive);
|
||||||
obs_data_set_bool(data, "recording", recordingActive);
|
obs_data_set_bool(data, "recording", recordingActive);
|
||||||
|
obs_data_set_bool(data, "recording-paused", recordingPaused);
|
||||||
obs_data_set_bool(data, "replay-buffer-active", replayBufferActive);
|
obs_data_set_bool(data, "replay-buffer-active", replayBufferActive);
|
||||||
|
|
||||||
obs_data_set_int(data, "bytes-per-sec", bytesPerSec);
|
obs_data_set_int(data, "bytes-per-sec", bytesPerSec);
|
||||||
@ -748,6 +838,7 @@ void WSEvents::Heartbeat() {
|
|||||||
|
|
||||||
bool streamingActive = obs_frontend_streaming_active();
|
bool streamingActive = obs_frontend_streaming_active();
|
||||||
bool recordingActive = obs_frontend_recording_active();
|
bool recordingActive = obs_frontend_recording_active();
|
||||||
|
bool recordingPaused = obs_frontend_recording_paused();
|
||||||
|
|
||||||
OBSDataAutoRelease data = obs_data_create();
|
OBSDataAutoRelease data = obs_data_create();
|
||||||
OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output();
|
OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output();
|
||||||
@ -756,23 +847,24 @@ void WSEvents::Heartbeat() {
|
|||||||
pulse = !pulse;
|
pulse = !pulse;
|
||||||
obs_data_set_bool(data, "pulse", pulse);
|
obs_data_set_bool(data, "pulse", pulse);
|
||||||
|
|
||||||
obs_data_set_string(data, "current-profile", obs_frontend_get_current_profile());
|
char* currentProfile = obs_frontend_get_current_profile();
|
||||||
|
obs_data_set_string(data, "current-profile", currentProfile);
|
||||||
|
bfree(currentProfile);
|
||||||
|
|
||||||
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
|
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
|
||||||
obs_data_set_string(data, "current-scene", obs_source_get_name(currentScene));
|
obs_data_set_string(data, "current-scene", obs_source_get_name(currentScene));
|
||||||
|
|
||||||
obs_data_set_bool(data, "streaming", streamingActive);
|
obs_data_set_bool(data, "streaming", streamingActive);
|
||||||
if (streamingActive) {
|
if (streamingActive) {
|
||||||
uint64_t totalStreamTime = (os_gettime_ns() - _streamStarttime) / 1000000000;
|
obs_data_set_int(data, "total-stream-time", (getStreamingTime() / 1000000000ULL));
|
||||||
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-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_int(data, "total-stream-frames", obs_output_get_total_frames(streamOutput));
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_data_set_bool(data, "recording", recordingActive);
|
obs_data_set_bool(data, "recording", recordingActive);
|
||||||
|
obs_data_set_bool(data, "recording-paused", recordingPaused);
|
||||||
if (recordingActive) {
|
if (recordingActive) {
|
||||||
uint64_t totalRecordTime = (os_gettime_ns() - _recStarttime) / 1000000000;
|
obs_data_set_int(data, "total-record-time", (getRecordingTime() / 1000000000ULL));
|
||||||
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-bytes", (uint64_t)obs_output_get_total_bytes(recordOutput));
|
||||||
obs_data_set_int(data, "total-record-frames", obs_output_get_total_frames(recordOutput));
|
obs_data_set_int(data, "total-record-frames", obs_output_get_total_frames(recordOutput));
|
||||||
}
|
}
|
||||||
@ -804,7 +896,10 @@ void WSEvents::TransitionDurationChanged(int ms) {
|
|||||||
* A transition (other than "cut") has begun.
|
* A transition (other than "cut") has begun.
|
||||||
*
|
*
|
||||||
* @return {String} `name` Transition name.
|
* @return {String} `name` Transition name.
|
||||||
|
* @return {String} `type` Transition type.
|
||||||
* @return {int} `duration` Transition duration (in milliseconds).
|
* @return {int} `duration` Transition duration (in milliseconds).
|
||||||
|
* Will be -1 for any transition with a fixed duration,
|
||||||
|
* such as a Stinger, due to limitations of the OBS API.
|
||||||
* @return {String} `from-scene` Source scene of the transition
|
* @return {String} `from-scene` Source scene of the transition
|
||||||
* @return {String} `to-scene` Destination scene of the transition
|
* @return {String} `to-scene` Destination scene of the transition
|
||||||
*
|
*
|
||||||
@ -817,40 +912,66 @@ void WSEvents::OnTransitionBegin(void* param, calldata_t* data) {
|
|||||||
auto instance = reinterpret_cast<WSEvents*>(param);
|
auto instance = reinterpret_cast<WSEvents*>(param);
|
||||||
|
|
||||||
OBSSource transition = calldata_get_pointer<obs_source_t>(data, "source");
|
OBSSource transition = calldata_get_pointer<obs_source_t>(data, "source");
|
||||||
if (!transition) return;
|
if (!transition) {
|
||||||
|
return;
|
||||||
// Detect if transition is the global transition or a transition override.
|
|
||||||
// Fetching the duration is different depending on the case.
|
|
||||||
OBSSourceAutoRelease sourceScene = obs_transition_get_source(transition, OBS_TRANSITION_SOURCE_A);
|
|
||||||
OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition);
|
|
||||||
OBSDataAutoRelease destinationSettings = obs_source_get_private_settings(destinationScene);
|
|
||||||
int duration = -1;
|
|
||||||
if (obs_data_has_default_value(destinationSettings, "transition_duration") ||
|
|
||||||
obs_data_has_user_value(destinationSettings, "transition_duration"))
|
|
||||||
{
|
|
||||||
duration = obs_data_get_int(destinationSettings, "transition_duration");
|
|
||||||
} else {
|
|
||||||
duration = Utils::GetTransitionDuration();
|
|
||||||
}
|
|
||||||
|
|
||||||
OBSDataAutoRelease fields = obs_data_create();
|
|
||||||
obs_data_set_string(fields, "name", obs_source_get_name(transition));
|
|
||||||
if (duration >= 0) {
|
|
||||||
obs_data_set_int(fields, "duration", duration);
|
|
||||||
} else {
|
|
||||||
blog(LOG_WARNING, "OnTransitionBegin: duration is negative !");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceScene) {
|
|
||||||
obs_data_set_string(fields, "from-scene", obs_source_get_name(sourceScene));
|
|
||||||
}
|
|
||||||
if (destinationScene) {
|
|
||||||
obs_data_set_string(fields, "to-scene", obs_source_get_name(destinationScene));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OBSDataAutoRelease fields = Utils::GetTransitionData(transition);
|
||||||
instance->broadcastUpdate("TransitionBegin", fields);
|
instance->broadcastUpdate("TransitionBegin", fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A transition (other than "cut") has ended.
|
||||||
|
* Please note that the `from-scene` field is not available in TransitionEnd.
|
||||||
|
*
|
||||||
|
* @return {String} `name` Transition name.
|
||||||
|
* @return {String} `type` Transition type.
|
||||||
|
* @return {int} `duration` Transition duration (in milliseconds).
|
||||||
|
* @return {String} `to-scene` Destination scene of the transition
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name TransitionEnd
|
||||||
|
* @category transitions
|
||||||
|
* @since 4.8.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnTransitionEnd(void* param, calldata_t* data) {
|
||||||
|
auto instance = reinterpret_cast<WSEvents*>(param);
|
||||||
|
|
||||||
|
OBSSource transition = calldata_get_pointer<obs_source_t>(data, "source");
|
||||||
|
if (!transition) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataAutoRelease fields = Utils::GetTransitionData(transition);
|
||||||
|
instance->broadcastUpdate("TransitionEnd", fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A stinger transition has finished playing its video.
|
||||||
|
*
|
||||||
|
* @return {String} `name` Transition name.
|
||||||
|
* @return {String} `type` Transition type.
|
||||||
|
* @return {int} `duration` Transition duration (in milliseconds).
|
||||||
|
* @return {String} `from-scene` Source scene of the transition
|
||||||
|
* @return {String} `to-scene` Destination scene of the transition
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name TransitionVideoEnd
|
||||||
|
* @category transitions
|
||||||
|
* @since 4.8.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnTransitionVideoEnd(void* param, calldata_t* data) {
|
||||||
|
auto instance = reinterpret_cast<WSEvents*>(param);
|
||||||
|
|
||||||
|
OBSSource transition = calldata_get_pointer<obs_source_t>(data, "source");
|
||||||
|
if (!transition) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataAutoRelease fields = Utils::GetTransitionData(transition);
|
||||||
|
instance->broadcastUpdate("TransitionVideoEnd", fields);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A source has been created. A source can be an input, a scene or a transition.
|
* A source has been created. A source can be an input, a scene or a transition.
|
||||||
*
|
*
|
||||||
@ -1011,9 +1132,9 @@ void WSEvents::OnSourceAudioSyncOffsetChanged(void* param, calldata_t* data) {
|
|||||||
* Audio mixer routing changed on a source.
|
* Audio mixer routing changed on a source.
|
||||||
*
|
*
|
||||||
* @return {String} `sourceName` Source name
|
* @return {String} `sourceName` Source name
|
||||||
* @return {Array<Object>} `routingStatus` Routing status of the source for each audio mixer (array of 6 values)
|
* @return {Array<Object>} `mixers` Routing status of the source for each audio mixer (array of 6 values)
|
||||||
* @return {int} `routingStatus.*.id` Mixer number
|
* @return {int} `mixers.*.id` Mixer number
|
||||||
* @return {boolean} `routingStatus.*.enabled` Routing status
|
* @return {boolean} `mixers.*.enabled` Routing status
|
||||||
* @return {String} `hexMixersValue` Raw mixer flags (little-endian, one bit per mixer) as an hexadecimal value
|
* @return {String} `hexMixersValue` Raw mixer flags (little-endian, one bit per mixer) as an hexadecimal value
|
||||||
*
|
*
|
||||||
* @api events
|
* @api events
|
||||||
@ -1056,6 +1177,7 @@ void WSEvents::OnSourceAudioMixersChanged(void* param, calldata_t* data) {
|
|||||||
*
|
*
|
||||||
* @return {String} `previousName` Previous source name
|
* @return {String} `previousName` Previous source name
|
||||||
* @return {String} `newName` New source name
|
* @return {String} `newName` New source name
|
||||||
|
* @return {String} `sourceType` Type of source (input, scene, filter, transition)
|
||||||
*
|
*
|
||||||
* @api events
|
* @api events
|
||||||
* @name SourceRenamed
|
* @name SourceRenamed
|
||||||
@ -1080,6 +1202,8 @@ void WSEvents::OnSourceRename(void* param, calldata_t* data) {
|
|||||||
OBSDataAutoRelease fields = obs_data_create();
|
OBSDataAutoRelease fields = obs_data_create();
|
||||||
obs_data_set_string(fields, "previousName", previousName);
|
obs_data_set_string(fields, "previousName", previousName);
|
||||||
obs_data_set_string(fields, "newName", newName);
|
obs_data_set_string(fields, "newName", newName);
|
||||||
|
obs_data_set_string(fields, "sourceType",
|
||||||
|
sourceTypeToString(obs_source_get_type(source))); // TODO: Split into dedicated events for source/scene. Only doing it this way for backwards compatability until 5.0
|
||||||
self->broadcastUpdate("SourceRenamed", fields);
|
self->broadcastUpdate("SourceRenamed", fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1109,6 +1233,8 @@ void WSEvents::OnSourceFilterAdded(void* param, calldata_t* data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self->connectFilterSignals(filter);
|
||||||
|
|
||||||
OBSDataAutoRelease filterSettings = obs_source_get_settings(filter);
|
OBSDataAutoRelease filterSettings = obs_source_get_settings(filter);
|
||||||
|
|
||||||
OBSDataAutoRelease fields = obs_data_create();
|
OBSDataAutoRelease fields = obs_data_create();
|
||||||
@ -1140,6 +1266,11 @@ void WSEvents::OnSourceFilterRemoved(void* param, calldata_t* data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
obs_source_t* filter = calldata_get_pointer<obs_source_t>(data, "filter");
|
obs_source_t* filter = calldata_get_pointer<obs_source_t>(data, "filter");
|
||||||
|
if (!filter) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->disconnectFilterSignals(filter);
|
||||||
|
|
||||||
OBSDataAutoRelease fields = obs_data_create();
|
OBSDataAutoRelease fields = obs_data_create();
|
||||||
obs_data_set_string(fields, "sourceName", obs_source_get_name(source));
|
obs_data_set_string(fields, "sourceName", obs_source_get_name(source));
|
||||||
@ -1148,6 +1279,36 @@ void WSEvents::OnSourceFilterRemoved(void* param, calldata_t* data) {
|
|||||||
self->broadcastUpdate("SourceFilterRemoved", fields);
|
self->broadcastUpdate("SourceFilterRemoved", fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The visibility/enabled state of a filter changed
|
||||||
|
*
|
||||||
|
* @return {String} `sourceName` Source name
|
||||||
|
* @return {String} `filterName` Filter name
|
||||||
|
* @return {Boolean} `filterEnabled` New filter state
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name SourceFilterVisibilityChanged
|
||||||
|
* @category sources
|
||||||
|
* @since 4.7.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnSourceFilterVisibilityChanged(void* param, calldata_t* data) {
|
||||||
|
auto self = reinterpret_cast<WSEvents*>(param);
|
||||||
|
|
||||||
|
OBSSource source = calldata_get_pointer<obs_source_t>(data, "source");
|
||||||
|
if (!source) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSSource parent = obs_filter_get_parent(source);
|
||||||
|
|
||||||
|
OBSDataAutoRelease fields = obs_data_create();
|
||||||
|
obs_data_set_string(fields, "sourceName", obs_source_get_name(parent));
|
||||||
|
obs_data_set_string(fields, "filterName", obs_source_get_name(source));
|
||||||
|
obs_data_set_bool(fields, "filterEnabled", obs_source_enabled(source));
|
||||||
|
|
||||||
|
self->broadcastUpdate("SourceFilterVisibilityChanged", fields);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters in a source have been reordered.
|
* Filters in a source have been reordered.
|
||||||
*
|
*
|
||||||
@ -1187,7 +1348,7 @@ void WSEvents::OnSourceFilterOrderChanged(void* param, calldata_t* data) {
|
|||||||
*
|
*
|
||||||
* @api events
|
* @api events
|
||||||
* @name SourceOrderChanged
|
* @name SourceOrderChanged
|
||||||
* @category sources
|
* @category scene items
|
||||||
* @since 4.0.0
|
* @since 4.0.0
|
||||||
*/
|
*/
|
||||||
void WSEvents::OnSceneReordered(void* param, calldata_t* data) {
|
void WSEvents::OnSceneReordered(void* param, calldata_t* data) {
|
||||||
@ -1229,7 +1390,7 @@ void WSEvents::OnSceneReordered(void* param, calldata_t* data) {
|
|||||||
*
|
*
|
||||||
* @api events
|
* @api events
|
||||||
* @name SceneItemAdded
|
* @name SceneItemAdded
|
||||||
* @category sources
|
* @category scene items
|
||||||
* @since 4.0.0
|
* @since 4.0.0
|
||||||
*/
|
*/
|
||||||
void WSEvents::OnSceneItemAdd(void* param, calldata_t* data) {
|
void WSEvents::OnSceneItemAdd(void* param, calldata_t* data) {
|
||||||
@ -1262,7 +1423,7 @@ void WSEvents::OnSceneItemAdd(void* param, calldata_t* data) {
|
|||||||
*
|
*
|
||||||
* @api events
|
* @api events
|
||||||
* @name SceneItemRemoved
|
* @name SceneItemRemoved
|
||||||
* @category sources
|
* @category scene items
|
||||||
* @since 4.0.0
|
* @since 4.0.0
|
||||||
*/
|
*/
|
||||||
void WSEvents::OnSceneItemDelete(void* param, calldata_t* data) {
|
void WSEvents::OnSceneItemDelete(void* param, calldata_t* data) {
|
||||||
@ -1296,7 +1457,7 @@ void WSEvents::OnSceneItemDelete(void* param, calldata_t* data) {
|
|||||||
*
|
*
|
||||||
* @api events
|
* @api events
|
||||||
* @name SceneItemVisibilityChanged
|
* @name SceneItemVisibilityChanged
|
||||||
* @category sources
|
* @category scene items
|
||||||
* @since 4.0.0
|
* @since 4.0.0
|
||||||
*/
|
*/
|
||||||
void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) {
|
void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) {
|
||||||
@ -1324,6 +1485,44 @@ void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) {
|
|||||||
instance->broadcastUpdate("SceneItemVisibilityChanged", fields);
|
instance->broadcastUpdate("SceneItemVisibilityChanged", fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An item's locked status has been toggled.
|
||||||
|
*
|
||||||
|
* @return {String} `scene-name` Name of the scene.
|
||||||
|
* @return {String} `item-name` Name of the item in the scene.
|
||||||
|
* @return {int} `item-id` Scene item ID
|
||||||
|
* @return {boolean} `item-locked` New locked state of the item.
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name SceneItemLockChanged
|
||||||
|
* @category scene items
|
||||||
|
* @since 4.8.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnSceneItemLockChanged(void* param, calldata_t* data) {
|
||||||
|
auto instance = reinterpret_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 locked = false;
|
||||||
|
calldata_get_bool(data, "locked", &locked);
|
||||||
|
|
||||||
|
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_int(fields, "item-id", obs_sceneitem_get_id(sceneItem));
|
||||||
|
obs_data_set_bool(fields, "item-locked", locked);
|
||||||
|
instance->broadcastUpdate("SceneItemLockChanged", fields);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An item's transform has been changed.
|
* An item's transform has been changed.
|
||||||
*
|
*
|
||||||
@ -1334,7 +1533,7 @@ void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) {
|
|||||||
*
|
*
|
||||||
* @api events
|
* @api events
|
||||||
* @name SceneItemTransformChanged
|
* @name SceneItemTransformChanged
|
||||||
* @category sources
|
* @category scene items
|
||||||
* @since 4.6.0
|
* @since 4.6.0
|
||||||
*/
|
*/
|
||||||
void WSEvents::OnSceneItemTransform(void* param, calldata_t* data) {
|
void WSEvents::OnSceneItemTransform(void* param, calldata_t* data) {
|
||||||
@ -1370,7 +1569,7 @@ void WSEvents::OnSceneItemTransform(void* param, calldata_t* data) {
|
|||||||
*
|
*
|
||||||
* @api events
|
* @api events
|
||||||
* @name SceneItemSelected
|
* @name SceneItemSelected
|
||||||
* @category sources
|
* @category scene items
|
||||||
* @since 4.6.0
|
* @since 4.6.0
|
||||||
*/
|
*/
|
||||||
void WSEvents::OnSceneItemSelected(void* param, calldata_t* data) {
|
void WSEvents::OnSceneItemSelected(void* param, calldata_t* data) {
|
||||||
@ -1405,7 +1604,7 @@ void WSEvents::OnSceneItemSelected(void* param, calldata_t* data) {
|
|||||||
*
|
*
|
||||||
* @api events
|
* @api events
|
||||||
* @name SceneItemDeselected
|
* @name SceneItemDeselected
|
||||||
* @category sources
|
* @category scene items
|
||||||
* @since 4.6.0
|
* @since 4.6.0
|
||||||
*/
|
*/
|
||||||
void WSEvents::OnSceneItemDeselected(void* param, calldata_t* data) {
|
void WSEvents::OnSceneItemDeselected(void* param, calldata_t* data) {
|
||||||
@ -1475,6 +1674,25 @@ void WSEvents::OnStudioModeSwitched(bool checked) {
|
|||||||
broadcastUpdate("StudioModeSwitched", data);
|
broadcastUpdate("StudioModeSwitched", data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom broadcast message was received
|
||||||
|
*
|
||||||
|
* @return {String} `realm` Identifier provided by the sender
|
||||||
|
* @return {Object} `data` User-defined data
|
||||||
|
*
|
||||||
|
* @api events
|
||||||
|
* @name BroadcastCustomMessage
|
||||||
|
* @category general
|
||||||
|
* @since 4.7.0
|
||||||
|
*/
|
||||||
|
void WSEvents::OnBroadcastCustomMessage(QString realm, obs_data_t* data) {
|
||||||
|
OBSDataAutoRelease broadcastData = obs_data_create();
|
||||||
|
obs_data_set_string(broadcastData, "realm", realm.toUtf8().constData());
|
||||||
|
obs_data_set_obj(broadcastData, "data", data);
|
||||||
|
|
||||||
|
broadcastUpdate("BroadcastCustomMessage", broadcastData);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} `OBSStats`
|
* @typedef {Object} `OBSStats`
|
||||||
* @property {double} `fps` Current framerate.
|
* @property {double} `fps` Current framerate.
|
||||||
@ -1500,12 +1718,12 @@ obs_data_t* WSEvents::GetStats() {
|
|||||||
double averageFrameTime = (double)obs_get_average_frame_time_ns() / 1000000.0;
|
double averageFrameTime = (double)obs_get_average_frame_time_ns() / 1000000.0;
|
||||||
|
|
||||||
config_t* currentProfile = obs_frontend_get_profile_config();
|
config_t* currentProfile = obs_frontend_get_profile_config();
|
||||||
QString outputMode = config_get_string(currentProfile, "Output", "Mode");
|
const char* outputMode = config_get_string(currentProfile, "Output", "Mode");
|
||||||
QString path = (outputMode == "Advanced") ?
|
const char* path = strcmp(outputMode, "Advanced") ?
|
||||||
config_get_string(currentProfile, "SimpleOutput", "FilePath") :
|
config_get_string(currentProfile, "SimpleOutput", "FilePath") :
|
||||||
config_get_string(currentProfile, "AdvOut", "RecFilePath");
|
config_get_string(currentProfile, "AdvOut", "RecFilePath");
|
||||||
|
|
||||||
double freeDiskSpace = (double)os_get_free_disk_space(path.toUtf8()) / (1024.0 * 1024.0);
|
double freeDiskSpace = (double)os_get_free_disk_space(path) / (1024.0 * 1024.0);
|
||||||
|
|
||||||
obs_data_set_double(stats, "fps", obs_get_active_fps());
|
obs_data_set_double(stats, "fps", obs_get_active_fps());
|
||||||
obs_data_set_int(stats, "render-total-frames", obs_get_total_frames());
|
obs_data_set_int(stats, "render-total-frames", obs_get_total_frames());
|
||||||
|
@ -40,12 +40,22 @@ public:
|
|||||||
void connectSourceSignals(obs_source_t* source);
|
void connectSourceSignals(obs_source_t* source);
|
||||||
void disconnectSourceSignals(obs_source_t* source);
|
void disconnectSourceSignals(obs_source_t* source);
|
||||||
|
|
||||||
uint64_t GetStreamingTime();
|
void connectFilterSignals(obs_source_t* filter);
|
||||||
const char* GetStreamingTimecode();
|
void disconnectFilterSignals(obs_source_t* filter);
|
||||||
uint64_t GetRecordingTime();
|
|
||||||
const char* GetRecordingTimecode();
|
void hookTransitionPlaybackEvents();
|
||||||
|
void unhookTransitionPlaybackEvents();
|
||||||
|
|
||||||
|
uint64_t getStreamingTime();
|
||||||
|
uint64_t getRecordingTime();
|
||||||
|
|
||||||
|
QString getStreamingTimecode();
|
||||||
|
QString getRecordingTimecode();
|
||||||
|
|
||||||
obs_data_t* GetStats();
|
obs_data_t* GetStats();
|
||||||
|
|
||||||
|
void OnBroadcastCustomMessage(QString realm, obs_data_t* data);
|
||||||
|
|
||||||
bool HeartbeatIsActive;
|
bool HeartbeatIsActive;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
@ -62,7 +72,6 @@ private:
|
|||||||
bool pulse;
|
bool pulse;
|
||||||
|
|
||||||
uint64_t _streamStarttime;
|
uint64_t _streamStarttime;
|
||||||
uint64_t _recStarttime;
|
|
||||||
|
|
||||||
uint64_t _lastBytesSent;
|
uint64_t _lastBytesSent;
|
||||||
uint64_t _lastBytesSentTime;
|
uint64_t _lastBytesSentTime;
|
||||||
@ -90,6 +99,8 @@ private:
|
|||||||
void OnRecordingStarted();
|
void OnRecordingStarted();
|
||||||
void OnRecordingStopping();
|
void OnRecordingStopping();
|
||||||
void OnRecordingStopped();
|
void OnRecordingStopped();
|
||||||
|
void OnRecordingPaused();
|
||||||
|
void OnRecordingResumed();
|
||||||
|
|
||||||
void OnReplayStarting();
|
void OnReplayStarting();
|
||||||
void OnReplayStarted();
|
void OnReplayStarted();
|
||||||
@ -105,6 +116,8 @@ private:
|
|||||||
enum obs_frontend_event event, void* privateData);
|
enum obs_frontend_event event, void* privateData);
|
||||||
|
|
||||||
static void OnTransitionBegin(void* param, calldata_t* data);
|
static void OnTransitionBegin(void* param, calldata_t* data);
|
||||||
|
static void OnTransitionEnd(void* param, calldata_t* data);
|
||||||
|
static void OnTransitionVideoEnd(void* param, calldata_t* data);
|
||||||
|
|
||||||
static void OnSourceCreate(void* param, calldata_t* data);
|
static void OnSourceCreate(void* param, calldata_t* data);
|
||||||
static void OnSourceDestroy(void* param, calldata_t* data);
|
static void OnSourceDestroy(void* param, calldata_t* data);
|
||||||
@ -118,12 +131,14 @@ private:
|
|||||||
|
|
||||||
static void OnSourceFilterAdded(void* param, calldata_t* data);
|
static void OnSourceFilterAdded(void* param, calldata_t* data);
|
||||||
static void OnSourceFilterRemoved(void* param, calldata_t* data);
|
static void OnSourceFilterRemoved(void* param, calldata_t* data);
|
||||||
|
static void OnSourceFilterVisibilityChanged(void* param, calldata_t* data);
|
||||||
static void OnSourceFilterOrderChanged(void* param, calldata_t* data);
|
static void OnSourceFilterOrderChanged(void* param, calldata_t* data);
|
||||||
|
|
||||||
static void OnSceneReordered(void* param, calldata_t* data);
|
static void OnSceneReordered(void* param, calldata_t* data);
|
||||||
static void OnSceneItemAdd(void* param, calldata_t* data);
|
static void OnSceneItemAdd(void* param, calldata_t* data);
|
||||||
static void OnSceneItemDelete(void* param, calldata_t* data);
|
static void OnSceneItemDelete(void* param, calldata_t* data);
|
||||||
static void OnSceneItemVisibilityChanged(void* param, calldata_t* data);
|
static void OnSceneItemVisibilityChanged(void* param, calldata_t* data);
|
||||||
|
static void OnSceneItemLockChanged(void* param, calldata_t* data);
|
||||||
static void OnSceneItemTransform(void* param, calldata_t* data);
|
static void OnSceneItemTransform(void* param, calldata_t* data);
|
||||||
static void OnSceneItemSelected(void* param, calldata_t* data);
|
static void OnSceneItemSelected(void* param, calldata_t* data);
|
||||||
static void OnSceneItemDeselected(void* param, calldata_t* data);
|
static void OnSceneItemDeselected(void* param, calldata_t* data);
|
||||||
|
@ -17,6 +17,8 @@
|
|||||||
* with this program. If not, see <https://www.gnu.org/licenses/>
|
* with this program. If not, see <https://www.gnu.org/licenses/>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
#include <obs-data.h>
|
#include <obs-data.h>
|
||||||
|
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
@ -24,204 +26,156 @@
|
|||||||
|
|
||||||
#include "WSRequestHandler.h"
|
#include "WSRequestHandler.h"
|
||||||
|
|
||||||
QHash<QString, HandlerResponse(*)(WSRequestHandler*)> WSRequestHandler::messageMap {
|
using namespace std::placeholders;
|
||||||
{ "GetVersion", WSRequestHandler::HandleGetVersion },
|
|
||||||
{ "GetAuthRequired", WSRequestHandler::HandleGetAuthRequired },
|
|
||||||
{ "Authenticate", WSRequestHandler::HandleAuthenticate },
|
|
||||||
|
|
||||||
{ "GetStats", WSRequestHandler::HandleGetStats },
|
const QHash<QString, RpcMethodHandler> WSRequestHandler::messageMap {
|
||||||
{ "SetHeartbeat", WSRequestHandler::HandleSetHeartbeat },
|
{ "GetVersion", &WSRequestHandler::GetVersion },
|
||||||
{ "GetVideoInfo", WSRequestHandler::HandleGetVideoInfo },
|
{ "GetAuthRequired", &WSRequestHandler::GetAuthRequired },
|
||||||
|
{ "Authenticate", &WSRequestHandler::Authenticate },
|
||||||
|
|
||||||
{ "SetFilenameFormatting", WSRequestHandler::HandleSetFilenameFormatting },
|
{ "GetStats", &WSRequestHandler::GetStats },
|
||||||
{ "GetFilenameFormatting", WSRequestHandler::HandleGetFilenameFormatting },
|
{ "SetHeartbeat", &WSRequestHandler::SetHeartbeat },
|
||||||
|
{ "GetVideoInfo", &WSRequestHandler::GetVideoInfo },
|
||||||
|
{ "OpenProjector", &WSRequestHandler::OpenProjector },
|
||||||
|
|
||||||
{ "SetCurrentScene", WSRequestHandler::HandleSetCurrentScene },
|
{ "SetFilenameFormatting", &WSRequestHandler::SetFilenameFormatting },
|
||||||
{ "GetCurrentScene", WSRequestHandler::HandleGetCurrentScene },
|
{ "GetFilenameFormatting", &WSRequestHandler::GetFilenameFormatting },
|
||||||
{ "GetSceneList", WSRequestHandler::HandleGetSceneList },
|
|
||||||
|
|
||||||
{ "SetSourceRender", WSRequestHandler::HandleSetSceneItemRender }, // Retrocompat
|
{ "BroadcastCustomMessage", &WSRequestHandler::BroadcastCustomMessage },
|
||||||
{ "SetSceneItemRender", WSRequestHandler::HandleSetSceneItemRender },
|
|
||||||
{ "SetSceneItemPosition", WSRequestHandler::HandleSetSceneItemPosition },
|
|
||||||
{ "SetSceneItemTransform", WSRequestHandler::HandleSetSceneItemTransform },
|
|
||||||
{ "SetSceneItemCrop", WSRequestHandler::HandleSetSceneItemCrop },
|
|
||||||
{ "GetSceneItemProperties", WSRequestHandler::HandleGetSceneItemProperties },
|
|
||||||
{ "SetSceneItemProperties", WSRequestHandler::HandleSetSceneItemProperties },
|
|
||||||
{ "ResetSceneItem", WSRequestHandler::HandleResetSceneItem },
|
|
||||||
{ "DeleteSceneItem", WSRequestHandler::HandleDeleteSceneItem },
|
|
||||||
{ "DuplicateSceneItem", WSRequestHandler::HandleDuplicateSceneItem },
|
|
||||||
{ "ReorderSceneItems", WSRequestHandler::HandleReorderSceneItems },
|
|
||||||
|
|
||||||
{ "GetStreamingStatus", WSRequestHandler::HandleGetStreamingStatus },
|
{ "SetCurrentScene", &WSRequestHandler::SetCurrentScene },
|
||||||
{ "StartStopStreaming", WSRequestHandler::HandleStartStopStreaming },
|
{ "GetCurrentScene", &WSRequestHandler::GetCurrentScene },
|
||||||
{ "StartStopRecording", WSRequestHandler::HandleStartStopRecording },
|
{ "GetSceneList", &WSRequestHandler::GetSceneList },
|
||||||
{ "StartStreaming", WSRequestHandler::HandleStartStreaming },
|
{ "SetSceneTransitionOverride", &WSRequestHandler::SetSceneTransitionOverride },
|
||||||
{ "StopStreaming", WSRequestHandler::HandleStopStreaming },
|
{ "RemoveSceneTransitionOverride", &WSRequestHandler::RemoveSceneTransitionOverride },
|
||||||
{ "StartRecording", WSRequestHandler::HandleStartRecording },
|
{ "GetSceneTransitionOverride", &WSRequestHandler::GetSceneTransitionOverride },
|
||||||
{ "StopRecording", WSRequestHandler::HandleStopRecording },
|
|
||||||
|
|
||||||
{ "StartStopReplayBuffer", WSRequestHandler::HandleStartStopReplayBuffer },
|
{ "SetSourceRender", &WSRequestHandler::SetSceneItemRender }, // Retrocompat
|
||||||
{ "StartReplayBuffer", WSRequestHandler::HandleStartReplayBuffer },
|
{ "SetSceneItemRender", &WSRequestHandler::SetSceneItemRender },
|
||||||
{ "StopReplayBuffer", WSRequestHandler::HandleStopReplayBuffer },
|
{ "SetSceneItemPosition", &WSRequestHandler::SetSceneItemPosition },
|
||||||
{ "SaveReplayBuffer", WSRequestHandler::HandleSaveReplayBuffer },
|
{ "SetSceneItemTransform", &WSRequestHandler::SetSceneItemTransform },
|
||||||
|
{ "SetSceneItemCrop", &WSRequestHandler::SetSceneItemCrop },
|
||||||
|
{ "GetSceneItemProperties", &WSRequestHandler::GetSceneItemProperties },
|
||||||
|
{ "SetSceneItemProperties", &WSRequestHandler::SetSceneItemProperties },
|
||||||
|
{ "ResetSceneItem", &WSRequestHandler::ResetSceneItem },
|
||||||
|
{ "DeleteSceneItem", &WSRequestHandler::DeleteSceneItem },
|
||||||
|
{ "DuplicateSceneItem", &WSRequestHandler::DuplicateSceneItem },
|
||||||
|
{ "ReorderSceneItems", &WSRequestHandler::ReorderSceneItems },
|
||||||
|
|
||||||
{ "SetRecordingFolder", WSRequestHandler::HandleSetRecordingFolder },
|
{ "GetStreamingStatus", &WSRequestHandler::GetStreamingStatus },
|
||||||
{ "GetRecordingFolder", WSRequestHandler::HandleGetRecordingFolder },
|
{ "StartStopStreaming", &WSRequestHandler::StartStopStreaming },
|
||||||
|
{ "StartStopRecording", &WSRequestHandler::StartStopRecording },
|
||||||
|
|
||||||
{ "GetTransitionList", WSRequestHandler::HandleGetTransitionList },
|
{ "StartStreaming", &WSRequestHandler::StartStreaming },
|
||||||
{ "GetCurrentTransition", WSRequestHandler::HandleGetCurrentTransition },
|
{ "StopStreaming", &WSRequestHandler::StopStreaming },
|
||||||
{ "SetCurrentTransition", WSRequestHandler::HandleSetCurrentTransition },
|
|
||||||
{ "SetTransitionDuration", WSRequestHandler::HandleSetTransitionDuration },
|
|
||||||
{ "GetTransitionDuration", WSRequestHandler::HandleGetTransitionDuration },
|
|
||||||
|
|
||||||
{ "SetVolume", WSRequestHandler::HandleSetVolume },
|
{ "StartRecording", &WSRequestHandler::StartRecording },
|
||||||
{ "GetVolume", WSRequestHandler::HandleGetVolume },
|
{ "StopRecording", &WSRequestHandler::StopRecording },
|
||||||
{ "ToggleMute", WSRequestHandler::HandleToggleMute },
|
{ "PauseRecording", &WSRequestHandler::PauseRecording },
|
||||||
{ "SetMute", WSRequestHandler::HandleSetMute },
|
{ "ResumeRecording", &WSRequestHandler::ResumeRecording },
|
||||||
{ "GetMute", WSRequestHandler::HandleGetMute },
|
|
||||||
{ "SetSyncOffset", WSRequestHandler::HandleSetSyncOffset },
|
|
||||||
{ "GetSyncOffset", WSRequestHandler::HandleGetSyncOffset },
|
|
||||||
{ "GetSpecialSources", WSRequestHandler::HandleGetSpecialSources },
|
|
||||||
{ "GetSourcesList", WSRequestHandler::HandleGetSourcesList },
|
|
||||||
{ "GetSourceTypesList", WSRequestHandler::HandleGetSourceTypesList },
|
|
||||||
{ "GetSourceSettings", WSRequestHandler::HandleGetSourceSettings },
|
|
||||||
{ "SetSourceSettings", WSRequestHandler::HandleSetSourceSettings },
|
|
||||||
{ "TakeSourceScreenshot", WSRequestHandler::HandleTakeSourceScreenshot },
|
|
||||||
|
|
||||||
{ "GetSourceFilters", WSRequestHandler::HandleGetSourceFilters },
|
{ "StartStopReplayBuffer", &WSRequestHandler::StartStopReplayBuffer },
|
||||||
{ "AddFilterToSource", WSRequestHandler::HandleAddFilterToSource },
|
{ "StartReplayBuffer", &WSRequestHandler::StartReplayBuffer },
|
||||||
{ "RemoveFilterFromSource", WSRequestHandler::HandleRemoveFilterFromSource },
|
{ "StopReplayBuffer", &WSRequestHandler::StopReplayBuffer },
|
||||||
{ "ReorderSourceFilter", WSRequestHandler::HandleReorderSourceFilter },
|
{ "SaveReplayBuffer", &WSRequestHandler::SaveReplayBuffer },
|
||||||
{ "MoveSourceFilter", WSRequestHandler::HandleMoveSourceFilter },
|
|
||||||
{ "SetSourceFilterSettings", WSRequestHandler::HandleSetSourceFilterSettings },
|
|
||||||
|
|
||||||
{ "SetCurrentSceneCollection", WSRequestHandler::HandleSetCurrentSceneCollection },
|
{ "SetRecordingFolder", &WSRequestHandler::SetRecordingFolder },
|
||||||
{ "GetCurrentSceneCollection", WSRequestHandler::HandleGetCurrentSceneCollection },
|
{ "GetRecordingFolder", &WSRequestHandler::GetRecordingFolder },
|
||||||
{ "ListSceneCollections", WSRequestHandler::HandleListSceneCollections },
|
|
||||||
|
|
||||||
{ "SetCurrentProfile", WSRequestHandler::HandleSetCurrentProfile },
|
{ "GetTransitionList", &WSRequestHandler::GetTransitionList },
|
||||||
{ "GetCurrentProfile", WSRequestHandler::HandleGetCurrentProfile },
|
{ "GetCurrentTransition", &WSRequestHandler::GetCurrentTransition },
|
||||||
{ "ListProfiles", WSRequestHandler::HandleListProfiles },
|
{ "SetCurrentTransition", &WSRequestHandler::SetCurrentTransition },
|
||||||
|
{ "SetTransitionDuration", &WSRequestHandler::SetTransitionDuration },
|
||||||
|
{ "GetTransitionDuration", &WSRequestHandler::GetTransitionDuration },
|
||||||
|
{ "GetTransitionPosition", &WSRequestHandler::GetTransitionPosition },
|
||||||
|
|
||||||
{ "SetStreamSettings", WSRequestHandler::HandleSetStreamSettings },
|
{ "SetVolume", &WSRequestHandler::SetVolume },
|
||||||
{ "GetStreamSettings", WSRequestHandler::HandleGetStreamSettings },
|
{ "GetVolume", &WSRequestHandler::GetVolume },
|
||||||
{ "SaveStreamSettings", WSRequestHandler::HandleSaveStreamSettings },
|
{ "ToggleMute", &WSRequestHandler::ToggleMute },
|
||||||
|
{ "SetMute", &WSRequestHandler::SetMute },
|
||||||
|
{ "GetMute", &WSRequestHandler::GetMute },
|
||||||
|
{ "SetSourceName", &WSRequestHandler::SetSourceName },
|
||||||
|
{ "SetSyncOffset", &WSRequestHandler::SetSyncOffset },
|
||||||
|
{ "GetSyncOffset", &WSRequestHandler::GetSyncOffset },
|
||||||
|
{ "GetSpecialSources", &WSRequestHandler::GetSpecialSources },
|
||||||
|
{ "GetSourcesList", &WSRequestHandler::GetSourcesList },
|
||||||
|
{ "GetSourceTypesList", &WSRequestHandler::GetSourceTypesList },
|
||||||
|
{ "GetSourceSettings", &WSRequestHandler::GetSourceSettings },
|
||||||
|
{ "SetSourceSettings", &WSRequestHandler::SetSourceSettings },
|
||||||
|
{ "GetAudioMonitorType", &WSRequestHandler::GetAudioMonitorType },
|
||||||
|
{ "SetAudioMonitorType", &WSRequestHandler::SetAudioMonitorType },
|
||||||
|
{ "TakeSourceScreenshot", &WSRequestHandler::TakeSourceScreenshot },
|
||||||
|
|
||||||
|
{ "GetSourceFilters", &WSRequestHandler::GetSourceFilters },
|
||||||
|
{ "GetSourceFilterInfo", &WSRequestHandler::GetSourceFilterInfo },
|
||||||
|
{ "AddFilterToSource", &WSRequestHandler::AddFilterToSource },
|
||||||
|
{ "RemoveFilterFromSource", &WSRequestHandler::RemoveFilterFromSource },
|
||||||
|
{ "ReorderSourceFilter", &WSRequestHandler::ReorderSourceFilter },
|
||||||
|
{ "MoveSourceFilter", &WSRequestHandler::MoveSourceFilter },
|
||||||
|
{ "SetSourceFilterSettings", &WSRequestHandler::SetSourceFilterSettings },
|
||||||
|
{ "SetSourceFilterVisibility", &WSRequestHandler::SetSourceFilterVisibility },
|
||||||
|
|
||||||
|
{ "SetCurrentSceneCollection", &WSRequestHandler::SetCurrentSceneCollection },
|
||||||
|
{ "GetCurrentSceneCollection", &WSRequestHandler::GetCurrentSceneCollection },
|
||||||
|
{ "ListSceneCollections", &WSRequestHandler::ListSceneCollections },
|
||||||
|
|
||||||
|
{ "SetCurrentProfile", &WSRequestHandler::SetCurrentProfile },
|
||||||
|
{ "GetCurrentProfile", &WSRequestHandler::GetCurrentProfile },
|
||||||
|
{ "ListProfiles", &WSRequestHandler::ListProfiles },
|
||||||
|
|
||||||
|
{ "SetStreamSettings", &WSRequestHandler::SetStreamSettings },
|
||||||
|
{ "GetStreamSettings", &WSRequestHandler::GetStreamSettings },
|
||||||
|
{ "SaveStreamSettings", &WSRequestHandler::SaveStreamSettings },
|
||||||
#if BUILD_CAPTIONS
|
#if BUILD_CAPTIONS
|
||||||
{ "SendCaptions", WSRequestHandler::HandleSendCaptions },
|
{ "SendCaptions", &WSRequestHandler::SendCaptions },
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
{ "GetStudioModeStatus", WSRequestHandler::HandleGetStudioModeStatus },
|
{ "GetStudioModeStatus", &WSRequestHandler::GetStudioModeStatus },
|
||||||
{ "GetPreviewScene", WSRequestHandler::HandleGetPreviewScene },
|
{ "GetPreviewScene", &WSRequestHandler::GetPreviewScene },
|
||||||
{ "SetPreviewScene", WSRequestHandler::HandleSetPreviewScene },
|
{ "SetPreviewScene", &WSRequestHandler::SetPreviewScene },
|
||||||
{ "TransitionToProgram", WSRequestHandler::HandleTransitionToProgram },
|
{ "TransitionToProgram", &WSRequestHandler::TransitionToProgram },
|
||||||
{ "EnableStudioMode", WSRequestHandler::HandleEnableStudioMode },
|
{ "EnableStudioMode", &WSRequestHandler::EnableStudioMode },
|
||||||
{ "DisableStudioMode", WSRequestHandler::HandleDisableStudioMode },
|
{ "DisableStudioMode", &WSRequestHandler::DisableStudioMode },
|
||||||
{ "ToggleStudioMode", WSRequestHandler::HandleToggleStudioMode },
|
{ "ToggleStudioMode", &WSRequestHandler::ToggleStudioMode },
|
||||||
|
|
||||||
{ "SetTextGDIPlusProperties", WSRequestHandler::HandleSetTextGDIPlusProperties },
|
{ "SetTextGDIPlusProperties", &WSRequestHandler::SetTextGDIPlusProperties },
|
||||||
{ "GetTextGDIPlusProperties", WSRequestHandler::HandleGetTextGDIPlusProperties },
|
{ "GetTextGDIPlusProperties", &WSRequestHandler::GetTextGDIPlusProperties },
|
||||||
|
|
||||||
{ "SetTextFreetype2Properties", WSRequestHandler::HandleSetTextFreetype2Properties },
|
{ "SetTextFreetype2Properties", &WSRequestHandler::SetTextFreetype2Properties },
|
||||||
{ "GetTextFreetype2Properties", WSRequestHandler::HandleGetTextFreetype2Properties },
|
{ "GetTextFreetype2Properties", &WSRequestHandler::GetTextFreetype2Properties },
|
||||||
|
|
||||||
{ "GetBrowserSourceProperties", WSRequestHandler::HandleGetBrowserSourceProperties },
|
{ "GetBrowserSourceProperties", &WSRequestHandler::GetBrowserSourceProperties },
|
||||||
{ "SetBrowserSourceProperties", WSRequestHandler::HandleSetBrowserSourceProperties }
|
{ "SetBrowserSourceProperties", &WSRequestHandler::SetBrowserSourceProperties },
|
||||||
|
|
||||||
|
{ "ListOutputs", &WSRequestHandler::ListOutputs },
|
||||||
|
{ "GetOutputInfo", &WSRequestHandler::GetOutputInfo },
|
||||||
|
{ "StartOutput", &WSRequestHandler::StartOutput },
|
||||||
|
{ "StopOutput", &WSRequestHandler::StopOutput }
|
||||||
};
|
};
|
||||||
|
|
||||||
QSet<QString> WSRequestHandler::authNotRequired {
|
const QSet<QString> WSRequestHandler::authNotRequired {
|
||||||
"GetVersion",
|
"GetVersion",
|
||||||
"GetAuthRequired",
|
"GetAuthRequired",
|
||||||
"Authenticate"
|
"Authenticate"
|
||||||
};
|
};
|
||||||
|
|
||||||
WSRequestHandler::WSRequestHandler(ConnectionProperties& connProperties) :
|
WSRequestHandler::WSRequestHandler(ConnectionProperties& connProperties) :
|
||||||
_messageId(0),
|
|
||||||
_requestType(""),
|
|
||||||
data(nullptr),
|
|
||||||
_connProperties(connProperties)
|
_connProperties(connProperties)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string WSRequestHandler::processIncomingMessage(std::string& textMessage) {
|
RpcResponse WSRequestHandler::processRequest(const RpcRequest& request){
|
||||||
if (GetConfig()->DebugEnabled) {
|
|
||||||
blog(LOG_INFO, "Request >> '%s'", textMessage.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
OBSDataAutoRelease responseData = processRequest(textMessage);
|
|
||||||
std::string response = obs_data_get_json(responseData);
|
|
||||||
|
|
||||||
if (GetConfig()->DebugEnabled) {
|
|
||||||
blog(LOG_INFO, "Response << '%s'", response.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
HandlerResponse WSRequestHandler::processRequest(std::string& textMessage){
|
|
||||||
std::string msgContainer(textMessage);
|
|
||||||
const char* msg = msgContainer.c_str();
|
|
||||||
|
|
||||||
data = obs_data_create_from_json(msg);
|
|
||||||
if (!data) {
|
|
||||||
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg);
|
|
||||||
return SendErrorResponse("invalid JSON payload");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasField("request-type") || !hasField("message-id")) {
|
|
||||||
return SendErrorResponse("missing request parameters");
|
|
||||||
}
|
|
||||||
|
|
||||||
_requestType = obs_data_get_string(data, "request-type");
|
|
||||||
_messageId = obs_data_get_string(data, "message-id");
|
|
||||||
|
|
||||||
if (GetConfig()->AuthRequired
|
if (GetConfig()->AuthRequired
|
||||||
&& (!authNotRequired.contains(_requestType))
|
&& (!authNotRequired.contains(request.methodName()))
|
||||||
&& (!_connProperties.isAuthenticated()))
|
&& (!_connProperties.isAuthenticated()))
|
||||||
{
|
{
|
||||||
return SendErrorResponse("Not Authenticated");
|
return RpcResponse::fail(request, "Not Authenticated");
|
||||||
}
|
}
|
||||||
|
|
||||||
HandlerResponse (*handlerFunc)(WSRequestHandler*) = (messageMap[_requestType]);
|
RpcMethodHandler handlerFunc = messageMap[request.methodName()];
|
||||||
if (!handlerFunc) {
|
if (!handlerFunc) {
|
||||||
return SendErrorResponse("invalid request type");
|
return RpcResponse::fail(request, "invalid request type");
|
||||||
}
|
}
|
||||||
|
|
||||||
return handlerFunc(this);
|
return std::bind(handlerFunc, this, _1)(request);
|
||||||
}
|
|
||||||
|
|
||||||
WSRequestHandler::~WSRequestHandler() {
|
|
||||||
}
|
|
||||||
|
|
||||||
HandlerResponse WSRequestHandler::SendOKResponse(obs_data_t* additionalFields) {
|
|
||||||
return SendResponse("ok", additionalFields);
|
|
||||||
}
|
|
||||||
|
|
||||||
HandlerResponse WSRequestHandler::SendErrorResponse(const char* errorMessage) {
|
|
||||||
OBSDataAutoRelease fields = obs_data_create();
|
|
||||||
obs_data_set_string(fields, "error", errorMessage);
|
|
||||||
|
|
||||||
return SendResponse("error", fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
HandlerResponse WSRequestHandler::SendErrorResponse(obs_data_t* additionalFields) {
|
|
||||||
return SendResponse("error", additionalFields);
|
|
||||||
}
|
|
||||||
|
|
||||||
HandlerResponse WSRequestHandler::SendResponse(const char* status, obs_data_t* fields) {
|
|
||||||
obs_data_t* response = obs_data_create();
|
|
||||||
obs_data_set_string(response, "message-id", _messageId);
|
|
||||||
obs_data_set_string(response, "status", status);
|
|
||||||
|
|
||||||
if (fields) {
|
|
||||||
obs_data_apply(response, fields);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WSRequestHandler::hasField(QString name) {
|
|
||||||
if (!data || name.isEmpty() || name.isNull())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return obs_data_has_user_value(data, name.toUtf8());
|
|
||||||
}
|
}
|
||||||
|
@ -19,145 +19,153 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QtCore/QString>
|
||||||
#include <QtCore/QHash>
|
#include <QtCore/QHash>
|
||||||
#include <QtCore/QSet>
|
#include <QtCore/QSet>
|
||||||
#include <QtCore/QVariantHash>
|
|
||||||
#include <QtCore/QString>
|
|
||||||
#include <QtCore/QSharedPointer>
|
|
||||||
|
|
||||||
#include <obs.hpp>
|
#include <obs.hpp>
|
||||||
#include <obs-frontend-api.h>
|
#include <obs-frontend-api.h>
|
||||||
|
|
||||||
#include "ConnectionProperties.h"
|
#include "ConnectionProperties.h"
|
||||||
|
|
||||||
|
#include "rpc/RpcRequest.h"
|
||||||
|
#include "rpc/RpcResponse.h"
|
||||||
|
|
||||||
#include "obs-websocket.h"
|
#include "obs-websocket.h"
|
||||||
|
|
||||||
typedef obs_data_t* HandlerResponse;
|
class WSRequestHandler;
|
||||||
|
typedef RpcResponse(WSRequestHandler::*RpcMethodHandler)(const RpcRequest&);
|
||||||
class WSRequestHandler : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
|
class WSRequestHandler {
|
||||||
public:
|
public:
|
||||||
explicit WSRequestHandler(ConnectionProperties& connProperties);
|
explicit WSRequestHandler(ConnectionProperties& connProperties);
|
||||||
~WSRequestHandler();
|
RpcResponse processRequest(const RpcRequest& textMessage);
|
||||||
std::string processIncomingMessage(std::string& textMessage);
|
|
||||||
bool hasField(QString name);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const char* _messageId;
|
|
||||||
const char* _requestType;
|
|
||||||
ConnectionProperties& _connProperties;
|
ConnectionProperties& _connProperties;
|
||||||
OBSDataAutoRelease data;
|
|
||||||
|
|
||||||
HandlerResponse processRequest(std::string& textMessage);
|
static const QHash<QString, RpcMethodHandler> messageMap;
|
||||||
|
static const QSet<QString> authNotRequired;
|
||||||
|
|
||||||
HandlerResponse SendOKResponse(obs_data_t* additionalFields = nullptr);
|
RpcResponse GetVersion(const RpcRequest&);
|
||||||
HandlerResponse SendErrorResponse(const char* errorMessage);
|
RpcResponse GetAuthRequired(const RpcRequest&);
|
||||||
HandlerResponse SendErrorResponse(obs_data_t* additionalFields = nullptr);
|
RpcResponse Authenticate(const RpcRequest&);
|
||||||
HandlerResponse SendResponse(const char* status, obs_data_t* additionalFields = nullptr);
|
|
||||||
|
|
||||||
static QHash<QString, HandlerResponse(*)(WSRequestHandler*)> messageMap;
|
RpcResponse GetStats(const RpcRequest&);
|
||||||
static QSet<QString> authNotRequired;
|
RpcResponse SetHeartbeat(const RpcRequest&);
|
||||||
|
RpcResponse GetVideoInfo(const RpcRequest&);
|
||||||
|
RpcResponse OpenProjector(const RpcRequest&);
|
||||||
|
|
||||||
static HandlerResponse HandleGetVersion(WSRequestHandler* req);
|
RpcResponse SetFilenameFormatting(const RpcRequest&);
|
||||||
static HandlerResponse HandleGetAuthRequired(WSRequestHandler* req);
|
RpcResponse GetFilenameFormatting(const RpcRequest&);
|
||||||
static HandlerResponse HandleAuthenticate(WSRequestHandler* req);
|
|
||||||
|
|
||||||
static HandlerResponse HandleGetStats(WSRequestHandler* req);
|
RpcResponse BroadcastCustomMessage(const RpcRequest&);
|
||||||
static HandlerResponse HandleSetHeartbeat(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleGetVideoInfo(WSRequestHandler* req);
|
|
||||||
|
|
||||||
static HandlerResponse HandleSetFilenameFormatting(WSRequestHandler* req);
|
RpcResponse SetCurrentScene(const RpcRequest&);
|
||||||
static HandlerResponse HandleGetFilenameFormatting(WSRequestHandler* req);
|
RpcResponse GetCurrentScene(const RpcRequest&);
|
||||||
|
RpcResponse GetSceneList(const RpcRequest&);
|
||||||
|
RpcResponse SetSceneTransitionOverride(const RpcRequest&);
|
||||||
|
RpcResponse RemoveSceneTransitionOverride(const RpcRequest&);
|
||||||
|
RpcResponse GetSceneTransitionOverride(const RpcRequest&);
|
||||||
|
|
||||||
static HandlerResponse HandleSetCurrentScene(WSRequestHandler* req);
|
RpcResponse SetSceneItemRender(const RpcRequest&);
|
||||||
static HandlerResponse HandleGetCurrentScene(WSRequestHandler* req);
|
RpcResponse SetSceneItemPosition(const RpcRequest&);
|
||||||
static HandlerResponse HandleGetSceneList(WSRequestHandler* req);
|
RpcResponse SetSceneItemTransform(const RpcRequest&);
|
||||||
|
RpcResponse SetSceneItemCrop(const RpcRequest&);
|
||||||
|
RpcResponse GetSceneItemProperties(const RpcRequest&);
|
||||||
|
RpcResponse SetSceneItemProperties(const RpcRequest&);
|
||||||
|
RpcResponse ResetSceneItem(const RpcRequest&);
|
||||||
|
RpcResponse DuplicateSceneItem(const RpcRequest&);
|
||||||
|
RpcResponse DeleteSceneItem(const RpcRequest&);
|
||||||
|
RpcResponse ReorderSceneItems(const RpcRequest&);
|
||||||
|
|
||||||
static HandlerResponse HandleSetSceneItemRender(WSRequestHandler* req);
|
RpcResponse GetStreamingStatus(const RpcRequest&);
|
||||||
static HandlerResponse HandleSetSceneItemPosition(WSRequestHandler* req);
|
RpcResponse StartStopStreaming(const RpcRequest&);
|
||||||
static HandlerResponse HandleSetSceneItemTransform(WSRequestHandler* req);
|
RpcResponse StartStopRecording(const RpcRequest&);
|
||||||
static HandlerResponse HandleSetSceneItemCrop(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleGetSceneItemProperties(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleSetSceneItemProperties(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleResetSceneItem(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleDuplicateSceneItem(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleDeleteSceneItem(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleReorderSceneItems(WSRequestHandler* req);
|
|
||||||
|
|
||||||
static HandlerResponse HandleGetStreamingStatus(WSRequestHandler* req);
|
RpcResponse StartStreaming(const RpcRequest&);
|
||||||
static HandlerResponse HandleStartStopStreaming(WSRequestHandler* req);
|
RpcResponse StopStreaming(const RpcRequest&);
|
||||||
static HandlerResponse HandleStartStopRecording(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleStartStreaming(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleStopStreaming(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleStartRecording(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleStopRecording(WSRequestHandler* req);
|
|
||||||
|
|
||||||
static HandlerResponse HandleStartStopReplayBuffer(WSRequestHandler* req);
|
RpcResponse StartRecording(const RpcRequest&);
|
||||||
static HandlerResponse HandleStartReplayBuffer(WSRequestHandler* req);
|
RpcResponse StopRecording(const RpcRequest&);
|
||||||
static HandlerResponse HandleStopReplayBuffer(WSRequestHandler* req);
|
RpcResponse PauseRecording(const RpcRequest&);
|
||||||
static HandlerResponse HandleSaveReplayBuffer(WSRequestHandler* req);
|
RpcResponse ResumeRecording(const RpcRequest&);
|
||||||
|
|
||||||
static HandlerResponse HandleSetRecordingFolder(WSRequestHandler* req);
|
RpcResponse StartStopReplayBuffer(const RpcRequest&);
|
||||||
static HandlerResponse HandleGetRecordingFolder(WSRequestHandler* req);
|
RpcResponse StartReplayBuffer(const RpcRequest&);
|
||||||
|
RpcResponse StopReplayBuffer(const RpcRequest&);
|
||||||
|
RpcResponse SaveReplayBuffer(const RpcRequest&);
|
||||||
|
|
||||||
static HandlerResponse HandleGetTransitionList(WSRequestHandler* req);
|
RpcResponse SetRecordingFolder(const RpcRequest&);
|
||||||
static HandlerResponse HandleGetCurrentTransition(WSRequestHandler* req);
|
RpcResponse GetRecordingFolder(const RpcRequest&);
|
||||||
static HandlerResponse HandleSetCurrentTransition(WSRequestHandler* req);
|
|
||||||
|
|
||||||
static HandlerResponse HandleSetVolume(WSRequestHandler* req);
|
RpcResponse GetTransitionList(const RpcRequest&);
|
||||||
static HandlerResponse HandleGetVolume(WSRequestHandler* req);
|
RpcResponse GetCurrentTransition(const RpcRequest&);
|
||||||
static HandlerResponse HandleToggleMute(WSRequestHandler* req);
|
RpcResponse SetCurrentTransition(const RpcRequest&);
|
||||||
static HandlerResponse HandleSetMute(WSRequestHandler* req);
|
RpcResponse SetTransitionDuration(const RpcRequest&);
|
||||||
static HandlerResponse HandleGetMute(WSRequestHandler* req);
|
RpcResponse GetTransitionDuration(const RpcRequest&);
|
||||||
static HandlerResponse HandleSetSyncOffset(WSRequestHandler* req);
|
RpcResponse GetTransitionPosition(const RpcRequest&);
|
||||||
static HandlerResponse HandleGetSyncOffset(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleGetSpecialSources(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleGetSourcesList(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleGetSourceTypesList(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleGetSourceSettings(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleSetSourceSettings(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleTakeSourceScreenshot(WSRequestHandler* req);
|
|
||||||
|
|
||||||
static HandlerResponse HandleGetSourceFilters(WSRequestHandler* req);
|
RpcResponse SetVolume(const RpcRequest&);
|
||||||
static HandlerResponse HandleAddFilterToSource(WSRequestHandler* req);
|
RpcResponse GetVolume(const RpcRequest&);
|
||||||
static HandlerResponse HandleRemoveFilterFromSource(WSRequestHandler* req);
|
RpcResponse ToggleMute(const RpcRequest&);
|
||||||
static HandlerResponse HandleReorderSourceFilter(WSRequestHandler* req);
|
RpcResponse SetMute(const RpcRequest&);
|
||||||
static HandlerResponse HandleMoveSourceFilter(WSRequestHandler* req);
|
RpcResponse GetMute(const RpcRequest&);
|
||||||
static HandlerResponse HandleSetSourceFilterSettings(WSRequestHandler* req);
|
RpcResponse SetSourceName(const RpcRequest&);
|
||||||
|
RpcResponse SetSyncOffset(const RpcRequest&);
|
||||||
|
RpcResponse GetSyncOffset(const RpcRequest&);
|
||||||
|
RpcResponse GetSpecialSources(const RpcRequest&);
|
||||||
|
RpcResponse GetSourcesList(const RpcRequest&);
|
||||||
|
RpcResponse GetSourceTypesList(const RpcRequest&);
|
||||||
|
RpcResponse GetSourceSettings(const RpcRequest&);
|
||||||
|
RpcResponse SetSourceSettings(const RpcRequest&);
|
||||||
|
RpcResponse GetAudioMonitorType(const RpcRequest&);
|
||||||
|
RpcResponse SetAudioMonitorType(const RpcRequest&);
|
||||||
|
RpcResponse TakeSourceScreenshot(const RpcRequest&);
|
||||||
|
|
||||||
static HandlerResponse HandleSetCurrentSceneCollection(WSRequestHandler* req);
|
RpcResponse GetSourceFilters(const RpcRequest&);
|
||||||
static HandlerResponse HandleGetCurrentSceneCollection(WSRequestHandler* req);
|
RpcResponse GetSourceFilterInfo(const RpcRequest&);
|
||||||
static HandlerResponse HandleListSceneCollections(WSRequestHandler* req);
|
RpcResponse AddFilterToSource(const RpcRequest&);
|
||||||
|
RpcResponse RemoveFilterFromSource(const RpcRequest&);
|
||||||
|
RpcResponse ReorderSourceFilter(const RpcRequest&);
|
||||||
|
RpcResponse MoveSourceFilter(const RpcRequest&);
|
||||||
|
RpcResponse SetSourceFilterSettings(const RpcRequest&);
|
||||||
|
RpcResponse SetSourceFilterVisibility(const RpcRequest&);
|
||||||
|
|
||||||
static HandlerResponse HandleSetCurrentProfile(WSRequestHandler* req);
|
RpcResponse SetCurrentSceneCollection(const RpcRequest&);
|
||||||
static HandlerResponse HandleGetCurrentProfile(WSRequestHandler* req);
|
RpcResponse GetCurrentSceneCollection(const RpcRequest&);
|
||||||
static HandlerResponse HandleListProfiles(WSRequestHandler* req);
|
RpcResponse ListSceneCollections(const RpcRequest&);
|
||||||
|
|
||||||
static HandlerResponse HandleSetStreamSettings(WSRequestHandler* req);
|
RpcResponse SetCurrentProfile(const RpcRequest&);
|
||||||
static HandlerResponse HandleGetStreamSettings(WSRequestHandler* req);
|
RpcResponse GetCurrentProfile(const RpcRequest&);
|
||||||
static HandlerResponse HandleSaveStreamSettings(WSRequestHandler* req);
|
RpcResponse ListProfiles(const RpcRequest&);
|
||||||
|
|
||||||
|
RpcResponse SetStreamSettings(const RpcRequest&);
|
||||||
|
RpcResponse GetStreamSettings(const RpcRequest&);
|
||||||
|
RpcResponse SaveStreamSettings(const RpcRequest&);
|
||||||
#if BUILD_CAPTIONS
|
#if BUILD_CAPTIONS
|
||||||
static HandlerResponse HandleSendCaptions(WSRequestHandler * req);
|
RpcResponse SendCaptions(const RpcRequest&);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static HandlerResponse HandleSetTransitionDuration(WSRequestHandler* req);
|
RpcResponse GetStudioModeStatus(const RpcRequest&);
|
||||||
static HandlerResponse HandleGetTransitionDuration(WSRequestHandler* req);
|
RpcResponse GetPreviewScene(const RpcRequest&);
|
||||||
|
RpcResponse SetPreviewScene(const RpcRequest&);
|
||||||
|
RpcResponse TransitionToProgram(const RpcRequest&);
|
||||||
|
RpcResponse EnableStudioMode(const RpcRequest&);
|
||||||
|
RpcResponse DisableStudioMode(const RpcRequest&);
|
||||||
|
RpcResponse ToggleStudioMode(const RpcRequest&);
|
||||||
|
|
||||||
static HandlerResponse HandleGetStudioModeStatus(WSRequestHandler* req);
|
RpcResponse SetTextGDIPlusProperties(const RpcRequest&);
|
||||||
static HandlerResponse HandleGetPreviewScene(WSRequestHandler* req);
|
RpcResponse GetTextGDIPlusProperties(const RpcRequest&);
|
||||||
static HandlerResponse HandleSetPreviewScene(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleTransitionToProgram(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleEnableStudioMode(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleDisableStudioMode(WSRequestHandler* req);
|
|
||||||
static HandlerResponse HandleToggleStudioMode(WSRequestHandler* req);
|
|
||||||
|
|
||||||
static HandlerResponse HandleSetTextGDIPlusProperties(WSRequestHandler* req);
|
RpcResponse SetTextFreetype2Properties(const RpcRequest&);
|
||||||
static HandlerResponse HandleGetTextGDIPlusProperties(WSRequestHandler* req);
|
RpcResponse GetTextFreetype2Properties(const RpcRequest&);
|
||||||
|
|
||||||
static HandlerResponse HandleSetTextFreetype2Properties(WSRequestHandler* req);
|
RpcResponse SetBrowserSourceProperties(const RpcRequest&);
|
||||||
static HandlerResponse HandleGetTextFreetype2Properties(WSRequestHandler* req);
|
RpcResponse GetBrowserSourceProperties(const RpcRequest&);
|
||||||
|
|
||||||
static HandlerResponse HandleSetBrowserSourceProperties(WSRequestHandler* req);
|
RpcResponse ListOutputs(const RpcRequest&);
|
||||||
static HandlerResponse HandleGetBrowserSourceProperties(WSRequestHandler* req);
|
RpcResponse GetOutputInfo(const RpcRequest&);
|
||||||
|
RpcResponse StartOutput(const RpcRequest&);
|
||||||
|
RpcResponse StopOutput(const RpcRequest&);
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
|
#include "WSRequestHandler.h"
|
||||||
|
|
||||||
|
#include <QtCore/QByteArray>
|
||||||
|
#include <QtGui/QImageWriter>
|
||||||
|
|
||||||
#include "obs-websocket.h"
|
#include "obs-websocket.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "WSEvents.h"
|
#include "WSEvents.h"
|
||||||
|
|
||||||
#include "WSRequestHandler.h"
|
|
||||||
|
|
||||||
#define CASE(x) case x: return #x;
|
#define CASE(x) case x: return #x;
|
||||||
const char *describe_output_format(int format) {
|
const char *describe_output_format(int format) {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
@ -60,32 +63,41 @@ const char *describe_scale_type(int scale) {
|
|||||||
* @return {String} `obs-websocket-version` obs-websocket plugin version.
|
* @return {String} `obs-websocket-version` obs-websocket plugin version.
|
||||||
* @return {String} `obs-studio-version` OBS Studio program 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").
|
* @return {String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3").
|
||||||
|
* @return {String} `supported-image-export-formats` List of supported formats for features that use image export (like the TakeSourceScreenshot request type) formatted as a comma-separated list string
|
||||||
*
|
*
|
||||||
* @api requests
|
* @api requests
|
||||||
* @name GetVersion
|
* @name GetVersion
|
||||||
* @category general
|
* @category general
|
||||||
* @since 0.3
|
* @since 0.3
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetVersion(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetVersion(const RpcRequest& request) {
|
||||||
QString obsVersion = Utils::OBSVersionString();
|
QString obsVersion = Utils::OBSVersionString();
|
||||||
|
|
||||||
QList<QString> names = req->messageMap.keys();
|
QList<QString> names = messageMap.keys();
|
||||||
names.sort(Qt::CaseInsensitive);
|
QList<QByteArray> imageWriterFormats = QImageWriter::supportedImageFormats();
|
||||||
|
|
||||||
// (Palakis) OBS' data arrays only support object arrays, so I improvised.
|
// (Palakis) OBS' data arrays only support object arrays, so I improvised.
|
||||||
QString requests;
|
QString requests;
|
||||||
|
names.sort(Qt::CaseInsensitive);
|
||||||
requests += names.takeFirst();
|
requests += names.takeFirst();
|
||||||
for (QString reqName : names) {
|
for (const QString& reqName : names) {
|
||||||
requests += ("," + reqName);
|
requests += ("," + reqName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString supportedImageExportFormats;
|
||||||
|
supportedImageExportFormats += QString::fromUtf8(imageWriterFormats.takeFirst());
|
||||||
|
for (const QByteArray& format : imageWriterFormats) {
|
||||||
|
supportedImageExportFormats += ("," + QString::fromUtf8(format));
|
||||||
|
}
|
||||||
|
|
||||||
OBSDataAutoRelease data = obs_data_create();
|
OBSDataAutoRelease data = obs_data_create();
|
||||||
obs_data_set_double(data, "version", 1.1);
|
obs_data_set_double(data, "version", 1.1);
|
||||||
obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION);
|
obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION);
|
||||||
obs_data_set_string(data, "obs-studio-version", obsVersion.toUtf8());
|
obs_data_set_string(data, "obs-studio-version", obsVersion.toUtf8());
|
||||||
obs_data_set_string(data, "available-requests", requests.toUtf8());
|
obs_data_set_string(data, "available-requests", requests.toUtf8());
|
||||||
|
obs_data_set_string(data, "supported-image-export-formats", supportedImageExportFormats.toUtf8());
|
||||||
|
|
||||||
return req->SendOKResponse(data);
|
return request.success(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -101,7 +113,7 @@ HandlerResponse WSRequestHandler::HandleGetVersion(WSRequestHandler* req) {
|
|||||||
* @category general
|
* @category general
|
||||||
* @since 0.3
|
* @since 0.3
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetAuthRequired(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetAuthRequired(const RpcRequest& request) {
|
||||||
bool authRequired = GetConfig()->AuthRequired;
|
bool authRequired = GetConfig()->AuthRequired;
|
||||||
|
|
||||||
OBSDataAutoRelease data = obs_data_create();
|
OBSDataAutoRelease data = obs_data_create();
|
||||||
@ -115,7 +127,7 @@ HandlerResponse WSRequestHandler::HandleGetAuthRequired(WSRequestHandler* req) {
|
|||||||
config->Salt.toUtf8());
|
config->Salt.toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
return req->SendOKResponse(data);
|
return request.success(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -128,26 +140,26 @@ HandlerResponse WSRequestHandler::HandleGetAuthRequired(WSRequestHandler* req) {
|
|||||||
* @category general
|
* @category general
|
||||||
* @since 0.3
|
* @since 0.3
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleAuthenticate(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::Authenticate(const RpcRequest& request) {
|
||||||
if (!req->hasField("auth")) {
|
if (!request.hasField("auth")) {
|
||||||
return req->SendErrorResponse("missing request parameters");
|
return request.failed("missing request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req->_connProperties.isAuthenticated()) {
|
if (_connProperties.isAuthenticated()) {
|
||||||
return req->SendErrorResponse("already authenticated");
|
return request.failed("already authenticated");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString auth = obs_data_get_string(req->data, "auth");
|
QString auth = obs_data_get_string(request.parameters(), "auth");
|
||||||
if (auth.isEmpty()) {
|
if (auth.isEmpty()) {
|
||||||
return req->SendErrorResponse("auth not specified!");
|
return request.failed("auth not specified!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetConfig()->CheckAuth(auth) == false) {
|
if (GetConfig()->CheckAuth(auth) == false) {
|
||||||
return req->SendErrorResponse("Authentication Failed.");
|
return request.failed("Authentication Failed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
req->_connProperties.setAuthenticated(true);
|
_connProperties.setAuthenticated(true);
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -160,17 +172,18 @@ HandlerResponse WSRequestHandler::HandleAuthenticate(WSRequestHandler* req) {
|
|||||||
* @category general
|
* @category general
|
||||||
* @since 4.3.0
|
* @since 4.3.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleSetHeartbeat(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SetHeartbeat(const RpcRequest& request) {
|
||||||
if (!req->hasField("enable")) {
|
if (!request.hasField("enable")) {
|
||||||
return req->SendErrorResponse("Heartbeat <enable> parameter missing");
|
return request.failed("Heartbeat <enable> parameter missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto events = GetEventsSystem();
|
auto events = GetEventsSystem();
|
||||||
events->HeartbeatIsActive = obs_data_get_bool(req->data, "enable");
|
events->HeartbeatIsActive = obs_data_get_bool(request.parameters(), "enable");
|
||||||
|
|
||||||
OBSDataAutoRelease response = obs_data_create();
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
obs_data_set_bool(response, "enable", events->HeartbeatIsActive);
|
obs_data_set_bool(response, "enable", events->HeartbeatIsActive);
|
||||||
return req->SendOKResponse(response);
|
|
||||||
|
return request.success(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -183,18 +196,19 @@ HandlerResponse WSRequestHandler::HandleSetHeartbeat(WSRequestHandler* req) {
|
|||||||
* @category general
|
* @category general
|
||||||
* @since 4.3.0
|
* @since 4.3.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleSetFilenameFormatting(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SetFilenameFormatting(const RpcRequest& request) {
|
||||||
if (!req->hasField("filename-formatting")) {
|
if (!request.hasField("filename-formatting")) {
|
||||||
return req->SendErrorResponse("<filename-formatting> parameter missing");
|
return request.failed("<filename-formatting> parameter missing");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString filenameFormatting = obs_data_get_string(req->data, "filename-formatting");
|
QString filenameFormatting = obs_data_get_string(request.parameters(), "filename-formatting");
|
||||||
if (filenameFormatting.isEmpty()) {
|
if (filenameFormatting.isEmpty()) {
|
||||||
return req->SendErrorResponse("invalid request parameters");
|
return request.failed("invalid request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::SetFilenameFormatting(filenameFormatting.toUtf8());
|
Utils::SetFilenameFormatting(filenameFormatting.toUtf8());
|
||||||
return req->SendOKResponse();
|
|
||||||
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -207,10 +221,11 @@ HandlerResponse WSRequestHandler::HandleSetFilenameFormatting(WSRequestHandler*
|
|||||||
* @category general
|
* @category general
|
||||||
* @since 4.3.0
|
* @since 4.3.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetFilenameFormatting(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetFilenameFormatting(const RpcRequest& request) {
|
||||||
OBSDataAutoRelease response = obs_data_create();
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
obs_data_set_string(response, "filename-formatting", Utils::GetFilenameFormatting());
|
obs_data_set_string(response, "filename-formatting", Utils::GetFilenameFormatting());
|
||||||
return req->SendOKResponse(response);
|
|
||||||
|
return request.success(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -223,14 +238,49 @@ HandlerResponse WSRequestHandler::HandleGetFilenameFormatting(WSRequestHandler*
|
|||||||
* @category general
|
* @category general
|
||||||
* @since 4.6.0
|
* @since 4.6.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetStats(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetStats(const RpcRequest& request) {
|
||||||
OBSDataAutoRelease stats = GetEventsSystem()->GetStats();
|
OBSDataAutoRelease stats = GetEventsSystem()->GetStats();
|
||||||
|
|
||||||
OBSDataAutoRelease response = obs_data_create();
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
obs_data_set_obj(response, "stats", stats);
|
obs_data_set_obj(response, "stats", stats);
|
||||||
return req->SendOKResponse(response);
|
|
||||||
|
return request.success(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Broadcast custom message to all connected WebSocket clients
|
||||||
|
*
|
||||||
|
* @param {String} `realm` Identifier to be choosen by the client
|
||||||
|
* @param {Object} `data` User-defined data
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name BroadcastCustomMessage
|
||||||
|
* @category general
|
||||||
|
* @since 4.7.0
|
||||||
|
*/
|
||||||
|
RpcResponse WSRequestHandler::BroadcastCustomMessage(const RpcRequest& request) {
|
||||||
|
if (!request.hasField("realm") || !request.hasField("data")) {
|
||||||
|
return request.failed("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString realm = obs_data_get_string(request.parameters(), "realm");
|
||||||
|
OBSDataAutoRelease data = obs_data_get_obj(request.parameters(), "data");
|
||||||
|
|
||||||
|
if (realm.isEmpty()) {
|
||||||
|
return request.failed("realm not specified!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return request.failed("data not specified!");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto events = GetEventsSystem();
|
||||||
|
events->OnBroadcastCustomMessage(realm, data);
|
||||||
|
|
||||||
|
return request.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get basic OBS video information
|
* Get basic OBS video information
|
||||||
*
|
*
|
||||||
@ -249,9 +299,10 @@ HandlerResponse WSRequestHandler::HandleGetStats(WSRequestHandler* req) {
|
|||||||
* @category general
|
* @category general
|
||||||
* @since 4.6.0
|
* @since 4.6.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetVideoInfo(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetVideoInfo(const RpcRequest& request) {
|
||||||
obs_video_info ovi;
|
obs_video_info ovi;
|
||||||
obs_get_video_info(&ovi);
|
obs_get_video_info(&ovi);
|
||||||
|
|
||||||
OBSDataAutoRelease response = obs_data_create();
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
obs_data_set_int(response, "baseWidth", ovi.base_width);
|
obs_data_set_int(response, "baseWidth", ovi.base_width);
|
||||||
obs_data_set_int(response, "baseHeight", ovi.base_height);
|
obs_data_set_int(response, "baseHeight", ovi.base_height);
|
||||||
@ -262,5 +313,34 @@ HandlerResponse WSRequestHandler::HandleGetVideoInfo(WSRequestHandler* req) {
|
|||||||
obs_data_set_string(response, "colorSpace", describe_color_space(ovi.colorspace));
|
obs_data_set_string(response, "colorSpace", describe_color_space(ovi.colorspace));
|
||||||
obs_data_set_string(response, "colorRange", describe_color_range(ovi.range));
|
obs_data_set_string(response, "colorRange", describe_color_range(ovi.range));
|
||||||
obs_data_set_string(response, "scaleType", describe_scale_type(ovi.scale_type));
|
obs_data_set_string(response, "scaleType", describe_scale_type(ovi.scale_type));
|
||||||
return req->SendOKResponse(response);
|
|
||||||
|
return request.success(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 or newer.
|
||||||
|
*
|
||||||
|
* @param {String (Optional)} `type` Type of projector: Preview (default), Source, Scene, StudioProgram, or Multiview (case insensitive).
|
||||||
|
* @param {int (Optional)} `monitor` Monitor to open the projector on. If -1 or omitted, opens a window.
|
||||||
|
* @param {String (Optional)} `geometry` Size and position of the projector window (only if monitor is -1). Encoded in Base64 using Qt's geometry encoding (https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors.
|
||||||
|
* @param {String (Optional)} `name` Name of the source or scene to be displayed (ignored for other projector types).
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name OpenProjector
|
||||||
|
* @category general
|
||||||
|
* @since 4.8.0
|
||||||
|
*/
|
||||||
|
RpcResponse WSRequestHandler::OpenProjector(const RpcRequest& request) {
|
||||||
|
const char* type = obs_data_get_string(request.parameters(), "type");
|
||||||
|
|
||||||
|
int monitor = -1;
|
||||||
|
if (request.hasField("monitor")) {
|
||||||
|
monitor = obs_data_get_int(request.parameters(), "monitor");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* geometry = obs_data_get_string(request.parameters(), "geometry");
|
||||||
|
const char* name = obs_data_get_string(request.parameters(), "name");
|
||||||
|
|
||||||
|
obs_frontend_open_projector(type, monitor, geometry, name);
|
||||||
|
return request.success();
|
||||||
}
|
}
|
||||||
|
184
src/WSRequestHandler_Outputs.cpp
Normal file
184
src/WSRequestHandler_Outputs.cpp
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include "WSRequestHandler.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} `Output`
|
||||||
|
* @property {String} `name` Output name
|
||||||
|
* @property {String} `type` Output type/kind
|
||||||
|
* @property {int} `width` Video output width
|
||||||
|
* @property {int} `height` Video output height
|
||||||
|
* @property {Object} `flags` Output flags
|
||||||
|
* @property {int} `flags.rawValue` Raw flags value
|
||||||
|
* @property {boolean} `flags.audio` Output uses audio
|
||||||
|
* @property {boolean} `flags.video` Output uses video
|
||||||
|
* @property {boolean} `flags.encoded` Output is encoded
|
||||||
|
* @property {boolean} `flags.multiTrack` Output uses several audio tracks
|
||||||
|
* @property {boolean} `flags.service` Output uses a service
|
||||||
|
* @property {Object} `settings` Output name
|
||||||
|
* @property {boolean} `active` Output status (active or not)
|
||||||
|
* @property {boolean} `reconnecting` Output reconnection status (reconnecting or not)
|
||||||
|
* @property {double} `congestion` Output congestion
|
||||||
|
* @property {int} `totalFrames` Number of frames sent
|
||||||
|
* @property {int} `droppedFrames` Number of frames dropped
|
||||||
|
* @property {int} `totalBytes` Total bytes sent
|
||||||
|
*/
|
||||||
|
obs_data_t* getOutputInfo(obs_output_t* output)
|
||||||
|
{
|
||||||
|
if (!output) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataAutoRelease settings = obs_output_get_settings(output);
|
||||||
|
|
||||||
|
uint32_t rawFlags = obs_output_get_flags(output);
|
||||||
|
OBSDataAutoRelease flags = obs_data_create();
|
||||||
|
obs_data_set_int(flags, "rawValue", rawFlags);
|
||||||
|
obs_data_set_bool(flags, "audio", rawFlags & OBS_OUTPUT_AUDIO);
|
||||||
|
obs_data_set_bool(flags, "video", rawFlags & OBS_OUTPUT_VIDEO);
|
||||||
|
obs_data_set_bool(flags, "encoded", rawFlags & OBS_OUTPUT_ENCODED);
|
||||||
|
obs_data_set_bool(flags, "multiTrack", rawFlags & OBS_OUTPUT_MULTI_TRACK);
|
||||||
|
obs_data_set_bool(flags, "service", rawFlags & OBS_OUTPUT_SERVICE);
|
||||||
|
|
||||||
|
obs_data_t* data = obs_data_create();
|
||||||
|
|
||||||
|
obs_data_set_string(data, "name", obs_output_get_name(output));
|
||||||
|
obs_data_set_string(data, "type", obs_output_get_id(output));
|
||||||
|
obs_data_set_int(data, "width", obs_output_get_width(output));
|
||||||
|
obs_data_set_int(data, "height", obs_output_get_height(output));
|
||||||
|
obs_data_set_obj(data, "flags", flags);
|
||||||
|
obs_data_set_obj(data, "settings", settings);
|
||||||
|
|
||||||
|
obs_data_set_bool(data, "active", obs_output_active(output));
|
||||||
|
obs_data_set_bool(data, "reconnecting", obs_output_reconnecting(output));
|
||||||
|
obs_data_set_double(data, "congestion", obs_output_get_congestion(output));
|
||||||
|
obs_data_set_int(data, "totalFrames", obs_output_get_total_frames(output));
|
||||||
|
obs_data_set_int(data, "droppedFrames", obs_output_get_frames_dropped(output));
|
||||||
|
obs_data_set_int(data, "totalBytes", obs_output_get_total_bytes(output));
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
RpcResponse findOutputOrFail(const RpcRequest& request, std::function<RpcResponse (obs_output_t*)> callback)
|
||||||
|
{
|
||||||
|
if (!request.hasField("outputName")) {
|
||||||
|
return request.failed("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* outputName = obs_data_get_string(request.parameters(), "outputName");
|
||||||
|
OBSOutputAutoRelease output = obs_get_output_by_name(outputName);
|
||||||
|
if (!output) {
|
||||||
|
return request.failed("specified output doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List existing outputs
|
||||||
|
*
|
||||||
|
* @return {Array<Output>} `outputs` Outputs list
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name ListOutputs
|
||||||
|
* @category outputs
|
||||||
|
* @since 4.7.0
|
||||||
|
*/
|
||||||
|
RpcResponse WSRequestHandler::ListOutputs(const RpcRequest& request)
|
||||||
|
{
|
||||||
|
OBSDataArrayAutoRelease outputs = obs_data_array_create();
|
||||||
|
|
||||||
|
obs_enum_outputs([](void* param, obs_output_t* output) {
|
||||||
|
obs_data_array_t* outputs = reinterpret_cast<obs_data_array_t*>(param);
|
||||||
|
|
||||||
|
OBSDataAutoRelease outputInfo = getOutputInfo(output);
|
||||||
|
obs_data_array_push_back(outputs, outputInfo);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, outputs);
|
||||||
|
|
||||||
|
OBSDataAutoRelease fields = obs_data_create();
|
||||||
|
obs_data_set_array(fields, "outputs", outputs);
|
||||||
|
|
||||||
|
return request.success(fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get information about a single output
|
||||||
|
*
|
||||||
|
* @param {String} `outputName` Output name
|
||||||
|
*
|
||||||
|
* @return {Output} `outputInfo` Output info
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetOutputInfo
|
||||||
|
* @category outputs
|
||||||
|
* @since 4.7.0
|
||||||
|
*/
|
||||||
|
RpcResponse WSRequestHandler::GetOutputInfo(const RpcRequest& request)
|
||||||
|
{
|
||||||
|
return findOutputOrFail(request, [request](obs_output_t* output) {
|
||||||
|
OBSDataAutoRelease outputInfo = getOutputInfo(output);
|
||||||
|
|
||||||
|
OBSDataAutoRelease fields = obs_data_create();
|
||||||
|
obs_data_set_obj(fields, "outputInfo", outputInfo);
|
||||||
|
return request.success(fields);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start an output
|
||||||
|
*
|
||||||
|
* @param {String} `outputName` Output name
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name StartOutput
|
||||||
|
* @category outputs
|
||||||
|
* @since 4.7.0
|
||||||
|
*/
|
||||||
|
RpcResponse WSRequestHandler::StartOutput(const RpcRequest& request)
|
||||||
|
{
|
||||||
|
return findOutputOrFail(request, [request](obs_output_t* output) {
|
||||||
|
if (obs_output_active(output)) {
|
||||||
|
return request.failed("output already active");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = obs_output_start(output);
|
||||||
|
if (!success) {
|
||||||
|
QString lastError = obs_output_get_last_error(output);
|
||||||
|
QString errorMessage = QString("output start failed: %1").arg(lastError);
|
||||||
|
return request.failed(errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return request.success();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop an output
|
||||||
|
*
|
||||||
|
* @param {String} `outputName` Output name
|
||||||
|
* @param {boolean (optional)} `force` Force stop (default: false)
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name StopOutput
|
||||||
|
* @category outputs
|
||||||
|
* @since 4.7.0
|
||||||
|
*/
|
||||||
|
RpcResponse WSRequestHandler::StopOutput(const RpcRequest& request)
|
||||||
|
{
|
||||||
|
return findOutputOrFail(request, [request](obs_output_t* output) {
|
||||||
|
if (!obs_output_active(output)) {
|
||||||
|
return request.failed("output not active");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool forceStop = obs_data_get_bool(request.parameters(), "force");
|
||||||
|
if (forceStop) {
|
||||||
|
obs_output_force_stop(output);
|
||||||
|
} else {
|
||||||
|
obs_output_stop(output);
|
||||||
|
}
|
||||||
|
|
||||||
|
return request.success();
|
||||||
|
});
|
||||||
|
}
|
@ -12,19 +12,19 @@
|
|||||||
* @category profiles
|
* @category profiles
|
||||||
* @since 4.0.0
|
* @since 4.0.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleSetCurrentProfile(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SetCurrentProfile(const RpcRequest& request) {
|
||||||
if (!req->hasField("profile-name")) {
|
if (!request.hasField("profile-name")) {
|
||||||
return req->SendErrorResponse("missing request parameters");
|
return request.failed("missing request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString profileName = obs_data_get_string(req->data, "profile-name");
|
QString profileName = obs_data_get_string(request.parameters(), "profile-name");
|
||||||
if (profileName.isEmpty()) {
|
if (profileName.isEmpty()) {
|
||||||
return req->SendErrorResponse("invalid request parameters");
|
return request.failed("invalid request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO : check if profile exists
|
// TODO : check if profile exists
|
||||||
obs_frontend_set_current_profile(profileName.toUtf8());
|
obs_frontend_set_current_profile(profileName.toUtf8());
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,10 +37,12 @@ HandlerResponse WSRequestHandler::HandleSetCurrentProfile(WSRequestHandler* req)
|
|||||||
* @category profiles
|
* @category profiles
|
||||||
* @since 4.0.0
|
* @since 4.0.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetCurrentProfile(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetCurrentProfile(const RpcRequest& request) {
|
||||||
OBSDataAutoRelease response = obs_data_create();
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
obs_data_set_string(response, "profile-name", obs_frontend_get_current_profile());
|
char* currentProfile = obs_frontend_get_current_profile();
|
||||||
return req->SendOKResponse(response);
|
obs_data_set_string(response, "profile-name", currentProfile);
|
||||||
|
bfree(currentProfile);
|
||||||
|
return request.success(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,7 +55,7 @@ HandlerResponse WSRequestHandler::HandleGetCurrentProfile(WSRequestHandler* req)
|
|||||||
* @category profiles
|
* @category profiles
|
||||||
* @since 4.0.0
|
* @since 4.0.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleListProfiles(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::ListProfiles(const RpcRequest& request) {
|
||||||
char** profiles = obs_frontend_get_profiles();
|
char** profiles = obs_frontend_get_profiles();
|
||||||
OBSDataArrayAutoRelease list = Utils::StringListToArray(profiles, "profile-name");
|
OBSDataArrayAutoRelease list = Utils::StringListToArray(profiles, "profile-name");
|
||||||
bfree(profiles);
|
bfree(profiles);
|
||||||
@ -61,5 +63,5 @@ HandlerResponse WSRequestHandler::HandleListProfiles(WSRequestHandler* req) {
|
|||||||
OBSDataAutoRelease response = obs_data_create();
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
obs_data_set_array(response, "profiles", list);
|
obs_data_set_array(response, "profiles", list);
|
||||||
|
|
||||||
return req->SendOKResponse(response);
|
return request.success(response);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,17 @@
|
|||||||
|
#include "WSRequestHandler.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <util/platform.h>
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
|
||||||
#include "WSRequestHandler.h"
|
RpcResponse ifCanPause(const RpcRequest& request, std::function<RpcResponse()> callback)
|
||||||
|
{
|
||||||
|
if (!obs_frontend_recording_active()) {
|
||||||
|
return request.failed("recording is not active");
|
||||||
|
}
|
||||||
|
|
||||||
|
return callback();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle recording on or off.
|
* Toggle recording on or off.
|
||||||
@ -10,13 +21,9 @@
|
|||||||
* @category recording
|
* @category recording
|
||||||
* @since 0.3
|
* @since 0.3
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleStartStopRecording(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::StartStopRecording(const RpcRequest& request) {
|
||||||
if (obs_frontend_recording_active())
|
(obs_frontend_recording_active() ? obs_frontend_recording_stop() : obs_frontend_recording_start());
|
||||||
obs_frontend_recording_stop();
|
return request.success();
|
||||||
else
|
|
||||||
obs_frontend_recording_start();
|
|
||||||
|
|
||||||
return req->SendOKResponse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,13 +35,13 @@ HandlerResponse WSRequestHandler::HandleStartStopRecording(WSRequestHandler* req
|
|||||||
* @category recording
|
* @category recording
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::StartRecording(const RpcRequest& request) {
|
||||||
if (obs_frontend_recording_active() == false) {
|
if (obs_frontend_recording_active()) {
|
||||||
obs_frontend_recording_start();
|
return request.failed("recording already active");
|
||||||
return req->SendOKResponse();
|
|
||||||
} else {
|
|
||||||
return req->SendErrorResponse("recording already active");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obs_frontend_recording_start();
|
||||||
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,13 +53,53 @@ HandlerResponse WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
|
|||||||
* @category recording
|
* @category recording
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleStopRecording(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::StopRecording(const RpcRequest& request) {
|
||||||
if (obs_frontend_recording_active() == true) {
|
if (!obs_frontend_recording_active()) {
|
||||||
obs_frontend_recording_stop();
|
return request.failed("recording not active");
|
||||||
return req->SendOKResponse();
|
|
||||||
} else {
|
|
||||||
return req->SendErrorResponse("recording not active");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obs_frontend_recording_stop();
|
||||||
|
return request.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pause the current recording.
|
||||||
|
* Returns an error if recording is not active or already paused.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name PauseRecording
|
||||||
|
* @category recording
|
||||||
|
* @since 4.7.0
|
||||||
|
*/
|
||||||
|
RpcResponse WSRequestHandler::PauseRecording(const RpcRequest& request) {
|
||||||
|
return ifCanPause(request, [request]() {
|
||||||
|
if (obs_frontend_recording_paused()) {
|
||||||
|
return request.failed("recording already paused");
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_frontend_recording_pause(true);
|
||||||
|
return request.success();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resume/unpause the current recording (if paused).
|
||||||
|
* Returns an error if recording is not active or not paused.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name ResumeRecording
|
||||||
|
* @category recording
|
||||||
|
* @since 4.7.0
|
||||||
|
*/
|
||||||
|
RpcResponse WSRequestHandler::ResumeRecording(const RpcRequest& request) {
|
||||||
|
return ifCanPause(request, [request]() {
|
||||||
|
if (!obs_frontend_recording_paused()) {
|
||||||
|
return request.failed("recording is not paused");
|
||||||
|
}
|
||||||
|
|
||||||
|
obs_frontend_recording_pause(false);
|
||||||
|
return request.success();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,7 +110,6 @@ HandlerResponse WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
|
|||||||
* in progress, the change won't be applied immediately and will be
|
* in progress, the change won't be applied immediately and will be
|
||||||
* effective on the next recording.
|
* effective on the next recording.
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @param {String} `rec-folder` Path of the recording folder.
|
* @param {String} `rec-folder` Path of the recording folder.
|
||||||
*
|
*
|
||||||
* @api requests
|
* @api requests
|
||||||
@ -71,18 +117,18 @@ HandlerResponse WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
|
|||||||
* @category recording
|
* @category recording
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleSetRecordingFolder(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SetRecordingFolder(const RpcRequest& request) {
|
||||||
if (!req->hasField("rec-folder")) {
|
if (!request.hasField("rec-folder")) {
|
||||||
return req->SendErrorResponse("missing request parameters");
|
return request.failed("missing request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* newRecFolder = obs_data_get_string(req->data, "rec-folder");
|
const char* newRecFolder = obs_data_get_string(request.parameters(), "rec-folder");
|
||||||
bool success = Utils::SetRecordingFolder(newRecFolder);
|
bool success = Utils::SetRecordingFolder(newRecFolder);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
return req->SendErrorResponse("invalid request parameters");
|
return request.failed("invalid request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -95,11 +141,11 @@ HandlerResponse WSRequestHandler::HandleSetRecordingFolder(WSRequestHandler* req
|
|||||||
* @category recording
|
* @category recording
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetRecordingFolder(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetRecordingFolder(const RpcRequest& request) {
|
||||||
const char* recFolder = Utils::GetRecordingFolder();
|
const char* recFolder = Utils::GetRecordingFolder();
|
||||||
|
|
||||||
OBSDataAutoRelease response = obs_data_create();
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
obs_data_set_string(response, "rec-folder", recFolder);
|
obs_data_set_string(response, "rec-folder", recFolder);
|
||||||
|
|
||||||
return req->SendOKResponse(response);
|
return request.success(response);
|
||||||
}
|
}
|
||||||
|
@ -10,13 +10,13 @@
|
|||||||
* @category replay buffer
|
* @category replay buffer
|
||||||
* @since 4.2.0
|
* @since 4.2.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleStartStopReplayBuffer(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::StartStopReplayBuffer(const RpcRequest& request) {
|
||||||
if (obs_frontend_replay_buffer_active()) {
|
if (obs_frontend_replay_buffer_active()) {
|
||||||
obs_frontend_replay_buffer_stop();
|
obs_frontend_replay_buffer_stop();
|
||||||
} else {
|
} else {
|
||||||
Utils::StartReplayBuffer();
|
Utils::StartReplayBuffer();
|
||||||
}
|
}
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,17 +31,17 @@ HandlerResponse WSRequestHandler::HandleStartStopReplayBuffer(WSRequestHandler*
|
|||||||
* @category replay buffer
|
* @category replay buffer
|
||||||
* @since 4.2.0
|
* @since 4.2.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleStartReplayBuffer(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::StartReplayBuffer(const RpcRequest& request) {
|
||||||
if (!Utils::ReplayBufferEnabled()) {
|
if (!Utils::ReplayBufferEnabled()) {
|
||||||
return req->SendErrorResponse("replay buffer disabled in settings");
|
return request.failed("replay buffer disabled in settings");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (obs_frontend_replay_buffer_active() == true) {
|
if (obs_frontend_replay_buffer_active() == true) {
|
||||||
return req->SendErrorResponse("replay buffer already active");
|
return request.failed("replay buffer already active");
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::StartReplayBuffer();
|
Utils::StartReplayBuffer();
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,12 +53,12 @@ HandlerResponse WSRequestHandler::HandleStartReplayBuffer(WSRequestHandler* req)
|
|||||||
* @category replay buffer
|
* @category replay buffer
|
||||||
* @since 4.2.0
|
* @since 4.2.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleStopReplayBuffer(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::StopReplayBuffer(const RpcRequest& request) {
|
||||||
if (obs_frontend_replay_buffer_active() == true) {
|
if (obs_frontend_replay_buffer_active() == true) {
|
||||||
obs_frontend_replay_buffer_stop();
|
obs_frontend_replay_buffer_stop();
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
} else {
|
} else {
|
||||||
return req->SendErrorResponse("replay buffer not active");
|
return request.failed("replay buffer not active");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,9 +72,9 @@ HandlerResponse WSRequestHandler::HandleStopReplayBuffer(WSRequestHandler* req)
|
|||||||
* @category replay buffer
|
* @category replay buffer
|
||||||
* @since 4.2.0
|
* @since 4.2.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleSaveReplayBuffer(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SaveReplayBuffer(const RpcRequest& request) {
|
||||||
if (!obs_frontend_replay_buffer_active()) {
|
if (!obs_frontend_replay_buffer_active()) {
|
||||||
return req->SendErrorResponse("replay buffer not active");
|
return request.failed("replay buffer not active");
|
||||||
}
|
}
|
||||||
|
|
||||||
OBSOutputAutoRelease replayOutput = obs_frontend_get_replay_buffer_output();
|
OBSOutputAutoRelease replayOutput = obs_frontend_get_replay_buffer_output();
|
||||||
@ -84,5 +84,5 @@ HandlerResponse WSRequestHandler::HandleSaveReplayBuffer(WSRequestHandler* req)
|
|||||||
proc_handler_call(ph, "save", &cd);
|
proc_handler_call(ph, "save", &cd);
|
||||||
calldata_free(&cd);
|
calldata_free(&cd);
|
||||||
|
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
@ -12,19 +12,19 @@
|
|||||||
* @category scene collections
|
* @category scene collections
|
||||||
* @since 4.0.0
|
* @since 4.0.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleSetCurrentSceneCollection(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SetCurrentSceneCollection(const RpcRequest& request) {
|
||||||
if (!req->hasField("sc-name")) {
|
if (!request.hasField("sc-name")) {
|
||||||
return req->SendErrorResponse("missing request parameters");
|
return request.failed("missing request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString sceneCollection = obs_data_get_string(req->data, "sc-name");
|
QString sceneCollection = obs_data_get_string(request.parameters(), "sc-name");
|
||||||
if (sceneCollection.isEmpty()) {
|
if (sceneCollection.isEmpty()) {
|
||||||
return req->SendErrorResponse("invalid request parameters");
|
return request.failed("invalid request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO : Check if specified profile exists and if changing is allowed
|
// TODO : Check if specified profile exists and if changing is allowed
|
||||||
obs_frontend_set_current_scene_collection(sceneCollection.toUtf8());
|
obs_frontend_set_current_scene_collection(sceneCollection.toUtf8());
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,12 +37,14 @@ HandlerResponse WSRequestHandler::HandleSetCurrentSceneCollection(WSRequestHandl
|
|||||||
* @category scene collections
|
* @category scene collections
|
||||||
* @since 4.0.0
|
* @since 4.0.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetCurrentSceneCollection(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetCurrentSceneCollection(const RpcRequest& request) {
|
||||||
OBSDataAutoRelease response = obs_data_create();
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
obs_data_set_string(response, "sc-name",
|
|
||||||
obs_frontend_get_current_scene_collection());
|
|
||||||
|
|
||||||
return req->SendOKResponse(response);
|
char* sceneCollection = obs_frontend_get_current_scene_collection();
|
||||||
|
obs_data_set_string(response, "sc-name", sceneCollection);
|
||||||
|
bfree(sceneCollection);
|
||||||
|
|
||||||
|
return request.success(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,7 +57,7 @@ HandlerResponse WSRequestHandler::HandleGetCurrentSceneCollection(WSRequestHandl
|
|||||||
* @category scene collections
|
* @category scene collections
|
||||||
* @since 4.0.0
|
* @since 4.0.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleListSceneCollections(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::ListSceneCollections(const RpcRequest& request) {
|
||||||
char** sceneCollections = obs_frontend_get_scene_collections();
|
char** sceneCollections = obs_frontend_get_scene_collections();
|
||||||
OBSDataArrayAutoRelease list =
|
OBSDataArrayAutoRelease list =
|
||||||
Utils::StringListToArray(sceneCollections, "sc-name");
|
Utils::StringListToArray(sceneCollections, "sc-name");
|
||||||
@ -64,5 +66,5 @@ HandlerResponse WSRequestHandler::HandleListSceneCollections(WSRequestHandler* r
|
|||||||
OBSDataAutoRelease response = obs_data_create();
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
obs_data_set_array(response, "scene-collections", list);
|
obs_data_set_array(response, "scene-collections", list);
|
||||||
|
|
||||||
return req->SendOKResponse(response);
|
return request.success(response);
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,13 @@
|
|||||||
* Gets the scene specific properties of the specified source item.
|
* Gets the scene specific properties of the specified source item.
|
||||||
* Coordinates are relative to the item's parent (the scene or group it belongs to).
|
* Coordinates are relative to the item's parent (the scene or group it belongs to).
|
||||||
*
|
*
|
||||||
* @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.
|
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
|
||||||
* @param {String} `item` The name of the source.
|
* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).
|
||||||
|
* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object)
|
||||||
|
* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object)
|
||||||
*
|
*
|
||||||
* @return {String} `name` The name of the source.
|
* @return {String} `name` Scene Item name.
|
||||||
|
* @return {int} `itemId` Scene Item ID.
|
||||||
* @return {int} `position.x` The x position of the source from the left.
|
* @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.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 {int} `position.alignment` The point on the source that the item is manipulated from.
|
||||||
@ -21,6 +24,7 @@
|
|||||||
* @return {int} `crop.bottom` The number of pixels cropped off the bottom 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 {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 {bool} `visible` If the source is visible.
|
||||||
|
* @return {bool} `muted` If the source is muted.
|
||||||
* @return {bool} `locked` If the source's transform is locked.
|
* @return {bool} `locked` If the source's transform is locked.
|
||||||
* @return {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE".
|
* @return {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE".
|
||||||
* @return {int} `bounds.alignment` Alignment of the bounding box.
|
* @return {int} `bounds.alignment` Alignment of the bounding box.
|
||||||
@ -30,48 +34,51 @@
|
|||||||
* @return {int} `sourceHeight` Base source (without scaling) of the source
|
* @return {int} `sourceHeight` Base source (without scaling) of the source
|
||||||
* @return {double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)
|
* @return {double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)
|
||||||
* @return {double} `height` Scene item height (base source height multiplied by the vertical scaling factor)
|
* @return {double} `height` Scene item height (base source height multiplied by the vertical scaling factor)
|
||||||
* @property {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
|
* @return {int} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.
|
||||||
* @property {Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group)
|
* @return {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)
|
||||||
|
* @return {Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is a group)
|
||||||
*
|
*
|
||||||
* @api requests
|
* @api requests
|
||||||
* @name GetSceneItemProperties
|
* @name GetSceneItemProperties
|
||||||
* @category scene items
|
* @category scene items
|
||||||
* @since 4.3.0
|
* @since 4.3.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetSceneItemProperties(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetSceneItemProperties(const RpcRequest& request) {
|
||||||
if (!req->hasField("item")) {
|
if (!request.hasField("item")) {
|
||||||
return req->SendErrorResponse("missing request parameters");
|
return request.failed("missing request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString itemName = obs_data_get_string(req->data, "item");
|
OBSData params = request.parameters();
|
||||||
if (itemName.isEmpty()) {
|
|
||||||
return req->SendErrorResponse("invalid request parameters");
|
|
||||||
}
|
|
||||||
|
|
||||||
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
QString sceneName = obs_data_get_string(params, "scene-name");
|
||||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
if (!scene) {
|
if (!scene) {
|
||||||
return req->SendErrorResponse("requested scene doesn't exist");
|
return request.failed("requested scene doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
OBSSceneItemAutoRelease sceneItem =
|
OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item");
|
||||||
Utils::GetSceneItemFromName(scene, itemName);
|
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField);
|
||||||
if (!sceneItem) {
|
if (!sceneItem) {
|
||||||
return req->SendErrorResponse("specified scene item doesn't exist");
|
return request.failed("specified scene item doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
OBSDataAutoRelease data = Utils::GetSceneItemPropertiesData(sceneItem);
|
OBSDataAutoRelease data = Utils::GetSceneItemPropertiesData(sceneItem);
|
||||||
obs_data_set_string(data, "name", itemName.toUtf8());
|
|
||||||
|
|
||||||
return req->SendOKResponse(data);
|
OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem);
|
||||||
|
obs_data_set_string(data, "name", obs_source_get_name(sceneItemSource));
|
||||||
|
obs_data_set_int(data, "itemId", obs_sceneitem_get_id(sceneItem));
|
||||||
|
|
||||||
|
return request.success(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the scene specific properties of a source. Unspecified properties will remain unchanged.
|
* Sets the scene specific properties of a source. Unspecified properties will remain unchanged.
|
||||||
* Coordinates are relative to the item's parent (the scene or group it belongs to).
|
* Coordinates are relative to the item's parent (the scene or group it belongs to).
|
||||||
*
|
*
|
||||||
* @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.
|
* @param {String (optional)} `scene-name` Name of the scene the source item belongs to. Defaults to the current scene.
|
||||||
* @param {String} `item` The name of the source.
|
* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).
|
||||||
|
* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object)
|
||||||
|
* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object)
|
||||||
* @param {int (optional)} `position.x` The new x position of the source.
|
* @param {int (optional)} `position.x` The new x position of the source.
|
||||||
* @param {int (optional)} `position.y` The new y position of the source.
|
* @param {int (optional)} `position.y` The new y position of the source.
|
||||||
* @param {int (optional)} `position.alignment` The new alignment of the source.
|
* @param {int (optional)} `position.alignment` The new alignment of the source.
|
||||||
@ -94,82 +101,87 @@ HandlerResponse WSRequestHandler::HandleGetSceneItemProperties(WSRequestHandler*
|
|||||||
* @category scene items
|
* @category scene items
|
||||||
* @since 4.3.0
|
* @since 4.3.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SetSceneItemProperties(const RpcRequest& request) {
|
||||||
if (!req->hasField("item")) {
|
if (!request.hasField("item")) {
|
||||||
return req->SendErrorResponse("missing request parameters");
|
return request.failed("missing request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString itemName = obs_data_get_string(req->data, "item");
|
OBSData params = request.parameters();
|
||||||
if (itemName.isEmpty()) {
|
|
||||||
return req->SendErrorResponse("invalid request parameters");
|
|
||||||
}
|
|
||||||
|
|
||||||
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
QString sceneName = obs_data_get_string(params, "scene-name");
|
||||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
if (!scene) {
|
if (!scene) {
|
||||||
return req->SendErrorResponse("requested scene doesn't exist");
|
return request.failed("requested scene doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
OBSSceneItemAutoRelease sceneItem =
|
OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item");
|
||||||
Utils::GetSceneItemFromName(scene, itemName);
|
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField);
|
||||||
if (!sceneItem) {
|
if (!sceneItem) {
|
||||||
return req->SendErrorResponse("specified scene item doesn't exist");
|
return request.failed("specified scene item doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool badRequest = false;
|
bool badRequest = false;
|
||||||
OBSDataAutoRelease errorMessage = obs_data_create();
|
OBSDataAutoRelease errorData = obs_data_create();
|
||||||
|
|
||||||
obs_sceneitem_defer_update_begin(sceneItem);
|
obs_sceneitem_defer_update_begin(sceneItem);
|
||||||
|
|
||||||
if (req->hasField("position")) {
|
if (request.hasField("position")) {
|
||||||
vec2 oldPosition;
|
vec2 oldPosition;
|
||||||
OBSDataAutoRelease positionError = obs_data_create();
|
OBSDataAutoRelease positionError = obs_data_create();
|
||||||
obs_sceneitem_get_pos(sceneItem, &oldPosition);
|
obs_sceneitem_get_pos(sceneItem, &oldPosition);
|
||||||
OBSDataAutoRelease reqPosition = obs_data_get_obj(req->data, "position");
|
|
||||||
|
OBSDataAutoRelease reqPosition = obs_data_get_obj(params, "position");
|
||||||
vec2 newPosition = oldPosition;
|
vec2 newPosition = oldPosition;
|
||||||
|
|
||||||
if (obs_data_has_user_value(reqPosition, "x")) {
|
if (obs_data_has_user_value(reqPosition, "x")) {
|
||||||
newPosition.x = obs_data_get_int(reqPosition, "x");
|
newPosition.x = obs_data_get_int(reqPosition, "x");
|
||||||
}
|
}
|
||||||
if (obs_data_has_user_value(reqPosition, "y")) {
|
if (obs_data_has_user_value(reqPosition, "y")) {
|
||||||
newPosition.y = obs_data_get_int(reqPosition, "y");
|
newPosition.y = obs_data_get_int(reqPosition, "y");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (obs_data_has_user_value(reqPosition, "alignment")) {
|
if (obs_data_has_user_value(reqPosition, "alignment")) {
|
||||||
const uint32_t alignment = obs_data_get_int(reqPosition, "alignment");
|
const uint32_t alignment = obs_data_get_int(reqPosition, "alignment");
|
||||||
if (Utils::IsValidAlignment(alignment)) {
|
if (Utils::IsValidAlignment(alignment)) {
|
||||||
obs_sceneitem_set_alignment(sceneItem, alignment);
|
obs_sceneitem_set_alignment(sceneItem, alignment);
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
badRequest = true;
|
badRequest = true;
|
||||||
obs_data_set_string(positionError, "alignment", "invalid");
|
obs_data_set_string(positionError, "alignment", "invalid");
|
||||||
obs_data_set_obj(errorMessage, "position", positionError);
|
obs_data_set_obj(errorData, "position", positionError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_sceneitem_set_pos(sceneItem, &newPosition);
|
obs_sceneitem_set_pos(sceneItem, &newPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req->hasField("rotation")) {
|
if (request.hasField("rotation")) {
|
||||||
obs_sceneitem_set_rot(sceneItem, (float)obs_data_get_double(req->data, "rotation"));
|
obs_sceneitem_set_rot(sceneItem, (float)obs_data_get_double(params, "rotation"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req->hasField("scale")) {
|
if (request.hasField("scale")) {
|
||||||
vec2 oldScale;
|
vec2 oldScale;
|
||||||
obs_sceneitem_get_scale(sceneItem, &oldScale);
|
obs_sceneitem_get_scale(sceneItem, &oldScale);
|
||||||
OBSDataAutoRelease reqScale = obs_data_get_obj(req->data, "scale");
|
|
||||||
vec2 newScale = oldScale;
|
vec2 newScale = oldScale;
|
||||||
|
|
||||||
|
OBSDataAutoRelease reqScale = obs_data_get_obj(params, "scale");
|
||||||
|
|
||||||
if (obs_data_has_user_value(reqScale, "x")) {
|
if (obs_data_has_user_value(reqScale, "x")) {
|
||||||
newScale.x = obs_data_get_double(reqScale, "x");
|
newScale.x = obs_data_get_double(reqScale, "x");
|
||||||
}
|
}
|
||||||
if (obs_data_has_user_value(reqScale, "y")) {
|
if (obs_data_has_user_value(reqScale, "y")) {
|
||||||
newScale.y = obs_data_get_double(reqScale, "y");
|
newScale.y = obs_data_get_double(reqScale, "y");
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_sceneitem_set_scale(sceneItem, &newScale);
|
obs_sceneitem_set_scale(sceneItem, &newScale);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req->hasField("crop")) {
|
if (request.hasField("crop")) {
|
||||||
obs_sceneitem_crop oldCrop;
|
obs_sceneitem_crop oldCrop;
|
||||||
obs_sceneitem_get_crop(sceneItem, &oldCrop);
|
obs_sceneitem_get_crop(sceneItem, &oldCrop);
|
||||||
OBSDataAutoRelease reqCrop = obs_data_get_obj(req->data, "crop");
|
|
||||||
|
OBSDataAutoRelease reqCrop = obs_data_get_obj(params, "crop");
|
||||||
obs_sceneitem_crop newCrop = oldCrop;
|
obs_sceneitem_crop newCrop = oldCrop;
|
||||||
|
|
||||||
if (obs_data_has_user_value(reqCrop, "top")) {
|
if (obs_data_has_user_value(reqCrop, "top")) {
|
||||||
newCrop.top = obs_data_get_int(reqCrop, "top");
|
newCrop.top = obs_data_get_int(reqCrop, "top");
|
||||||
}
|
}
|
||||||
@ -182,21 +194,23 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler*
|
|||||||
if (obs_data_has_user_value(reqCrop, "left")) {
|
if (obs_data_has_user_value(reqCrop, "left")) {
|
||||||
newCrop.left = obs_data_get_int(reqCrop, "left");
|
newCrop.left = obs_data_get_int(reqCrop, "left");
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_sceneitem_set_crop(sceneItem, &newCrop);
|
obs_sceneitem_set_crop(sceneItem, &newCrop);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req->hasField("visible")) {
|
if (request.hasField("visible")) {
|
||||||
obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(req->data, "visible"));
|
obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(params, "visible"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req->hasField("locked")) {
|
if (request.hasField("locked")) {
|
||||||
obs_sceneitem_set_locked(sceneItem, obs_data_get_bool(req->data, "locked"));
|
obs_sceneitem_set_locked(sceneItem, obs_data_get_bool(params, "locked"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req->hasField("bounds")) {
|
if (request.hasField("bounds")) {
|
||||||
bool badBounds = false;
|
bool badBounds = false;
|
||||||
OBSDataAutoRelease boundsError = obs_data_create();
|
OBSDataAutoRelease boundsError = obs_data_create();
|
||||||
OBSDataAutoRelease reqBounds = obs_data_get_obj(req->data, "bounds");
|
OBSDataAutoRelease reqBounds = obs_data_get_obj(params, "bounds");
|
||||||
|
|
||||||
if (obs_data_has_user_value(reqBounds, "type")) {
|
if (obs_data_has_user_value(reqBounds, "type")) {
|
||||||
QString newBoundsType = obs_data_get_string(reqBounds, "type");
|
QString newBoundsType = obs_data_get_string(reqBounds, "type");
|
||||||
if (newBoundsType == "OBS_BOUNDS_NONE") {
|
if (newBoundsType == "OBS_BOUNDS_NONE") {
|
||||||
@ -225,16 +239,20 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler*
|
|||||||
obs_data_set_string(boundsError, "type", "invalid");
|
obs_data_set_string(boundsError, "type", "invalid");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vec2 oldBounds;
|
vec2 oldBounds;
|
||||||
obs_sceneitem_get_bounds(sceneItem, &oldBounds);
|
obs_sceneitem_get_bounds(sceneItem, &oldBounds);
|
||||||
vec2 newBounds = oldBounds;
|
vec2 newBounds = oldBounds;
|
||||||
|
|
||||||
if (obs_data_has_user_value(reqBounds, "x")) {
|
if (obs_data_has_user_value(reqBounds, "x")) {
|
||||||
newBounds.x = obs_data_get_double(reqBounds, "x");
|
newBounds.x = obs_data_get_double(reqBounds, "x");
|
||||||
}
|
}
|
||||||
if (obs_data_has_user_value(reqBounds, "y")) {
|
if (obs_data_has_user_value(reqBounds, "y")) {
|
||||||
newBounds.y = obs_data_get_double(reqBounds, "y");
|
newBounds.y = obs_data_get_double(reqBounds, "y");
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_sceneitem_set_bounds(sceneItem, &newBounds);
|
obs_sceneitem_set_bounds(sceneItem, &newBounds);
|
||||||
|
|
||||||
if (obs_data_has_user_value(reqBounds, "alignment")) {
|
if (obs_data_has_user_value(reqBounds, "alignment")) {
|
||||||
const uint32_t bounds_alignment = obs_data_get_int(reqBounds, "alignment");
|
const uint32_t bounds_alignment = obs_data_get_int(reqBounds, "alignment");
|
||||||
if (Utils::IsValidAlignment(bounds_alignment)) {
|
if (Utils::IsValidAlignment(bounds_alignment)) {
|
||||||
@ -245,52 +263,51 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler*
|
|||||||
obs_data_set_string(boundsError, "alignment", "invalid");
|
obs_data_set_string(boundsError, "alignment", "invalid");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (badBounds) {
|
if (badBounds) {
|
||||||
obs_data_set_obj(errorMessage, "bounds", boundsError);
|
obs_data_set_obj(errorData, "bounds", boundsError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_sceneitem_defer_update_end(sceneItem);
|
obs_sceneitem_defer_update_end(sceneItem);
|
||||||
|
|
||||||
if (badRequest) {
|
if (badRequest) {
|
||||||
return req->SendErrorResponse(errorMessage);
|
return request.failed("error", errorData);
|
||||||
}
|
}
|
||||||
|
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset a scene item.
|
* Reset a scene item.
|
||||||
*
|
*
|
||||||
* @param {String (optional)} `scene-name` Name of the scene the source belongs to. Defaults to the current scene.
|
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
|
||||||
* @param {String} `item` Name of the source item.
|
* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).
|
||||||
|
* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object)
|
||||||
|
* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object)
|
||||||
*
|
*
|
||||||
* @api requests
|
* @api requests
|
||||||
* @name ResetSceneItem
|
* @name ResetSceneItem
|
||||||
* @category scene items
|
* @category scene items
|
||||||
* @since 4.2.0
|
* @since 4.2.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleResetSceneItem(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::ResetSceneItem(const RpcRequest& request) {
|
||||||
// TODO: remove this request, or refactor it to ResetSource
|
if (!request.hasField("item")) {
|
||||||
|
return request.failed("missing request parameters");
|
||||||
if (!req->hasField("item")) {
|
|
||||||
return req->SendErrorResponse("missing request parameters");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* itemName = obs_data_get_string(req->data, "item");
|
OBSData params = request.parameters();
|
||||||
if (!itemName) {
|
|
||||||
return req->SendErrorResponse("invalid request parameters");
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* sceneName = obs_data_get_string(req->data, "scene-name");
|
const char* sceneName = obs_data_get_string(params, "scene-name");
|
||||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
if (!scene) {
|
if (!scene) {
|
||||||
return req->SendErrorResponse("requested scene doesn't exist");
|
return request.failed("requested scene doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item");
|
||||||
|
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField);
|
||||||
if (!sceneItem) {
|
if (!sceneItem) {
|
||||||
return req->SendErrorResponse("specified scene item doesn't exist");
|
return request.failed("specified scene item doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem);
|
OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem);
|
||||||
@ -298,15 +315,15 @@ HandlerResponse WSRequestHandler::HandleResetSceneItem(WSRequestHandler* req) {
|
|||||||
OBSDataAutoRelease settings = obs_source_get_settings(sceneItemSource);
|
OBSDataAutoRelease settings = obs_source_get_settings(sceneItemSource);
|
||||||
obs_source_update(sceneItemSource, settings);
|
obs_source_update(sceneItemSource, settings);
|
||||||
|
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show or hide a specified source item in a specified scene.
|
* Show or hide a specified source item in a specified scene.
|
||||||
*
|
*
|
||||||
* @param {String} `source` Scene item name in the specified scene.
|
* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the currently active scene.
|
||||||
|
* @param {String} `source` Scene Item name.
|
||||||
* @param {boolean} `render` true = shown ; false = hidden
|
* @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
|
* @api requests
|
||||||
* @name SetSceneItemRender
|
* @name SetSceneItemRender
|
||||||
@ -314,41 +331,41 @@ HandlerResponse WSRequestHandler::HandleResetSceneItem(WSRequestHandler* req) {
|
|||||||
* @since 0.3
|
* @since 0.3
|
||||||
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleSetSceneItemRender(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SetSceneItemRender(const RpcRequest& request) {
|
||||||
if (!req->hasField("source") ||
|
if (!request.hasField("source") ||
|
||||||
!req->hasField("render"))
|
!request.hasField("render"))
|
||||||
{
|
{
|
||||||
return req->SendErrorResponse("missing request parameters");
|
return request.failed("missing request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* itemName = obs_data_get_string(req->data, "source");
|
const char* itemName = obs_data_get_string(request.parameters(), "source");
|
||||||
bool isVisible = obs_data_get_bool(req->data, "render");
|
bool isVisible = obs_data_get_bool(request.parameters(), "render");
|
||||||
|
|
||||||
if (!itemName) {
|
if (!itemName) {
|
||||||
return req->SendErrorResponse("invalid request parameters");
|
return request.failed("invalid request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* sceneName = obs_data_get_string(req->data, "scene-name");
|
const char* sceneName = obs_data_get_string(request.parameters(), "scene-name");
|
||||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
if (!scene) {
|
if (!scene) {
|
||||||
return req->SendErrorResponse("requested scene doesn't exist");
|
return request.failed("requested scene doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
OBSSceneItemAutoRelease sceneItem =
|
OBSSceneItemAutoRelease sceneItem =
|
||||||
Utils::GetSceneItemFromName(scene, itemName);
|
Utils::GetSceneItemFromName(scene, itemName);
|
||||||
if (!sceneItem) {
|
if (!sceneItem) {
|
||||||
return req->SendErrorResponse("specified scene item doesn't exist");
|
return request.failed("specified scene item doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_sceneitem_set_visible(sceneItem, isVisible);
|
obs_sceneitem_set_visible(sceneItem, isVisible);
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the coordinates of a specified source item.
|
* 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 (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
|
||||||
* @param {String} `item` The name of the source item.
|
* @param {String} `item` Scene Item name.
|
||||||
* @param {double} `x` X coordinate.
|
* @param {double} `x` X coordinate.
|
||||||
* @param {double} `y` Y coordinate.
|
* @param {double} `y` Y coordinate.
|
||||||
|
|
||||||
@ -359,41 +376,41 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemRender(WSRequestHandler* req
|
|||||||
* @since 4.0.0
|
* @since 4.0.0
|
||||||
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleSetSceneItemPosition(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SetSceneItemPosition(const RpcRequest& request) {
|
||||||
if (!req->hasField("item") ||
|
if (!request.hasField("item") ||
|
||||||
!req->hasField("x") || !req->hasField("y")) {
|
!request.hasField("x") || !request.hasField("y")) {
|
||||||
return req->SendErrorResponse("missing request parameters");
|
return request.failed("missing request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString itemName = obs_data_get_string(req->data, "item");
|
QString itemName = obs_data_get_string(request.parameters(), "item");
|
||||||
if (itemName.isEmpty()) {
|
if (itemName.isEmpty()) {
|
||||||
return req->SendErrorResponse("invalid request parameters");
|
return request.failed("invalid request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
QString sceneName = obs_data_get_string(request.parameters(), "scene-name");
|
||||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
if (!scene) {
|
if (!scene) {
|
||||||
return req->SendErrorResponse("requested scene could not be found");
|
return request.failed("requested scene could not be found");
|
||||||
}
|
}
|
||||||
|
|
||||||
OBSSceneItem sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
OBSSceneItem sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||||
if (!sceneItem) {
|
if (!sceneItem) {
|
||||||
return req->SendErrorResponse("specified scene item doesn't exist");
|
return request.failed("specified scene item doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
vec2 item_position = { 0 };
|
vec2 item_position = { 0 };
|
||||||
item_position.x = obs_data_get_double(req->data, "x");
|
item_position.x = obs_data_get_double(request.parameters(), "x");
|
||||||
item_position.y = obs_data_get_double(req->data, "y");
|
item_position.y = obs_data_get_double(request.parameters(), "y");
|
||||||
obs_sceneitem_set_pos(sceneItem, &item_position);
|
obs_sceneitem_set_pos(sceneItem, &item_position);
|
||||||
|
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the transform of the specified source item.
|
* 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 (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
|
||||||
* @param {String} `item` The name of the source item.
|
* @param {String} `item` Scene Item name.
|
||||||
* @param {double} `x-scale` Width scale factor.
|
* @param {double} `x-scale` Width scale factor.
|
||||||
* @param {double} `y-scale` Height scale factor.
|
* @param {double} `y-scale` Height scale factor.
|
||||||
* @param {double} `rotation` Source item rotation (in degrees).
|
* @param {double} `rotation` Source item rotation (in degrees).
|
||||||
@ -404,34 +421,34 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemPosition(WSRequestHandler* r
|
|||||||
* @since 4.0.0
|
* @since 4.0.0
|
||||||
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SetSceneItemTransform(const RpcRequest& request) {
|
||||||
if (!req->hasField("item") ||
|
if (!request.hasField("item") ||
|
||||||
!req->hasField("x-scale") ||
|
!request.hasField("x-scale") ||
|
||||||
!req->hasField("y-scale") ||
|
!request.hasField("y-scale") ||
|
||||||
!req->hasField("rotation"))
|
!request.hasField("rotation"))
|
||||||
{
|
{
|
||||||
return req->SendErrorResponse("missing request parameters");
|
return request.failed("missing request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString itemName = obs_data_get_string(req->data, "item");
|
QString itemName = obs_data_get_string(request.parameters(), "item");
|
||||||
if (itemName.isEmpty()) {
|
if (itemName.isEmpty()) {
|
||||||
return req->SendErrorResponse("invalid request parameters");
|
return request.failed("invalid request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
QString sceneName = obs_data_get_string(request.parameters(), "scene-name");
|
||||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
if (!scene) {
|
if (!scene) {
|
||||||
return req->SendErrorResponse("requested scene doesn't exist");
|
return request.failed("requested scene doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
vec2 scale;
|
vec2 scale;
|
||||||
scale.x = obs_data_get_double(req->data, "x-scale");
|
scale.x = obs_data_get_double(request.parameters(), "x-scale");
|
||||||
scale.y = obs_data_get_double(req->data, "y-scale");
|
scale.y = obs_data_get_double(request.parameters(), "y-scale");
|
||||||
float rotation = obs_data_get_double(req->data, "rotation");
|
float rotation = obs_data_get_double(request.parameters(), "rotation");
|
||||||
|
|
||||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||||
if (!sceneItem) {
|
if (!sceneItem) {
|
||||||
return req->SendErrorResponse("specified scene item doesn't exist");
|
return request.failed("specified scene item doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_sceneitem_defer_update_begin(sceneItem);
|
obs_sceneitem_defer_update_begin(sceneItem);
|
||||||
@ -441,14 +458,14 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler*
|
|||||||
|
|
||||||
obs_sceneitem_defer_update_end(sceneItem);
|
obs_sceneitem_defer_update_end(sceneItem);
|
||||||
|
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the crop coordinates of the specified source item.
|
* 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 (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.
|
||||||
* @param {String} `item` The name of the source.
|
* @param {String} `item` Scene Item name.
|
||||||
* @param {int} `top` Pixel position of the top of the source item.
|
* @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} `bottom` Pixel position of the bottom of the source item.
|
||||||
* @param {int} `left` Pixel position of the left of the source item.
|
* @param {int} `left` Pixel position of the left of the source item.
|
||||||
@ -460,83 +477,71 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler*
|
|||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleSetSceneItemCrop(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SetSceneItemCrop(const RpcRequest& request) {
|
||||||
if (!req->hasField("item")) {
|
if (!request.hasField("item")) {
|
||||||
return req->SendErrorResponse("missing request parameters");
|
return request.failed("missing request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString itemName = obs_data_get_string(req->data, "item");
|
QString itemName = obs_data_get_string(request.parameters(), "item");
|
||||||
if (itemName.isEmpty()) {
|
if (itemName.isEmpty()) {
|
||||||
return req->SendErrorResponse("invalid request parameters");
|
return request.failed("invalid request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
QString sceneName = obs_data_get_string(request.parameters(), "scene-name");
|
||||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
if (!scene) {
|
if (!scene) {
|
||||||
return req->SendErrorResponse("requested scene doesn't exist");
|
return request.failed("requested scene doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||||
if (!sceneItem) {
|
if (!sceneItem) {
|
||||||
return req->SendErrorResponse("specified scene item doesn't exist");
|
return request.failed("specified scene item doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
struct obs_sceneitem_crop crop = { 0 };
|
struct obs_sceneitem_crop crop = { 0 };
|
||||||
crop.top = obs_data_get_int(req->data, "top");
|
crop.top = obs_data_get_int(request.parameters(), "top");
|
||||||
crop.bottom = obs_data_get_int(req->data, "bottom");
|
crop.bottom = obs_data_get_int(request.parameters(), "bottom");
|
||||||
crop.left = obs_data_get_int(req->data, "left");
|
crop.left = obs_data_get_int(request.parameters(), "left");
|
||||||
crop.right = obs_data_get_int(req->data, "right");
|
crop.right = obs_data_get_int(request.parameters(), "right");
|
||||||
|
|
||||||
obs_sceneitem_set_crop(sceneItem, &crop);
|
obs_sceneitem_set_crop(sceneItem, &crop);
|
||||||
|
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes a scene item.
|
* Deletes a scene item.
|
||||||
*
|
*
|
||||||
* @param {String (optional)} `scene` Name of the scene the source belongs to. Defaults to the current scene.
|
* @param {String (optional)} `scene` Name of the scene the scene item belongs to. Defaults to the current scene.
|
||||||
* @param {Object} `item` item to delete (required)
|
* @param {Object} `item` Scene item to delete (required)
|
||||||
* @param {String} `item.name` name of the scene item (prefer `id`, including both is acceptable).
|
* @param {String} `item.name` Scene Item name (prefer `id`, including both is acceptable).
|
||||||
* @param {int} `item.id` id of the scene item.
|
* @param {int} `item.id` Scene Item ID.
|
||||||
*
|
*
|
||||||
* @api requests
|
* @api requests
|
||||||
* @name DeleteSceneItem
|
* @name DeleteSceneItem
|
||||||
* @category scene items
|
* @category scene items
|
||||||
* @since 4.5.0
|
* @since 4.5.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleDeleteSceneItem(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::DeleteSceneItem(const RpcRequest& request) {
|
||||||
if (!req->hasField("item")) {
|
if (!request.hasField("item")) {
|
||||||
return req->SendErrorResponse("missing request parameters");
|
return request.failed("missing request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* sceneName = obs_data_get_string(req->data, "scene");
|
const char* sceneName = obs_data_get_string(request.parameters(), "scene");
|
||||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
if (!scene) {
|
if (!scene) {
|
||||||
return req->SendErrorResponse("requested scene doesn't exist");
|
return request.failed("requested scene doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
OBSDataAutoRelease item = obs_data_get_obj(req->data, "item");
|
OBSDataItemAutoRelease itemField = obs_data_item_byname(request.parameters(), "item");
|
||||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item);
|
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField);
|
||||||
if (!sceneItem) {
|
if (!sceneItem) {
|
||||||
return req->SendErrorResponse("item with id/name combination not found in specified scene");
|
return request.failed("item with id/name combination not found in specified scene");
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_sceneitem_remove(sceneItem);
|
obs_sceneitem_remove(sceneItem);
|
||||||
|
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
|
||||||
|
|
||||||
struct DuplicateSceneItemData {
|
|
||||||
obs_sceneitem_t *referenceItem;
|
|
||||||
obs_source_t *fromSource;
|
|
||||||
obs_sceneitem_t *newItem;
|
|
||||||
};
|
|
||||||
|
|
||||||
static void DuplicateSceneItem(void *_data, obs_scene_t *scene) {
|
|
||||||
DuplicateSceneItemData *data = (DuplicateSceneItemData *)_data;
|
|
||||||
data->newItem = obs_scene_add(scene, data->fromSource);
|
|
||||||
obs_sceneitem_set_visible(data->newItem, obs_sceneitem_visible(data->referenceItem));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -544,9 +549,9 @@ static void DuplicateSceneItem(void *_data, obs_scene_t *scene) {
|
|||||||
*
|
*
|
||||||
* @param {String (optional)} `fromScene` Name of the scene to copy the item from. Defaults to the current scene.
|
* @param {String (optional)} `fromScene` Name of the scene to copy the item from. Defaults to the current scene.
|
||||||
* @param {String (optional)} `toScene` Name of the scene to create the item in. Defaults to the current scene.
|
* @param {String (optional)} `toScene` Name of the scene to create the item in. Defaults to the current scene.
|
||||||
* @param {Object} `item` item to duplicate (required)
|
* @param {Object} `item` Scene Item to duplicate from the source scene (required)
|
||||||
* @param {String} `item.name` name of the scene item (prefer `id`, including both is acceptable).
|
* @param {String} `item.name` Scene Item name (prefer `id`, including both is acceptable).
|
||||||
* @param {int} `item.id` id of the scene item.
|
* @param {int} `item.id` Scene Item ID.
|
||||||
*
|
*
|
||||||
* @return {String} `scene` Name of the scene where the new item was created
|
* @return {String} `scene` Name of the scene where the new item was created
|
||||||
* @return {Object} `item` New item info
|
* @return {Object} `item` New item info
|
||||||
@ -558,27 +563,33 @@ static void DuplicateSceneItem(void *_data, obs_scene_t *scene) {
|
|||||||
* @category scene items
|
* @category scene items
|
||||||
* @since 4.5.0
|
* @since 4.5.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleDuplicateSceneItem(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::DuplicateSceneItem(const RpcRequest& request) {
|
||||||
if (!req->hasField("item")) {
|
struct DuplicateSceneItemData {
|
||||||
return req->SendErrorResponse("missing request parameters");
|
obs_sceneitem_t *referenceItem;
|
||||||
|
obs_source_t *fromSource;
|
||||||
|
obs_sceneitem_t *newItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!request.hasField("item")) {
|
||||||
|
return request.failed("missing request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* fromSceneName = obs_data_get_string(req->data, "fromScene");
|
const char* fromSceneName = obs_data_get_string(request.parameters(), "fromScene");
|
||||||
OBSSourceAutoRelease fromScene = Utils::GetSceneFromNameOrCurrent(fromSceneName);
|
OBSScene fromScene = Utils::GetSceneFromNameOrCurrent(fromSceneName);
|
||||||
if (!fromScene) {
|
if (!fromScene) {
|
||||||
return req->SendErrorResponse("requested fromScene doesn't exist");
|
return request.failed("requested fromScene doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* toSceneName = obs_data_get_string(req->data, "toScene");
|
const char* toSceneName = obs_data_get_string(request.parameters(), "toScene");
|
||||||
OBSSourceAutoRelease toScene = Utils::GetSceneFromNameOrCurrent(toSceneName);
|
OBSScene toScene = Utils::GetSceneFromNameOrCurrent(toSceneName);
|
||||||
if (!toScene) {
|
if (!toScene) {
|
||||||
return req->SendErrorResponse("requested toScene doesn't exist");
|
return request.failed("requested toScene doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
OBSDataAutoRelease item = obs_data_get_obj(req->data, "item");
|
OBSDataItemAutoRelease itemField = obs_data_item_byname(request.parameters(), "item");
|
||||||
OBSSceneItemAutoRelease referenceItem = Utils::GetSceneItemFromItem(fromScene, item);
|
OBSSceneItemAutoRelease referenceItem = Utils::GetSceneItemFromRequestField(fromScene, itemField);
|
||||||
if (!referenceItem) {
|
if (!referenceItem) {
|
||||||
return req->SendErrorResponse("item with id/name combination not found in specified scene");
|
return request.failed("item with id/name combination not found in specified scene");
|
||||||
}
|
}
|
||||||
|
|
||||||
DuplicateSceneItemData data;
|
DuplicateSceneItemData data;
|
||||||
@ -586,12 +597,16 @@ HandlerResponse WSRequestHandler::HandleDuplicateSceneItem(WSRequestHandler* req
|
|||||||
data.referenceItem = referenceItem;
|
data.referenceItem = referenceItem;
|
||||||
|
|
||||||
obs_enter_graphics();
|
obs_enter_graphics();
|
||||||
obs_scene_atomic_update(obs_scene_from_source(toScene), DuplicateSceneItem, &data);
|
obs_scene_atomic_update(toScene, [](void *_data, obs_scene_t *scene) {
|
||||||
|
auto data = reinterpret_cast<DuplicateSceneItemData*>(_data);
|
||||||
|
data->newItem = obs_scene_add(scene, data->fromSource);
|
||||||
|
obs_sceneitem_set_visible(data->newItem, obs_sceneitem_visible(data->referenceItem));
|
||||||
|
}, &data);
|
||||||
obs_leave_graphics();
|
obs_leave_graphics();
|
||||||
|
|
||||||
obs_sceneitem_t *newItem = data.newItem;
|
obs_sceneitem_t *newItem = data.newItem;
|
||||||
if (!newItem) {
|
if (!newItem) {
|
||||||
return req->SendErrorResponse("Error duplicating scene item");
|
return request.failed("Error duplicating scene item");
|
||||||
}
|
}
|
||||||
|
|
||||||
OBSDataAutoRelease itemData = obs_data_create();
|
OBSDataAutoRelease itemData = obs_data_create();
|
||||||
@ -600,7 +615,7 @@ HandlerResponse WSRequestHandler::HandleDuplicateSceneItem(WSRequestHandler* req
|
|||||||
|
|
||||||
OBSDataAutoRelease responseData = obs_data_create();
|
OBSDataAutoRelease responseData = obs_data_create();
|
||||||
obs_data_set_obj(responseData, "item", itemData);
|
obs_data_set_obj(responseData, "item", itemData);
|
||||||
obs_data_set_string(responseData, "scene", obs_source_get_name(toScene));
|
obs_data_set_string(responseData, "scene", obs_source_get_name(obs_scene_get_source(toScene)));
|
||||||
|
|
||||||
return req->SendOKResponse(responseData);
|
return request.success(responseData);
|
||||||
}
|
}
|
||||||
|
@ -18,19 +18,19 @@
|
|||||||
* @category scenes
|
* @category scenes
|
||||||
* @since 0.3
|
* @since 0.3
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleSetCurrentScene(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SetCurrentScene(const RpcRequest& request) {
|
||||||
if (!req->hasField("scene-name")) {
|
if (!request.hasField("scene-name")) {
|
||||||
return req->SendErrorResponse("missing request parameters");
|
return request.failed("missing request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* sceneName = obs_data_get_string(req->data, "scene-name");
|
const char* sceneName = obs_data_get_string(request.parameters(), "scene-name");
|
||||||
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName);
|
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName);
|
||||||
|
|
||||||
if (source) {
|
if (source) {
|
||||||
obs_frontend_set_current_scene(source);
|
obs_frontend_set_current_scene(source);
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
} else {
|
} else {
|
||||||
return req->SendErrorResponse("requested scene does not exist");
|
return request.failed("requested scene does not exist");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ HandlerResponse WSRequestHandler::HandleSetCurrentScene(WSRequestHandler* req) {
|
|||||||
* @category scenes
|
* @category scenes
|
||||||
* @since 0.3
|
* @since 0.3
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetCurrentScene(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetCurrentScene(const RpcRequest& request) {
|
||||||
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
|
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
|
||||||
OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(currentScene);
|
OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(currentScene);
|
||||||
|
|
||||||
@ -53,21 +53,21 @@ HandlerResponse WSRequestHandler::HandleGetCurrentScene(WSRequestHandler* req) {
|
|||||||
obs_data_set_string(data, "name", obs_source_get_name(currentScene));
|
obs_data_set_string(data, "name", obs_source_get_name(currentScene));
|
||||||
obs_data_set_array(data, "sources", sceneItems);
|
obs_data_set_array(data, "sources", sceneItems);
|
||||||
|
|
||||||
return req->SendOKResponse(data);
|
return request.success(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a list of scenes in the currently active profile.
|
* Get a list of scenes in the currently active profile.
|
||||||
*
|
*
|
||||||
* @return {String} `current-scene` Name of the currently active scene.
|
* @return {String} `current-scene` Name of the currently active scene.
|
||||||
* @return {Array<Scene>} `scenes` Ordered list of the current profile's scenes (See `[GetCurrentScene](#getcurrentscene)` for more information).
|
* @return {Array<Scene>} `scenes` Ordered list of the current profile's scenes (See [GetCurrentScene](#getcurrentscene) for more information).
|
||||||
*
|
*
|
||||||
* @api requests
|
* @api requests
|
||||||
* @name GetSceneList
|
* @name GetSceneList
|
||||||
* @category scenes
|
* @category scenes
|
||||||
* @since 0.3
|
* @since 0.3
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetSceneList(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetSceneList(const RpcRequest& request) {
|
||||||
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
|
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
|
||||||
OBSDataArrayAutoRelease scenes = Utils::GetScenes();
|
OBSDataArrayAutoRelease scenes = Utils::GetScenes();
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ HandlerResponse WSRequestHandler::HandleGetSceneList(WSRequestHandler* req) {
|
|||||||
obs_source_get_name(currentScene));
|
obs_source_get_name(currentScene));
|
||||||
obs_data_set_array(data, "scenes", scenes);
|
obs_data_set_array(data, "scenes", scenes);
|
||||||
|
|
||||||
return req->SendOKResponse(data);
|
return request.success(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,50 +92,184 @@ HandlerResponse WSRequestHandler::HandleGetSceneList(WSRequestHandler* req) {
|
|||||||
* @category scenes
|
* @category scenes
|
||||||
* @since 4.5.0
|
* @since 4.5.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleReorderSceneItems(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::ReorderSceneItems(const RpcRequest& request) {
|
||||||
QString sceneName = obs_data_get_string(req->data, "scene");
|
QString sceneName = obs_data_get_string(request.parameters(), "scene");
|
||||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||||
if (!scene) {
|
if (!scene) {
|
||||||
return req->SendErrorResponse("requested scene doesn't exist");
|
return request.failed("requested scene doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
OBSDataArrayAutoRelease items = obs_data_get_array(req->data, "items");
|
OBSDataArrayAutoRelease items = obs_data_get_array(request.parameters(), "items");
|
||||||
if (!items) {
|
if (!items) {
|
||||||
return req->SendErrorResponse("sceneItem order not specified");
|
return request.failed("sceneItem order not specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t count = obs_data_array_count(items);
|
struct reorder_context {
|
||||||
|
obs_data_array_t* items;
|
||||||
|
bool success;
|
||||||
|
QString errorMessage;
|
||||||
|
};
|
||||||
|
|
||||||
std::vector<obs_sceneitem_t*> newOrder;
|
struct reorder_context ctx;
|
||||||
newOrder.reserve(count);
|
ctx.success = false;
|
||||||
|
ctx.items = items;
|
||||||
|
|
||||||
for (size_t i = 0; i < count; ++i) {
|
obs_scene_atomic_update(scene, [](void* param, obs_scene_t* scene) {
|
||||||
OBSDataAutoRelease item = obs_data_array_item(items, i);
|
auto ctx = reinterpret_cast<struct reorder_context*>(param);
|
||||||
|
|
||||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item);
|
QVector<struct obs_sceneitem_order_info> orderList;
|
||||||
obs_sceneitem_release(sceneItem); // ref dec
|
struct obs_sceneitem_order_info info;
|
||||||
|
|
||||||
if (!sceneItem) {
|
size_t itemCount = obs_data_array_count(ctx->items);
|
||||||
return req->SendErrorResponse("Invalid sceneItem id or name specified");
|
for (uint i = 0; i < itemCount; i++) {
|
||||||
}
|
OBSDataAutoRelease item = obs_data_array_item(ctx->items, i);
|
||||||
|
|
||||||
for (size_t j = 0; j <= i; ++j) {
|
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item);
|
||||||
if (sceneItem == newOrder[j]) {
|
if (!sceneItem) {
|
||||||
return req->SendErrorResponse("Duplicate sceneItem in specified order");
|
ctx->success = false;
|
||||||
|
ctx->errorMessage = "Invalid sceneItem id or name specified";
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info.group = nullptr;
|
||||||
|
info.item = sceneItem;
|
||||||
|
orderList.insert(0, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
newOrder.push_back(sceneItem);
|
ctx->success = obs_scene_reorder_items2(scene, orderList.data(), orderList.size());
|
||||||
|
if (!ctx->success) {
|
||||||
|
ctx->errorMessage = "Invalid sceneItem order";
|
||||||
|
}
|
||||||
|
}, &ctx);
|
||||||
|
|
||||||
|
if (!ctx.success) {
|
||||||
|
return request.failed(ctx.errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = obs_scene_reorder_items(obs_scene_from_source(scene), newOrder.data(), count);
|
return request.success();
|
||||||
if (!success) {
|
}
|
||||||
return req->SendErrorResponse("Invalid sceneItem order");
|
|
||||||
}
|
/**
|
||||||
|
* Set a scene to use a specific transition override.
|
||||||
for (auto const& item: newOrder) {
|
*
|
||||||
obs_sceneitem_release(item);
|
* @param {String} `sceneName` Name of the scene to switch to.
|
||||||
}
|
* @param {String} `transitionName` Name of the transition to use.
|
||||||
|
* @param {int (Optional)} `transitionDuration` Duration in milliseconds of the transition if transition is not fixed. Defaults to the current duration specified in the UI if there is no current override and this value is not given.
|
||||||
return req->SendOKResponse();
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name SetSceneTransitionOverride
|
||||||
|
* @category scenes
|
||||||
|
* @since 4.9.0
|
||||||
|
*/
|
||||||
|
RpcResponse WSRequestHandler::SetSceneTransitionOverride(const RpcRequest& request) {
|
||||||
|
if (!request.hasField("sceneName") || !request.hasField("transitionName")) {
|
||||||
|
return request.failed("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString sceneName = obs_data_get_string(request.parameters(), "sceneName");
|
||||||
|
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8());
|
||||||
|
if (!source) {
|
||||||
|
return request.failed("requested scene does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
enum obs_source_type sourceType = obs_source_get_type(source);
|
||||||
|
if (sourceType != OBS_SOURCE_TYPE_SCENE) {
|
||||||
|
return request.failed("requested scene is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString transitionName = obs_data_get_string(request.parameters(), "transitionName");
|
||||||
|
if (!Utils::GetTransitionFromName(transitionName)) {
|
||||||
|
return request.failed("requested transition does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataAutoRelease sourceData = obs_source_get_private_settings(source);
|
||||||
|
obs_data_set_string(sourceData, "transition", transitionName.toUtf8().constData());
|
||||||
|
|
||||||
|
if (request.hasField("transitionDuration")) {
|
||||||
|
int transitionOverrideDuration = obs_data_get_int(request.parameters(), "transitionDuration");
|
||||||
|
obs_data_set_int(sourceData, "transition_duration", transitionOverrideDuration);
|
||||||
|
} else if(!obs_data_has_user_value(sourceData, "transition_duration")) {
|
||||||
|
obs_data_set_int(sourceData, "transition_duration",
|
||||||
|
obs_frontend_get_transition_duration()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return request.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove any transition override on a scene.
|
||||||
|
*
|
||||||
|
* @param {String} `sceneName` Name of the scene to switch to.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name RemoveSceneTransitionOverride
|
||||||
|
* @category scenes
|
||||||
|
* @since 4.9.0
|
||||||
|
*/
|
||||||
|
RpcResponse WSRequestHandler::RemoveSceneTransitionOverride(const RpcRequest& request) {
|
||||||
|
if (!request.hasField("sceneName")) {
|
||||||
|
return request.failed("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString sceneName = obs_data_get_string(request.parameters(), "sceneName");
|
||||||
|
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8());
|
||||||
|
if (!source) {
|
||||||
|
return request.failed("requested scene does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
enum obs_source_type sourceType = obs_source_get_type(source);
|
||||||
|
if (sourceType != OBS_SOURCE_TYPE_SCENE) {
|
||||||
|
return request.failed("requested scene is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataAutoRelease sourceData = obs_source_get_private_settings(source);
|
||||||
|
obs_data_erase(sourceData, "transition");
|
||||||
|
obs_data_erase(sourceData, "transition_duration");
|
||||||
|
|
||||||
|
return request.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current scene transition override.
|
||||||
|
*
|
||||||
|
* @param {String} `sceneName` Name of the scene to switch to.
|
||||||
|
*
|
||||||
|
* @return {String} `transitionName` Name of the current overriding transition. Empty string if no override is set.
|
||||||
|
* @return {int} `transitionDuration` Transition duration. `-1` if no override is set.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetSceneTransitionOverride
|
||||||
|
* @category scenes
|
||||||
|
* @since 4.9.0
|
||||||
|
*/
|
||||||
|
RpcResponse WSRequestHandler::GetSceneTransitionOverride(const RpcRequest& request) {
|
||||||
|
if (!request.hasField("sceneName")) {
|
||||||
|
return request.failed("missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString sceneName = obs_data_get_string(request.parameters(), "sceneName");
|
||||||
|
OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8());
|
||||||
|
if (!source) {
|
||||||
|
return request.failed("requested scene does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
enum obs_source_type sourceType = obs_source_get_type(source);
|
||||||
|
if (sourceType != OBS_SOURCE_TYPE_SCENE) {
|
||||||
|
return request.failed("requested scene is invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataAutoRelease sourceData = obs_source_get_private_settings(source);
|
||||||
|
const char* transitionOverrideName = obs_data_get_string(sourceData, "transition");
|
||||||
|
|
||||||
|
bool hasDurationOverride = obs_data_has_user_value(sourceData, "transition_duration");
|
||||||
|
int transitionOverrideDuration = obs_data_get_int(sourceData, "transition_duration");
|
||||||
|
|
||||||
|
OBSDataAutoRelease fields = obs_data_create();
|
||||||
|
obs_data_set_string(fields, "transitionName", transitionOverrideName);
|
||||||
|
obs_data_set_int(fields, "transitionDuration",
|
||||||
|
(hasDurationOverride ? transitionOverrideDuration : -1)
|
||||||
|
);
|
||||||
|
|
||||||
|
return request.success(fields);
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -20,28 +20,26 @@
|
|||||||
* @category streaming
|
* @category streaming
|
||||||
* @since 0.3
|
* @since 0.3
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetStreamingStatus(const RpcRequest& request) {
|
||||||
auto events = GetEventsSystem();
|
auto events = GetEventsSystem();
|
||||||
|
|
||||||
OBSDataAutoRelease data = obs_data_create();
|
OBSDataAutoRelease data = obs_data_create();
|
||||||
obs_data_set_bool(data, "streaming", obs_frontend_streaming_active());
|
obs_data_set_bool(data, "streaming", obs_frontend_streaming_active());
|
||||||
obs_data_set_bool(data, "recording", obs_frontend_recording_active());
|
obs_data_set_bool(data, "recording", obs_frontend_recording_active());
|
||||||
|
obs_data_set_bool(data, "recording-paused", obs_frontend_recording_paused());
|
||||||
obs_data_set_bool(data, "preview-only", false);
|
obs_data_set_bool(data, "preview-only", false);
|
||||||
|
|
||||||
const char* tc = nullptr;
|
|
||||||
if (obs_frontend_streaming_active()) {
|
if (obs_frontend_streaming_active()) {
|
||||||
tc = events->GetStreamingTimecode();
|
QString streamingTimecode = events->getStreamingTimecode();
|
||||||
obs_data_set_string(data, "stream-timecode", tc);
|
obs_data_set_string(data, "stream-timecode", streamingTimecode.toUtf8().constData());
|
||||||
bfree((void*)tc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (obs_frontend_recording_active()) {
|
if (obs_frontend_recording_active()) {
|
||||||
tc = events->GetRecordingTimecode();
|
QString recordingTimecode = events->getRecordingTimecode();
|
||||||
obs_data_set_string(data, "rec-timecode", tc);
|
obs_data_set_string(data, "rec-timecode", recordingTimecode.toUtf8().constData());
|
||||||
bfree((void*)tc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return req->SendOKResponse(data);
|
return request.success(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,11 +50,11 @@ HandlerResponse WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler* req
|
|||||||
* @category streaming
|
* @category streaming
|
||||||
* @since 0.3
|
* @since 0.3
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleStartStopStreaming(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::StartStopStreaming(const RpcRequest& request) {
|
||||||
if (obs_frontend_streaming_active())
|
if (obs_frontend_streaming_active())
|
||||||
return HandleStopStreaming(req);
|
return StopStreaming(request);
|
||||||
else
|
else
|
||||||
return HandleStartStreaming(req);
|
return StartStreaming(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -69,24 +67,24 @@ HandlerResponse WSRequestHandler::HandleStartStopStreaming(WSRequestHandler* req
|
|||||||
* @param {Object (optional)} `stream.settings` Settings for the stream.
|
* @param {Object (optional)} `stream.settings` Settings for the stream.
|
||||||
* @param {String (optional)} `stream.settings.server` The publish URL.
|
* @param {String (optional)} `stream.settings.server` The publish URL.
|
||||||
* @param {String (optional)} `stream.settings.key` The publish key of the stream.
|
* @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 {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.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`.
|
* @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
|
* @api requests
|
||||||
* @name StartStreaming
|
* @name StartStreaming
|
||||||
* @category streaming
|
* @category streaming
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleStartStreaming(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::StartStreaming(const RpcRequest& request) {
|
||||||
if (obs_frontend_streaming_active() == false) {
|
if (obs_frontend_streaming_active() == false) {
|
||||||
OBSService configuredService = obs_frontend_get_streaming_service();
|
OBSService configuredService = obs_frontend_get_streaming_service();
|
||||||
OBSService newService = nullptr;
|
OBSService newService = nullptr;
|
||||||
|
|
||||||
// TODO: fix service memory leak
|
// TODO: fix service memory leak
|
||||||
|
|
||||||
if (req->hasField("stream")) {
|
if (request.hasField("stream")) {
|
||||||
OBSDataAutoRelease streamData = obs_data_get_obj(req->data, "stream");
|
OBSDataAutoRelease streamData = obs_data_get_obj(request.parameters(), "stream");
|
||||||
OBSDataAutoRelease newSettings = obs_data_get_obj(streamData, "settings");
|
OBSDataAutoRelease newSettings = obs_data_get_obj(streamData, "settings");
|
||||||
OBSDataAutoRelease newMetadata = obs_data_get_obj(streamData, "metadata");
|
OBSDataAutoRelease newMetadata = obs_data_get_obj(streamData, "metadata");
|
||||||
|
|
||||||
@ -105,10 +103,10 @@ HandlerResponse WSRequestHandler::HandleStartStreaming(WSRequestHandler* req) {
|
|||||||
&& obs_data_has_user_value(newSettings, "key"))
|
&& obs_data_has_user_value(newSettings, "key"))
|
||||||
{
|
{
|
||||||
const char* key = obs_data_get_string(newSettings, "key");
|
const char* key = obs_data_get_string(newSettings, "key");
|
||||||
int keylen = strlen(key);
|
size_t keylen = strlen(key);
|
||||||
|
|
||||||
bool hasQuestionMark = false;
|
bool hasQuestionMark = false;
|
||||||
for (int i = 0; i < keylen; i++) {
|
for (size_t i = 0; i < keylen; i++) {
|
||||||
if (key[i] == '?') {
|
if (key[i] == '?') {
|
||||||
hasQuestionMark = true;
|
hasQuestionMark = true;
|
||||||
break;
|
break;
|
||||||
@ -159,9 +157,9 @@ HandlerResponse WSRequestHandler::HandleStartStreaming(WSRequestHandler* req) {
|
|||||||
obs_frontend_set_streaming_service(configuredService);
|
obs_frontend_set_streaming_service(configuredService);
|
||||||
}
|
}
|
||||||
|
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
} else {
|
} else {
|
||||||
return req->SendErrorResponse("streaming already active");
|
return request.failed("streaming already active");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,12 +172,12 @@ HandlerResponse WSRequestHandler::HandleStartStreaming(WSRequestHandler* req) {
|
|||||||
* @category streaming
|
* @category streaming
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleStopStreaming(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::StopStreaming(const RpcRequest& request) {
|
||||||
if (obs_frontend_streaming_active() == true) {
|
if (obs_frontend_streaming_active() == true) {
|
||||||
obs_frontend_streaming_stop();
|
obs_frontend_streaming_stop();
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
} else {
|
} else {
|
||||||
return req->SendErrorResponse("streaming not active");
|
return request.failed("streaming not active");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +188,7 @@ HandlerResponse WSRequestHandler::HandleStopStreaming(WSRequestHandler* req) {
|
|||||||
* @param {Object} `settings` The actual settings of the stream.
|
* @param {Object} `settings` The actual settings of the stream.
|
||||||
* @param {String (optional)} `settings.server` The publish URL.
|
* @param {String (optional)} `settings.server` The publish URL.
|
||||||
* @param {String (optional)} `settings.key` The publish key.
|
* @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 {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.username` The username for the streaming service.
|
||||||
* @param {String (optional)} `settings.password` The password for the streaming service.
|
* @param {String (optional)} `settings.password` The password for the streaming service.
|
||||||
* @param {boolean} `save` Persist the settings to disk.
|
* @param {boolean} `save` Persist the settings to disk.
|
||||||
@ -200,21 +198,22 @@ HandlerResponse WSRequestHandler::HandleStopStreaming(WSRequestHandler* req) {
|
|||||||
* @category streaming
|
* @category streaming
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleSetStreamSettings(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SetStreamSettings(const RpcRequest& request) {
|
||||||
OBSService service = obs_frontend_get_streaming_service();
|
OBSService service = obs_frontend_get_streaming_service();
|
||||||
|
|
||||||
OBSDataAutoRelease requestSettings = obs_data_get_obj(req->data, "settings");
|
OBSDataAutoRelease requestSettings = obs_data_get_obj(request.parameters(), "settings");
|
||||||
if (!requestSettings) {
|
if (!requestSettings) {
|
||||||
return req->SendErrorResponse("'settings' are required'");
|
return request.failed("'settings' are required'");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString serviceType = obs_service_get_type(service);
|
QString serviceType = obs_service_get_type(service);
|
||||||
QString requestedType = obs_data_get_string(req->data, "type");
|
QString requestedType = obs_data_get_string(request.parameters(), "type");
|
||||||
|
|
||||||
if (requestedType != nullptr && requestedType != serviceType) {
|
if (requestedType != nullptr && requestedType != serviceType) {
|
||||||
OBSDataAutoRelease hotkeys = obs_hotkeys_save_service(service);
|
OBSDataAutoRelease hotkeys = obs_hotkeys_save_service(service);
|
||||||
service = obs_service_create(
|
service = obs_service_create(
|
||||||
requestedType.toUtf8(), STREAM_SERVICE_ID, requestSettings, hotkeys);
|
requestedType.toUtf8(), STREAM_SERVICE_ID, requestSettings, hotkeys);
|
||||||
|
obs_frontend_set_streaming_service(service);
|
||||||
} else {
|
} else {
|
||||||
// If type isn't changing, we should overlay the settings we got
|
// 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
|
// to the existing settings. By doing so, you can send a request that
|
||||||
@ -233,17 +232,19 @@ HandlerResponse WSRequestHandler::HandleSetStreamSettings(WSRequestHandler* req)
|
|||||||
}
|
}
|
||||||
|
|
||||||
//if save is specified we should immediately save the streaming service
|
//if save is specified we should immediately save the streaming service
|
||||||
if (obs_data_get_bool(req->data, "save")) {
|
if (obs_data_get_bool(request.parameters(), "save")) {
|
||||||
obs_frontend_save_streaming_service();
|
obs_frontend_save_streaming_service();
|
||||||
}
|
}
|
||||||
|
|
||||||
OBSDataAutoRelease serviceSettings = obs_service_get_settings(service);
|
OBSService responseService = obs_frontend_get_streaming_service();
|
||||||
|
OBSDataAutoRelease serviceSettings = obs_service_get_settings(responseService);
|
||||||
|
const char* responseType = obs_service_get_type(responseService);
|
||||||
|
|
||||||
OBSDataAutoRelease response = obs_data_create();
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
obs_data_set_string(response, "type", requestedType.toUtf8());
|
obs_data_set_string(response, "type", responseType);
|
||||||
obs_data_set_obj(response, "settings", serviceSettings);
|
obs_data_set_obj(response, "settings", serviceSettings);
|
||||||
|
|
||||||
return req->SendOKResponse(response);
|
return request.success(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -253,16 +254,16 @@ HandlerResponse WSRequestHandler::HandleSetStreamSettings(WSRequestHandler* req)
|
|||||||
* @return {Object} `settings` Stream settings object.
|
* @return {Object} `settings` Stream settings object.
|
||||||
* @return {String} `settings.server` The publish URL.
|
* @return {String} `settings.server` The publish URL.
|
||||||
* @return {String} `settings.key` The publish key of the stream.
|
* @return {String} `settings.key` The publish key of the stream.
|
||||||
* @return {boolean} `settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.
|
* @return {boolean} `settings.use_auth` Indicates whether authentication 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.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`.
|
* @return {String} `settings.password` The password to use when accessing the streaming server. Only present if `use_auth` is `true`.
|
||||||
*
|
*
|
||||||
* @api requests
|
* @api requests
|
||||||
* @name GetStreamSettings
|
* @name GetStreamSettings
|
||||||
* @category streaming
|
* @category streaming
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetStreamSettings(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetStreamSettings(const RpcRequest& request) {
|
||||||
OBSService service = obs_frontend_get_streaming_service();
|
OBSService service = obs_frontend_get_streaming_service();
|
||||||
|
|
||||||
const char* serviceType = obs_service_get_type(service);
|
const char* serviceType = obs_service_get_type(service);
|
||||||
@ -272,7 +273,7 @@ HandlerResponse WSRequestHandler::HandleGetStreamSettings(WSRequestHandler* req)
|
|||||||
obs_data_set_string(response, "type", serviceType);
|
obs_data_set_string(response, "type", serviceType);
|
||||||
obs_data_set_obj(response, "settings", settings);
|
obs_data_set_obj(response, "settings", settings);
|
||||||
|
|
||||||
return req->SendOKResponse(response);
|
return request.success(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -283,9 +284,9 @@ HandlerResponse WSRequestHandler::HandleGetStreamSettings(WSRequestHandler* req)
|
|||||||
* @category streaming
|
* @category streaming
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleSaveStreamSettings(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SaveStreamSettings(const RpcRequest& request) {
|
||||||
obs_frontend_save_streaming_service();
|
obs_frontend_save_streaming_service();
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -301,18 +302,19 @@ HandlerResponse WSRequestHandler::HandleSaveStreamSettings(WSRequestHandler* req
|
|||||||
* @since 4.6.0
|
* @since 4.6.0
|
||||||
*/
|
*/
|
||||||
#if BUILD_CAPTIONS
|
#if BUILD_CAPTIONS
|
||||||
HandlerResponse WSRequestHandler::HandleSendCaptions(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SendCaptions(const RpcRequest& request) {
|
||||||
if (!req->hasField("text")) {
|
if (!request.hasField("text")) {
|
||||||
return req->SendErrorResponse("missing request parameters");
|
return request.failed("missing request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
OBSOutputAutoRelease output = obs_frontend_get_streaming_output();
|
OBSOutputAutoRelease output = obs_frontend_get_streaming_output();
|
||||||
if (output) {
|
if (output) {
|
||||||
const char* caption = obs_data_get_string(req->data, "text");
|
const char* caption = obs_data_get_string(request.parameters(), "text");
|
||||||
obs_output_output_caption_text1(output, caption);
|
// Send caption text with immediately (0 second delay)
|
||||||
|
obs_output_output_caption_text2(output, caption, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -12,13 +12,13 @@
|
|||||||
* @category studio mode
|
* @category studio mode
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetStudioModeStatus(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetStudioModeStatus(const RpcRequest& request) {
|
||||||
bool previewActive = obs_frontend_preview_program_mode_active();
|
bool previewActive = obs_frontend_preview_program_mode_active();
|
||||||
|
|
||||||
OBSDataAutoRelease response = obs_data_create();
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
obs_data_set_bool(response, "studio-mode", previewActive);
|
obs_data_set_bool(response, "studio-mode", previewActive);
|
||||||
|
|
||||||
return req->SendOKResponse(response);
|
return request.success(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,9 +33,9 @@ HandlerResponse WSRequestHandler::HandleGetStudioModeStatus(WSRequestHandler* re
|
|||||||
* @category studio mode
|
* @category studio mode
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetPreviewScene(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetPreviewScene(const RpcRequest& request) {
|
||||||
if (!obs_frontend_preview_program_mode_active()) {
|
if (!obs_frontend_preview_program_mode_active()) {
|
||||||
return req->SendErrorResponse("studio mode not enabled");
|
return request.failed("studio mode not enabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
OBSSourceAutoRelease scene = obs_frontend_get_current_preview_scene();
|
OBSSourceAutoRelease scene = obs_frontend_get_current_preview_scene();
|
||||||
@ -45,7 +45,7 @@ HandlerResponse WSRequestHandler::HandleGetPreviewScene(WSRequestHandler* req) {
|
|||||||
obs_data_set_string(data, "name", obs_source_get_name(scene));
|
obs_data_set_string(data, "name", obs_source_get_name(scene));
|
||||||
obs_data_set_array(data, "sources", sceneItems);
|
obs_data_set_array(data, "sources", sceneItems);
|
||||||
|
|
||||||
return req->SendOKResponse(data);
|
return request.success(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -59,23 +59,23 @@ HandlerResponse WSRequestHandler::HandleGetPreviewScene(WSRequestHandler* req) {
|
|||||||
* @category studio mode
|
* @category studio mode
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleSetPreviewScene(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SetPreviewScene(const RpcRequest& request) {
|
||||||
if (!obs_frontend_preview_program_mode_active()) {
|
if (!obs_frontend_preview_program_mode_active()) {
|
||||||
return req->SendErrorResponse("studio mode not enabled");
|
return request.failed("studio mode not enabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!req->hasField("scene-name")) {
|
if (!request.hasField("scene-name")) {
|
||||||
return req->SendErrorResponse("missing request parameters");
|
return request.failed("missing request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* scene_name = obs_data_get_string(req->data, "scene-name");
|
const char* scene_name = obs_data_get_string(request.parameters(), "scene-name");
|
||||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(scene_name);
|
OBSScene scene = Utils::GetSceneFromNameOrCurrent(scene_name);
|
||||||
if (!scene) {
|
if (!scene) {
|
||||||
return req->SendErrorResponse("specified scene doesn't exist");
|
return request.failed("specified scene doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
obs_frontend_set_current_preview_scene(scene);
|
obs_frontend_set_current_preview_scene(obs_scene_get_source(scene));
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,37 +91,37 @@ HandlerResponse WSRequestHandler::HandleSetPreviewScene(WSRequestHandler* req) {
|
|||||||
* @category studio mode
|
* @category studio mode
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleTransitionToProgram(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::TransitionToProgram(const RpcRequest& request) {
|
||||||
if (!obs_frontend_preview_program_mode_active()) {
|
if (!obs_frontend_preview_program_mode_active()) {
|
||||||
return req->SendErrorResponse("studio mode not enabled");
|
return request.failed("studio mode not enabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req->hasField("with-transition")) {
|
if (request.hasField("with-transition")) {
|
||||||
OBSDataAutoRelease transitionInfo =
|
OBSDataAutoRelease transitionInfo =
|
||||||
obs_data_get_obj(req->data, "with-transition");
|
obs_data_get_obj(request.parameters(), "with-transition");
|
||||||
|
|
||||||
if (obs_data_has_user_value(transitionInfo, "name")) {
|
if (obs_data_has_user_value(transitionInfo, "name")) {
|
||||||
QString transitionName =
|
QString transitionName =
|
||||||
obs_data_get_string(transitionInfo, "name");
|
obs_data_get_string(transitionInfo, "name");
|
||||||
if (transitionName.isEmpty()) {
|
if (transitionName.isEmpty()) {
|
||||||
return req->SendErrorResponse("invalid request parameters");
|
return request.failed("invalid request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = Utils::SetTransitionByName(transitionName);
|
bool success = Utils::SetTransitionByName(transitionName);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
return req->SendErrorResponse("specified transition doesn't exist");
|
return request.failed("specified transition doesn't exist");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (obs_data_has_user_value(transitionInfo, "duration")) {
|
if (obs_data_has_user_value(transitionInfo, "duration")) {
|
||||||
int transitionDuration =
|
int transitionDuration =
|
||||||
obs_data_get_int(transitionInfo, "duration");
|
obs_data_get_int(transitionInfo, "duration");
|
||||||
Utils::SetTransitionDuration(transitionDuration);
|
obs_frontend_set_transition_duration(transitionDuration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Utils::TransitionToProgram();
|
obs_frontend_preview_program_trigger_transition();
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -132,9 +132,16 @@ HandlerResponse WSRequestHandler::HandleTransitionToProgram(WSRequestHandler* re
|
|||||||
* @category studio mode
|
* @category studio mode
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleEnableStudioMode(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::EnableStudioMode(const RpcRequest& request) {
|
||||||
obs_frontend_set_preview_program_mode(true);
|
if (obs_frontend_preview_program_mode_active()) {
|
||||||
return req->SendOKResponse();
|
return request.failed("studio mode already active");
|
||||||
|
}
|
||||||
|
obs_queue_task(OBS_TASK_UI, [](void* param) {
|
||||||
|
obs_frontend_set_preview_program_mode(true);
|
||||||
|
|
||||||
|
UNUSED_PARAMETER(param);
|
||||||
|
}, nullptr, true);
|
||||||
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -145,9 +152,17 @@ HandlerResponse WSRequestHandler::HandleEnableStudioMode(WSRequestHandler* req)
|
|||||||
* @category studio mode
|
* @category studio mode
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleDisableStudioMode(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::DisableStudioMode(const RpcRequest& request) {
|
||||||
obs_frontend_set_preview_program_mode(false);
|
if (!obs_frontend_preview_program_mode_active()) {
|
||||||
return req->SendOKResponse();
|
return request.failed("studio mode not active");
|
||||||
|
}
|
||||||
|
obs_queue_task(OBS_TASK_UI, [](void* param) {
|
||||||
|
obs_frontend_set_preview_program_mode(false);
|
||||||
|
|
||||||
|
UNUSED_PARAMETER(param);
|
||||||
|
}, nullptr, true);
|
||||||
|
|
||||||
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -158,8 +173,13 @@ HandlerResponse WSRequestHandler::HandleDisableStudioMode(WSRequestHandler* req)
|
|||||||
* @category studio mode
|
* @category studio mode
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleToggleStudioMode(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::ToggleStudioMode(const RpcRequest& request) {
|
||||||
bool previewProgramMode = obs_frontend_preview_program_mode_active();
|
obs_queue_task(OBS_TASK_UI, [](void* param) {
|
||||||
obs_frontend_set_preview_program_mode(!previewProgramMode);
|
bool previewProgramMode = obs_frontend_preview_program_mode_active();
|
||||||
return req->SendOKResponse();
|
obs_frontend_set_preview_program_mode(!previewProgramMode);
|
||||||
|
|
||||||
|
UNUSED_PARAMETER(param);
|
||||||
|
}, nullptr, true);
|
||||||
|
|
||||||
|
return request.success();
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
* @category transitions
|
* @category transitions
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetTransitionList(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetTransitionList(const RpcRequest& request) {
|
||||||
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
|
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
|
||||||
obs_frontend_source_list transitionList = {};
|
obs_frontend_source_list transitionList = {};
|
||||||
obs_frontend_get_transitions(&transitionList);
|
obs_frontend_get_transitions(&transitionList);
|
||||||
@ -34,7 +34,7 @@ HandlerResponse WSRequestHandler::HandleGetTransitionList(WSRequestHandler* req)
|
|||||||
obs_source_get_name(currentTransition));
|
obs_source_get_name(currentTransition));
|
||||||
obs_data_set_array(response, "transitions", transitions);
|
obs_data_set_array(response, "transitions", transitions);
|
||||||
|
|
||||||
return req->SendOKResponse(response);
|
return request.success(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -48,7 +48,7 @@ HandlerResponse WSRequestHandler::HandleGetTransitionList(WSRequestHandler* req)
|
|||||||
* @category transitions
|
* @category transitions
|
||||||
* @since 0.3
|
* @since 0.3
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetCurrentTransition(const RpcRequest& request) {
|
||||||
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
|
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
|
||||||
|
|
||||||
OBSDataAutoRelease response = obs_data_create();
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
@ -56,9 +56,9 @@ HandlerResponse WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler* r
|
|||||||
obs_source_get_name(currentTransition));
|
obs_source_get_name(currentTransition));
|
||||||
|
|
||||||
if (!obs_transition_fixed(currentTransition))
|
if (!obs_transition_fixed(currentTransition))
|
||||||
obs_data_set_int(response, "duration", Utils::GetTransitionDuration());
|
obs_data_set_int(response, "duration", obs_frontend_get_transition_duration());
|
||||||
|
|
||||||
return req->SendOKResponse(response);
|
return request.success(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -71,18 +71,18 @@ HandlerResponse WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler* r
|
|||||||
* @category transitions
|
* @category transitions
|
||||||
* @since 0.3
|
* @since 0.3
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleSetCurrentTransition(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SetCurrentTransition(const RpcRequest& request) {
|
||||||
if (!req->hasField("transition-name")) {
|
if (!request.hasField("transition-name")) {
|
||||||
return req->SendErrorResponse("missing request parameters");
|
return request.failed("missing request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString name = obs_data_get_string(req->data, "transition-name");
|
QString name = obs_data_get_string(request.parameters(), "transition-name");
|
||||||
bool success = Utils::SetTransitionByName(name);
|
bool success = Utils::SetTransitionByName(name);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
return req->SendErrorResponse("requested transition does not exist");
|
return request.failed("requested transition does not exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -95,14 +95,14 @@ HandlerResponse WSRequestHandler::HandleSetCurrentTransition(WSRequestHandler* r
|
|||||||
* @category transitions
|
* @category transitions
|
||||||
* @since 4.0.0
|
* @since 4.0.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleSetTransitionDuration(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::SetTransitionDuration(const RpcRequest& request) {
|
||||||
if (!req->hasField("duration")) {
|
if (!request.hasField("duration")) {
|
||||||
return req->SendErrorResponse("missing request parameters");
|
return request.failed("missing request parameters");
|
||||||
}
|
}
|
||||||
|
|
||||||
int ms = obs_data_get_int(req->data, "duration");
|
int ms = obs_data_get_int(request.parameters(), "duration");
|
||||||
Utils::SetTransitionDuration(ms);
|
obs_frontend_set_transition_duration(ms);
|
||||||
return req->SendOKResponse();
|
return request.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -115,8 +115,27 @@ HandlerResponse WSRequestHandler::HandleSetTransitionDuration(WSRequestHandler*
|
|||||||
* @category transitions
|
* @category transitions
|
||||||
* @since 4.1.0
|
* @since 4.1.0
|
||||||
*/
|
*/
|
||||||
HandlerResponse WSRequestHandler::HandleGetTransitionDuration(WSRequestHandler* req) {
|
RpcResponse WSRequestHandler::GetTransitionDuration(const RpcRequest& request) {
|
||||||
OBSDataAutoRelease response = obs_data_create();
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
obs_data_set_int(response, "transition-duration", Utils::GetTransitionDuration());
|
obs_data_set_int(response, "transition-duration", obs_frontend_get_transition_duration());
|
||||||
return req->SendOKResponse(response);
|
return request.success(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the position of the current transition.
|
||||||
|
*
|
||||||
|
* @return {double} `position` current transition position. This value will be between 0.0 and 1.0. Note: Transition returns 1.0 when not active.
|
||||||
|
*
|
||||||
|
* @api requests
|
||||||
|
* @name GetTransitionPosition
|
||||||
|
* @category transitions
|
||||||
|
* @since 4.8.0
|
||||||
|
*/
|
||||||
|
RpcResponse WSRequestHandler::GetTransitionPosition(const RpcRequest& request) {
|
||||||
|
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
|
||||||
|
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
obs_data_set_double(response, "position", obs_transition_get_time(currentTransition));
|
||||||
|
|
||||||
|
return request.success(response);
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
|||||||
#include "obs-websocket.h"
|
#include "obs-websocket.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
#include "protocol/OBSRemoteProtocol.h"
|
||||||
|
|
||||||
QT_USE_NAMESPACE
|
QT_USE_NAMESPACE
|
||||||
|
|
||||||
@ -43,6 +44,7 @@ WSServer::WSServer()
|
|||||||
_connections(),
|
_connections(),
|
||||||
_clMutex(QMutex::Recursive)
|
_clMutex(QMutex::Recursive)
|
||||||
{
|
{
|
||||||
|
_server.get_alog().clear_channels(websocketpp::log::alevel::frame_header | websocketpp::log::alevel::frame_payload | websocketpp::log::alevel::control);
|
||||||
_server.init_asio();
|
_server.init_asio();
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
_server.set_reuse_addr(true);
|
_server.set_reuse_addr(true);
|
||||||
@ -82,7 +84,7 @@ void WSServer::start(quint16 port)
|
|||||||
|
|
||||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||||
QString errorTitle = tr("OBSWebsocket.Server.StartFailed.Title");
|
QString errorTitle = tr("OBSWebsocket.Server.StartFailed.Title");
|
||||||
QString errorMessage = tr("OBSWebsocket.Server.StartFailed.Message").arg(_serverPort);
|
QString errorMessage = tr("OBSWebsocket.Server.StartFailed.Message").arg(_serverPort).arg(errorCodeMessage.c_str());
|
||||||
obs_frontend_pop_ui_translation();
|
obs_frontend_pop_ui_translation();
|
||||||
|
|
||||||
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window());
|
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window());
|
||||||
@ -124,8 +126,15 @@ void WSServer::stop()
|
|||||||
blog(LOG_INFO, "server stopped successfully");
|
blog(LOG_INFO, "server stopped successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
void WSServer::broadcast(std::string message)
|
void WSServer::broadcast(const RpcEvent& event)
|
||||||
{
|
{
|
||||||
|
OBSRemoteProtocol protocol;
|
||||||
|
std::string message = protocol.encodeEvent(event);
|
||||||
|
|
||||||
|
if (GetConfig()->DebugEnabled) {
|
||||||
|
blog(LOG_INFO, "Update << '%s'", message.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
QMutexLocker locker(&_clMutex);
|
QMutexLocker locker(&_clMutex);
|
||||||
for (connection_hdl hdl : _connections) {
|
for (connection_hdl hdl : _connections) {
|
||||||
if (GetConfig()->AuthRequired) {
|
if (GetConfig()->AuthRequired) {
|
||||||
@ -134,7 +143,15 @@ void WSServer::broadcast(std::string message)
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_server.send(hdl, message, websocketpp::frame::opcode::text);
|
|
||||||
|
websocketpp::lib::error_code errorCode;
|
||||||
|
_server.send(hdl, message, websocketpp::frame::opcode::text, errorCode);
|
||||||
|
|
||||||
|
if (errorCode) {
|
||||||
|
std::string errorCodeMessage = errorCode.message();
|
||||||
|
blog(LOG_INFO, "server(broadcast): send failed: %s",
|
||||||
|
errorCodeMessage.c_str());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,10 +180,26 @@ void WSServer::onMessage(connection_hdl hdl, server::message_ptr message)
|
|||||||
ConnectionProperties& connProperties = _connectionProperties[hdl];
|
ConnectionProperties& connProperties = _connectionProperties[hdl];
|
||||||
locker.unlock();
|
locker.unlock();
|
||||||
|
|
||||||
WSRequestHandler handler(connProperties);
|
if (GetConfig()->DebugEnabled) {
|
||||||
std::string response = handler.processIncomingMessage(payload);
|
blog(LOG_INFO, "Request >> '%s'", payload.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
_server.send(hdl, response, websocketpp::frame::opcode::text);
|
WSRequestHandler requestHandler(connProperties);
|
||||||
|
OBSRemoteProtocol protocol;
|
||||||
|
std::string response = protocol.processMessage(requestHandler, payload);
|
||||||
|
|
||||||
|
if (GetConfig()->DebugEnabled) {
|
||||||
|
blog(LOG_INFO, "Response << '%s'", response.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
websocketpp::lib::error_code errorCode;
|
||||||
|
_server.send(hdl, response, websocketpp::frame::opcode::text, errorCode);
|
||||||
|
|
||||||
|
if (errorCode) {
|
||||||
|
std::string errorCodeMessage = errorCode.message();
|
||||||
|
blog(LOG_INFO, "server(response): send failed: %s",
|
||||||
|
errorCodeMessage.c_str());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,11 +30,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
|||||||
#include <websocketpp/server.hpp>
|
#include <websocketpp/server.hpp>
|
||||||
|
|
||||||
#include "ConnectionProperties.h"
|
#include "ConnectionProperties.h"
|
||||||
|
|
||||||
#include "WSRequestHandler.h"
|
#include "WSRequestHandler.h"
|
||||||
|
#include "rpc/RpcEvent.h"
|
||||||
QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
|
|
||||||
QT_FORWARD_DECLARE_CLASS(QWebSocket)
|
|
||||||
|
|
||||||
using websocketpp::connection_hdl;
|
using websocketpp::connection_hdl;
|
||||||
|
|
||||||
@ -49,7 +46,7 @@ public:
|
|||||||
virtual ~WSServer();
|
virtual ~WSServer();
|
||||||
void start(quint16 port);
|
void start(quint16 port);
|
||||||
void stop();
|
void stop();
|
||||||
void broadcast(std::string message);
|
void broadcast(const RpcEvent& event);
|
||||||
QThreadPool* threadPool() {
|
QThreadPool* threadPool() {
|
||||||
return &_threadPool;
|
return &_threadPool;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
|||||||
|
|
||||||
#include <obs-module.h>
|
#include <obs-module.h>
|
||||||
#include <obs-frontend-api.h>
|
#include <obs-frontend-api.h>
|
||||||
|
#include <obs-data.h>
|
||||||
|
|
||||||
#include <QtCore/QTimer>
|
#include <QtCore/QTimer>
|
||||||
#include <QtWidgets/QAction>
|
#include <QtWidgets/QAction>
|
||||||
@ -35,6 +36,11 @@ void ___data_dummy_addref(obs_data_t*) {}
|
|||||||
void ___data_array_dummy_addref(obs_data_array_t*) {}
|
void ___data_array_dummy_addref(obs_data_array_t*) {}
|
||||||
void ___output_dummy_addref(obs_output_t*) {}
|
void ___output_dummy_addref(obs_output_t*) {}
|
||||||
|
|
||||||
|
void ___data_item_dummy_addref(obs_data_item_t*) {}
|
||||||
|
void ___data_item_release(obs_data_item_t* dataItem) {
|
||||||
|
obs_data_item_release(&dataItem);
|
||||||
|
}
|
||||||
|
|
||||||
OBS_DECLARE_MODULE()
|
OBS_DECLARE_MODULE()
|
||||||
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
|
OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
|
||||||
|
|
||||||
@ -55,10 +61,6 @@ bool obs_module_load(void) {
|
|||||||
_server = WSServerPtr(new WSServer());
|
_server = WSServerPtr(new WSServer());
|
||||||
_eventsSystem = WSEventsPtr(new WSEvents(_server));
|
_eventsSystem = WSEventsPtr(new WSEvents(_server));
|
||||||
|
|
||||||
if (_config->ServerEnabled) {
|
|
||||||
_server->start(_config->ServerPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI setup
|
// UI setup
|
||||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||||
QMainWindow* mainWindow = (QMainWindow*)obs_frontend_get_main_window();
|
QMainWindow* mainWindow = (QMainWindow*)obs_frontend_get_main_window();
|
||||||
@ -75,6 +77,17 @@ bool obs_module_load(void) {
|
|||||||
settingsDialog->ToggleShowHide();
|
settingsDialog->ToggleShowHide();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Setup event handler to start the server once OBS is ready
|
||||||
|
auto eventCallback = [](enum obs_frontend_event event, void *param) {
|
||||||
|
if (event == OBS_FRONTEND_EVENT_FINISHED_LOADING) {
|
||||||
|
if (_config->ServerEnabled) {
|
||||||
|
_server->start(_config->ServerPort);
|
||||||
|
}
|
||||||
|
obs_frontend_remove_event_callback((obs_frontend_event_cb)param, nullptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
obs_frontend_add_event_callback(eventCallback, (void*)(obs_frontend_event_cb)eventCallback);
|
||||||
|
|
||||||
// Loading finished
|
// Loading finished
|
||||||
blog(LOG_INFO, "module loaded!");
|
blog(LOG_INFO, "module loaded!");
|
||||||
|
|
||||||
|
@ -38,6 +38,11 @@ using OBSDataArrayAutoRelease =
|
|||||||
using OBSOutputAutoRelease =
|
using OBSOutputAutoRelease =
|
||||||
OBSRef<obs_output_t*, ___output_dummy_addref, obs_output_release>;
|
OBSRef<obs_output_t*, ___output_dummy_addref, obs_output_release>;
|
||||||
|
|
||||||
|
void ___data_item_dummy_addref(obs_data_item_t*);
|
||||||
|
void ___data_item_release(obs_data_item_t*);
|
||||||
|
using OBSDataItemAutoRelease =
|
||||||
|
OBSRef<obs_data_item_t*, ___data_item_dummy_addref, ___data_item_release>;
|
||||||
|
|
||||||
class Config;
|
class Config;
|
||||||
typedef std::shared_ptr<Config> ConfigPtr;
|
typedef std::shared_ptr<Config> ConfigPtr;
|
||||||
|
|
||||||
@ -51,6 +56,6 @@ ConfigPtr GetConfig();
|
|||||||
WSServerPtr GetServer();
|
WSServerPtr GetServer();
|
||||||
WSEventsPtr GetEventsSystem();
|
WSEventsPtr GetEventsSystem();
|
||||||
|
|
||||||
#define OBS_WEBSOCKET_VERSION "4.6.0"
|
#define OBS_WEBSOCKET_VERSION "4.8.0"
|
||||||
|
|
||||||
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)
|
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)
|
||||||
|
119
src/protocol/OBSRemoteProtocol.cpp
Normal file
119
src/protocol/OBSRemoteProtocol.cpp
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2019 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 <inttypes.h>
|
||||||
|
|
||||||
|
#include "OBSRemoteProtocol.h"
|
||||||
|
#include "../WSRequestHandler.h"
|
||||||
|
#include "../rpc/RpcEvent.h"
|
||||||
|
#include "../Utils.h"
|
||||||
|
|
||||||
|
std::string OBSRemoteProtocol::processMessage(WSRequestHandler& requestHandler, std::string message)
|
||||||
|
{
|
||||||
|
std::string msgContainer(message);
|
||||||
|
const char* msg = msgContainer.c_str();
|
||||||
|
|
||||||
|
OBSDataAutoRelease data = obs_data_create_from_json(msg);
|
||||||
|
if (!data) {
|
||||||
|
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg);
|
||||||
|
return errorResponse(QString::Null(), "invalid JSON payload");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!obs_data_has_user_value(data, "request-type") || !obs_data_has_user_value(data, "message-id")) {
|
||||||
|
return errorResponse(QString::Null(), "missing request parameters");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString methodName = obs_data_get_string(data, "request-type");
|
||||||
|
QString messageId = obs_data_get_string(data, "message-id");
|
||||||
|
|
||||||
|
OBSDataAutoRelease params = obs_data_create();
|
||||||
|
obs_data_apply(params, data);
|
||||||
|
obs_data_unset_user_value(params, "request-type");
|
||||||
|
obs_data_unset_user_value(params, "message-id");
|
||||||
|
|
||||||
|
RpcRequest request(messageId, methodName, params);
|
||||||
|
RpcResponse response = requestHandler.processRequest(request);
|
||||||
|
|
||||||
|
OBSData additionalFields = response.additionalFields();
|
||||||
|
switch (response.status()) {
|
||||||
|
case RpcResponse::Status::Ok:
|
||||||
|
return successResponse(messageId, additionalFields);
|
||||||
|
case RpcResponse::Status::Error:
|
||||||
|
return errorResponse(messageId, response.errorMessage(), additionalFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OBSRemoteProtocol::encodeEvent(const RpcEvent& event)
|
||||||
|
{
|
||||||
|
OBSDataAutoRelease eventData = obs_data_create();
|
||||||
|
|
||||||
|
QString updateType = event.updateType();
|
||||||
|
obs_data_set_string(eventData, "update-type", updateType.toUtf8().constData());
|
||||||
|
|
||||||
|
std::optional<uint64_t> streamTime = event.streamTime();
|
||||||
|
if (streamTime.has_value()) {
|
||||||
|
QString streamingTimecode = Utils::nsToTimestamp(streamTime.value());
|
||||||
|
obs_data_set_string(eventData, "stream-timecode", streamingTimecode.toUtf8().constData());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<uint64_t> recordingTime = event.recordingTime();
|
||||||
|
if (recordingTime.has_value()) {
|
||||||
|
QString recordingTimecode = Utils::nsToTimestamp(recordingTime.value());
|
||||||
|
obs_data_set_string(eventData, "rec-timecode", recordingTimecode.toUtf8().constData());
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSData additionalFields = event.additionalFields();
|
||||||
|
if (additionalFields) {
|
||||||
|
obs_data_apply(eventData, additionalFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::string(obs_data_get_json(eventData));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OBSRemoteProtocol::buildResponse(QString messageId, QString status, obs_data_t* fields)
|
||||||
|
{
|
||||||
|
OBSDataAutoRelease response = obs_data_create();
|
||||||
|
if (!messageId.isNull()) {
|
||||||
|
obs_data_set_string(response, "message-id", messageId.toUtf8().constData());
|
||||||
|
}
|
||||||
|
obs_data_set_string(response, "status", status.toUtf8().constData());
|
||||||
|
|
||||||
|
if (fields) {
|
||||||
|
obs_data_apply(response, fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string responseString = obs_data_get_json(response);
|
||||||
|
return responseString;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OBSRemoteProtocol::successResponse(QString messageId, obs_data_t* fields)
|
||||||
|
{
|
||||||
|
return buildResponse(messageId, "ok", fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string OBSRemoteProtocol::errorResponse(QString messageId, QString errorMessage, obs_data_t* additionalFields)
|
||||||
|
{
|
||||||
|
OBSDataAutoRelease fields = obs_data_create();
|
||||||
|
if (additionalFields) {
|
||||||
|
obs_data_apply(fields, additionalFields);
|
||||||
|
}
|
||||||
|
obs_data_set_string(fields, "error", errorMessage.toUtf8().constData());
|
||||||
|
return buildResponse(messageId, "error", fields);
|
||||||
|
}
|
38
src/protocol/OBSRemoteProtocol.h
Normal file
38
src/protocol/OBSRemoteProtocol.h
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2019 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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <obs-data.h>
|
||||||
|
#include <QtCore/QString>
|
||||||
|
|
||||||
|
class WSRequestHandler;
|
||||||
|
class RpcEvent;
|
||||||
|
|
||||||
|
class OBSRemoteProtocol
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
std::string processMessage(WSRequestHandler& requestHandler, std::string message);
|
||||||
|
std::string encodeEvent(const RpcEvent& event);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string buildResponse(QString messageId, QString status, obs_data_t* fields = nullptr);
|
||||||
|
std::string successResponse(QString messageId, obs_data_t* fields = nullptr);
|
||||||
|
std::string errorResponse(QString messageId, QString errorMessage, obs_data_t* additionalFields = nullptr);
|
||||||
|
};
|
35
src/rpc/RpcEvent.cpp
Normal file
35
src/rpc/RpcEvent.cpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2020 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 "RpcEvent.h"
|
||||||
|
|
||||||
|
RpcEvent::RpcEvent(
|
||||||
|
const QString& updateType,
|
||||||
|
std::optional<uint64_t> streamTime, std::optional<uint64_t> recordingTime,
|
||||||
|
obs_data_t* additionalFields
|
||||||
|
) :
|
||||||
|
_updateType(updateType),
|
||||||
|
_streamTime(streamTime),
|
||||||
|
_recordingTime(recordingTime),
|
||||||
|
_additionalFields(nullptr)
|
||||||
|
{
|
||||||
|
if (additionalFields) {
|
||||||
|
_additionalFields = obs_data_create();
|
||||||
|
obs_data_apply(_additionalFields, additionalFields);
|
||||||
|
}
|
||||||
|
}
|
61
src/rpc/RpcEvent.h
Normal file
61
src/rpc/RpcEvent.h
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2020 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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <obs-data.h>
|
||||||
|
#include <QtCore/QString>
|
||||||
|
|
||||||
|
#include "../obs-websocket.h"
|
||||||
|
|
||||||
|
class RpcEvent
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit RpcEvent(
|
||||||
|
const QString& updateType,
|
||||||
|
std::optional<uint64_t> streamTime, std::optional<uint64_t> recordingTime,
|
||||||
|
obs_data_t* additionalFields = nullptr
|
||||||
|
);
|
||||||
|
|
||||||
|
const QString& updateType() const
|
||||||
|
{
|
||||||
|
return _updateType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::optional<uint64_t> streamTime() const
|
||||||
|
{
|
||||||
|
return _streamTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::optional<uint64_t> recordingTime() const
|
||||||
|
{
|
||||||
|
return _recordingTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OBSData additionalFields() const
|
||||||
|
{
|
||||||
|
return OBSData(_additionalFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString _updateType;
|
||||||
|
std::optional<uint64_t> _streamTime;
|
||||||
|
std::optional<uint64_t> _recordingTime;
|
||||||
|
OBSDataAutoRelease _additionalFields;
|
||||||
|
};
|
104
src/rpc/RpcRequest.cpp
Normal file
104
src/rpc/RpcRequest.cpp
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2019 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 "RpcRequest.h"
|
||||||
|
#include "RpcResponse.h"
|
||||||
|
|
||||||
|
RpcRequest::RpcRequest(const QString& messageId, const QString& methodName, obs_data_t* params) :
|
||||||
|
_messageId(messageId),
|
||||||
|
_methodName(methodName),
|
||||||
|
_parameters(nullptr)
|
||||||
|
{
|
||||||
|
if (params) {
|
||||||
|
_parameters = obs_data_create();
|
||||||
|
obs_data_apply(_parameters, params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const RpcResponse RpcRequest::success(obs_data_t* additionalFields) const
|
||||||
|
{
|
||||||
|
return RpcResponse::ok(*this, additionalFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
const RpcResponse RpcRequest::failed(const QString& errorMessage, obs_data_t* additionalFields) const
|
||||||
|
{
|
||||||
|
return RpcResponse::fail(*this, errorMessage, additionalFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool RpcRequest::hasField(QString name, obs_data_type expectedFieldType, obs_data_number_type expectedNumberType) const
|
||||||
|
{
|
||||||
|
if (!_parameters || name.isEmpty() || name.isNull()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OBSDataItemAutoRelease dataItem = obs_data_item_byname(_parameters, name.toUtf8());
|
||||||
|
if (!dataItem) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expectedFieldType != OBS_DATA_NULL) {
|
||||||
|
obs_data_type fieldType = obs_data_item_gettype(dataItem);
|
||||||
|
if (fieldType != expectedFieldType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fieldType == OBS_DATA_NUMBER && expectedNumberType != OBS_DATA_NUM_INVALID) {
|
||||||
|
obs_data_number_type numberType = obs_data_item_numtype(dataItem);
|
||||||
|
if (numberType != expectedNumberType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool RpcRequest::hasBool(QString fieldName) const
|
||||||
|
{
|
||||||
|
return this->hasField(fieldName, OBS_DATA_BOOLEAN);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool RpcRequest::hasString(QString fieldName) const
|
||||||
|
{
|
||||||
|
return this->hasField(fieldName, OBS_DATA_STRING);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool RpcRequest::hasNumber(QString fieldName, obs_data_number_type expectedNumberType) const
|
||||||
|
{
|
||||||
|
return this->hasField(fieldName, OBS_DATA_NUMBER, expectedNumberType);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool RpcRequest::hasInteger(QString fieldName) const
|
||||||
|
{
|
||||||
|
return this->hasNumber(fieldName, OBS_DATA_NUM_INT);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool RpcRequest::hasDouble(QString fieldName) const
|
||||||
|
{
|
||||||
|
return this->hasNumber(fieldName, OBS_DATA_NUM_DOUBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool RpcRequest::hasArray(QString fieldName) const
|
||||||
|
{
|
||||||
|
return this->hasField(fieldName, OBS_DATA_ARRAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool RpcRequest::hasObject(QString fieldName) const
|
||||||
|
{
|
||||||
|
return this->hasField(fieldName, OBS_DATA_OBJECT);
|
||||||
|
}
|
65
src/rpc/RpcRequest.h
Normal file
65
src/rpc/RpcRequest.h
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2019 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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <obs-data.h>
|
||||||
|
#include <QtCore/QString>
|
||||||
|
#include "../obs-websocket.h"
|
||||||
|
|
||||||
|
// forward declarations
|
||||||
|
class RpcResponse;
|
||||||
|
|
||||||
|
class RpcRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit RpcRequest(const QString& messageId, const QString& methodName, obs_data_t* params);
|
||||||
|
|
||||||
|
const QString& messageId() const
|
||||||
|
{
|
||||||
|
return _messageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString& methodName() const
|
||||||
|
{
|
||||||
|
return _methodName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OBSData parameters() const
|
||||||
|
{
|
||||||
|
return OBSData(_parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
const RpcResponse success(obs_data_t* additionalFields = nullptr) const;
|
||||||
|
const RpcResponse failed(const QString& errorMessage, obs_data_t* additionalFields = nullptr) const;
|
||||||
|
|
||||||
|
const bool hasField(QString fieldName, obs_data_type expectedFieldType = OBS_DATA_NULL,
|
||||||
|
obs_data_number_type expectedNumberType = OBS_DATA_NUM_INVALID) const;
|
||||||
|
const bool hasBool(QString fieldName) const;
|
||||||
|
const bool hasString(QString fieldName) const;
|
||||||
|
const bool hasNumber(QString fieldName, obs_data_number_type expectedNumberType = OBS_DATA_NUM_INVALID) const;
|
||||||
|
const bool hasInteger(QString fieldName) const;
|
||||||
|
const bool hasDouble(QString fieldName) const;
|
||||||
|
const bool hasArray(QString fieldName) const;
|
||||||
|
const bool hasObject(QString fieldName) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const QString _messageId;
|
||||||
|
const QString _methodName;
|
||||||
|
OBSDataAutoRelease _parameters;
|
||||||
|
};
|
48
src/rpc/RpcResponse.cpp
Normal file
48
src/rpc/RpcResponse.cpp
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2019 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 "RpcResponse.h"
|
||||||
|
#include "RpcRequest.h"
|
||||||
|
|
||||||
|
RpcResponse::RpcResponse(
|
||||||
|
Status status, const QString& messageId,
|
||||||
|
const QString& methodName, obs_data_t* additionalFields
|
||||||
|
) :
|
||||||
|
_status(status),
|
||||||
|
_messageId(messageId),
|
||||||
|
_methodName(methodName),
|
||||||
|
_additionalFields(nullptr)
|
||||||
|
{
|
||||||
|
if (additionalFields) {
|
||||||
|
_additionalFields = obs_data_create();
|
||||||
|
obs_data_apply(_additionalFields, additionalFields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const RpcResponse RpcResponse::ok(const RpcRequest& request, obs_data_t* additionalFields)
|
||||||
|
{
|
||||||
|
RpcResponse response(Status::Ok, request.messageId(), request.methodName(), additionalFields);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RpcResponse RpcResponse::fail(const RpcRequest& request, const QString& errorMessage, obs_data_t* additionalFields)
|
||||||
|
{
|
||||||
|
RpcResponse response(Status::Error, request.messageId(), request.methodName(), additionalFields);
|
||||||
|
response._errorMessage = errorMessage;
|
||||||
|
return response;
|
||||||
|
}
|
70
src/rpc/RpcResponse.h
Normal file
70
src/rpc/RpcResponse.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
obs-websocket
|
||||||
|
Copyright (C) 2016-2019 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/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <obs-data.h>
|
||||||
|
#include <QtCore/QString>
|
||||||
|
#include "../obs-websocket.h"
|
||||||
|
|
||||||
|
class RpcRequest;
|
||||||
|
|
||||||
|
class RpcResponse
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum Status { Unknown, Ok, Error };
|
||||||
|
|
||||||
|
static RpcResponse ofRequest(const RpcRequest& request);
|
||||||
|
static const RpcResponse ok(const RpcRequest& request, obs_data_t* additionalFields = nullptr);
|
||||||
|
static const RpcResponse fail(
|
||||||
|
const RpcRequest& request, const QString& errorMessage,
|
||||||
|
obs_data_t* additionalFields = nullptr
|
||||||
|
);
|
||||||
|
|
||||||
|
Status status() {
|
||||||
|
return _status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString& messageId() const {
|
||||||
|
return _messageId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString& methodName() const {
|
||||||
|
return _methodName;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString& errorMessage() const {
|
||||||
|
return _errorMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OBSData additionalFields() const {
|
||||||
|
return OBSData(_additionalFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
explicit RpcResponse(
|
||||||
|
Status status,
|
||||||
|
const QString& messageId, const QString& methodName,
|
||||||
|
obs_data_t* additionalFields = nullptr
|
||||||
|
);
|
||||||
|
const Status _status;
|
||||||
|
const QString _messageId;
|
||||||
|
const QString _methodName;
|
||||||
|
QString _errorMessage;
|
||||||
|
OBSDataAutoRelease _additionalFields;
|
||||||
|
};
|
Reference in New Issue
Block a user