mirror of
https://github.com/Palakis/obs-websocket.git
synced 2024-08-30 18:12:16 +00:00
Merge branch '4.x-current' into bugfix/cmake-frontend-lib
This commit is contained in:
commit
824cfd4871
5
.gitignore
vendored
5
.gitignore
vendored
@ -4,6 +4,7 @@
|
||||
/build32/
|
||||
/build64/
|
||||
/release/
|
||||
/package/
|
||||
/installer/Output/
|
||||
|
||||
.vscode
|
||||
.idea
|
||||
.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
|
@ -1,8 +1,6 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
|
||||
cd /root/obs-websocket
|
||||
|
||||
mkdir build && cd build
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
|
||||
make -j4
|
@ -18,25 +18,25 @@ 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
|
||||
if exist "%OBSPath%\obs-studio-last-tag-built.txt" (
|
||||
set /p OBSLastTagBuilt=<"%OBSPath%\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\ (
|
||||
if exist %OBSPath% (
|
||||
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
|
||||
cd /D %OBSPath%
|
||||
git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBSPath%\latest-obs-studio-tag-pre-pull.txt"
|
||||
set /p OBSLatestTagPrePull=<"%OBSPath%\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
|
||||
git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBSPath%\latest-obs-studio-tag-post-pull.txt"
|
||||
set /p OBSLatestTagPostPull=<"%OBSPath%\latest-obs-studio-tag-post-pull.txt"
|
||||
set /p OBSLatestTag=<"%OBSPath%\latest-obs-studio-tag-post-pull.txt"
|
||||
echo %OBSLatestTagPostPull%> "%OBSPath%\latest-obs-studio-tag.txt"
|
||||
)
|
||||
|
||||
REM Check the obs-studio tags for mismatches.
|
||||
@ -58,22 +58,22 @@ if not %OBSLatestTagPostPull%==%OBSLastTagBuilt% (
|
||||
|
||||
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 (
|
||||
if not exist %OBSPath% (
|
||||
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
|
||||
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"
|
||||
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 (
|
||||
if not exist %OBSPath%\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 (
|
||||
if not exist %OBSPath%\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
|
||||
)
|
||||
@ -95,35 +95,43 @@ echo:
|
||||
REM If the build flag is set, build obs-studio.
|
||||
if defined BuildOBS (
|
||||
echo Building obs-studio...
|
||||
cd /D %OBSPath%
|
||||
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
|
||||
|
||||
echo Removing previous build dirs...
|
||||
if exist build32 rmdir /s /q "%OBSPath%\build32"
|
||||
if exist build64 rmdir /s /q "%OBSPath%\build64"
|
||||
|
||||
echo Making new build dirs...
|
||||
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 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 14 2015 Win64" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true ..
|
||||
|
||||
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:
|
||||
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
|
||||
|
||||
REM echo Building obs-studio %OBSLatestTag% 32-bit ^(Build Config: %build_config%^)...
|
||||
REM call msbuild /m /p:Configuration=%build_config% %OBSPath%\build32\obs-studio.sln
|
||||
|
||||
REM echo Building obs-studio %OBSLatestTag% 64-bit ^(Build Config: %build_config%^)...
|
||||
REM call msbuild /m /p:Configuration=%build_config% %OBSPath%\build64\obs-studio.sln
|
||||
|
||||
cd ..
|
||||
git describe --tags --abbrev=0 > "%OBSPath%\obs-studio-last-tag-built.txt"
|
||||
set /p OBSLastTagBuilt=<"%OBSPath%\obs-studio-last-tag-built.txt"
|
||||
) else (
|
||||
echo Last OBS tag built is: %OBSLastTagBuilt%
|
||||
echo No need to rebuild OBS.
|
||||
)
|
||||
|
||||
dir "%OBSPath%\libobs"
|
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 "-- NPM version: $(npm -v)"
|
||||
|
||||
git fetch origin
|
||||
git checkout ${CHECKOUT_REF/refs\/heads\//}
|
||||
|
||||
cd docs
|
||||
npm install
|
||||
npm run build
|
||||
@ -15,19 +18,14 @@ if git diff --quiet; then
|
||||
exit 0
|
||||
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)"
|
||||
TARGET_REPO=${REMOTE_URL/https:\/\/github.com\//github.com/}
|
||||
GITHUB_REPO=https://${GH_TOKEN:-git}@${TARGET_REPO}
|
||||
|
||||
git config user.name "Travis CI"
|
||||
git config user.name "Azure CI"
|
||||
git config user.email "$COMMIT_AUTHOR_EMAIL"
|
||||
|
||||
git add ./generated
|
||||
git pull
|
||||
git commit -m "docs(travis): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]"
|
||||
git push -q $GITHUB_REPO HEAD:$TRAVIS_BRANCH
|
||||
git commit -m "docs(ci): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]"
|
||||
git push -q $GITHUB_REPO
|
||||
|
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
|
@ -19,10 +19,12 @@ export VERSION="$GIT_HASH-$GIT_BRANCH_OR_TAG"
|
||||
export LATEST_VERSION="$GIT_BRANCH_OR_TAG"
|
||||
|
||||
export FILENAME="obs-websocket-$VERSION.pkg"
|
||||
export LATEST_FILENAME="obs-websocket-latest-$LATEST_VERSION.pkg"
|
||||
|
||||
echo "[obs-websocket] Modifying obs-websocket.so"
|
||||
install_name_tool \
|
||||
-add_rpath @executable_path/../Frameworks/QtWidgets.framework/Versions/5/ \
|
||||
-add_rpath @executable_path/../Frameworks/QtGui.framework/Versions/5/ \
|
||||
-add_rpath @executable_path/../Frameworks/QtCore.framework/Versions/5/ \
|
||||
-change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets \
|
||||
-change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @rpath/QtGui \
|
||||
-change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \
|
||||
@ -37,4 +39,3 @@ packagesbuild ./CI/macos/obs-websocket.pkgproj
|
||||
|
||||
echo "[obs-websocket] Renaming obs-websocket.pkg to $FILENAME"
|
||||
mv ./release/obs-websocket.pkg ./release/$FILENAME
|
||||
cp ./release/$FILENAME ./release/$LATEST_FILENAME
|
||||
|
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,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"
|
@ -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/*
|
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" ..
|
@ -8,23 +8,15 @@ set(CMAKE_AUTOUIC ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
if (WIN32 OR APPLE)
|
||||
include(external/FindLibObs.cmake)
|
||||
endif()
|
||||
|
||||
add_definitions(-DASIO_STANDALONE)
|
||||
|
||||
if (UNIX)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
endif()
|
||||
|
||||
if (WIN32 OR APPLE)
|
||||
include(external/FindLibObs.cmake)
|
||||
endif()
|
||||
|
||||
find_package(LibObs REQUIRED)
|
||||
find_package(Qt5Core REQUIRED)
|
||||
find_package(Qt5Widgets REQUIRED)
|
||||
find_package(Qt5 REQUIRED COMPONENTS Core Widgets)
|
||||
|
||||
set(obs-websocket_SOURCES
|
||||
src/obs-websocket.cpp
|
||||
@ -42,9 +34,14 @@ set(obs-websocket_SOURCES
|
||||
src/WSRequestHandler_Streaming.cpp
|
||||
src/WSRequestHandler_StudioMode.cpp
|
||||
src/WSRequestHandler_Transitions.cpp
|
||||
src/WSRequestHandler_Outputs.cpp
|
||||
src/WSEvents.cpp
|
||||
src/Config.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)
|
||||
|
||||
set(obs-websocket_HEADERS
|
||||
@ -55,6 +52,10 @@ set(obs-websocket_HEADERS
|
||||
src/WSEvents.h
|
||||
src/Config.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)
|
||||
|
||||
# --- Platform-independent build settings ---
|
||||
@ -83,6 +84,10 @@ if(WIN32)
|
||||
message(FATAL_ERROR "Could not find OBS Frontend API's library !")
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
add_compile_options("/MP")
|
||||
endif()
|
||||
|
||||
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
|
||||
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
@ -119,6 +124,16 @@ if(WIN32)
|
||||
"$<TARGET_FILE:obs-websocket>"
|
||||
"${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
|
||||
COMMAND if $<CONFIG:RelWithDebInfo>==1 (
|
||||
"${CMAKE_COMMAND}" -E make_directory
|
||||
@ -173,8 +188,8 @@ if(UNIX AND NOT APPLE)
|
||||
install(TARGETS obs-websocket
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/obs-plugins")
|
||||
# Dirty fix for Ubuntu
|
||||
install(TARGETS obs-websocket
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/${UNAME_MACHINE}-linux-gnu/obs-plugins")
|
||||
install(TARGETS obs-websocket
|
||||
LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/${UNAME_MACHINE}-linux-gnu/obs-plugins")
|
||||
|
||||
install(FILES ${locale_files}
|
||||
DESTINATION "${CMAKE_INSTALL_PREFIX}/share/obs/obs-plugins/obs-websocket/locale")
|
||||
|
13
README.md
13
README.md
@ -1,20 +1,24 @@
|
||||
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)
|
||||
|
||||
[](https://ci.appveyor.com/project/Palakis/obs-websocket/history) [](https://travis-ci.org/Palakis/obs-websocket)
|
||||
[](https://ci.appveyor.com/project/Palakis/obs-websocket/history) [](https://travis-ci.org/Palakis/obs-websocket)
|
||||
|
||||
## 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
|
||||
|
||||
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.
|
||||
|
||||
### Possible use cases
|
||||
|
||||
- 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)
|
||||
- Automate scene switching with a third-party program (e.g. : auto-pilot, foot pedal, ...)
|
||||
@ -30,6 +34,7 @@ Here's a list of available language APIs for obs-websocket :
|
||||
- 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
|
||||
- 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
|
||||
|
||||
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` !
|
||||
|
||||
@ -60,7 +65,7 @@ If your Pull Request is not ready to merge yet, tag it with the `work in progres
|
||||
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).
|
||||
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:
|
||||
|
||||
|
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,158 @@
|
||||
pool:
|
||||
vmImage: 'macOS-10.13'
|
||||
jobs:
|
||||
- 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
|
||||
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: true
|
||||
- script: ./CI/generate-docs.sh
|
||||
displayName: 'Generate docs'
|
||||
env:
|
||||
CHECKOUT_REF: $(Build.SourceBranch)
|
||||
GH_TOKEN: $(GithubToken)
|
||||
|
||||
- script: ./CI/install-dependencies-macos.sh
|
||||
displayName: 'Install Dependencies'
|
||||
- job: 'Build_Windows'
|
||||
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/install-build-obs-macos.sh
|
||||
displayName: 'Build OBS'
|
||||
- script: ./CI/install-qt-win.cmd
|
||||
displayName: 'Install Qt'
|
||||
env:
|
||||
QtBaseDir: $(QtBaseDir)
|
||||
|
||||
- script: ./CI/build-macos.sh
|
||||
displayName: 'Build obs-websocket'
|
||||
- task: Cache@2
|
||||
displayName: Restore cached OBS Studio dependencies
|
||||
inputs:
|
||||
key: 'obsdeps | "$(Agent.OS)"'
|
||||
restoreKeys: |
|
||||
obsdeps | "$(Agent.OS)"
|
||||
path: $(DepsBasePath)
|
||||
|
||||
- script: ./CI/package-macos.sh
|
||||
displayName: 'Package'
|
||||
- script: ./CI/download-obs-deps.cmd
|
||||
displayName: 'Download OBS Studio dependencies'
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
pathtoPublish: './release'
|
||||
artifactName: 'build'
|
||||
- task: Cache@2
|
||||
displayName: Restore cached OBS Studio builds
|
||||
inputs:
|
||||
key: 'obs | "$(Agent.OS)"'
|
||||
restoreKeys: |
|
||||
obs | "$(Agent.OS)"
|
||||
path: $(OBSPath)
|
||||
|
||||
- script: ./CI/checkout-cmake-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'
|
||||
|
||||
- script: ./CI/package-macos.sh
|
||||
displayName: 'Package obs-websocket'
|
||||
|
||||
- task: PublishBuildArtifacts@1
|
||||
inputs:
|
||||
pathtoPublish: './release'
|
||||
artifactName: 'macos_build'
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
<!-- This file was generated based on handlebars templates. Do not edit directly! -->
|
||||
|
||||
# obs-websocket 4.6.0 protocol reference
|
||||
# obs-websocket 4.7.0 protocol reference
|
||||
|
||||
# General Introduction
|
||||
Messages are exchanged between the client and the server as JSON objects.
|
||||
@ -46,6 +46,7 @@ auth_response = base64_encode(auth_response_hash)
|
||||
* [SceneItem](#sceneitem)
|
||||
* [SceneItemTransform](#sceneitemtransform)
|
||||
* [OBSStats](#obsstats)
|
||||
* [Output](#output)
|
||||
* [Scene](#scene)
|
||||
- [Events](#events)
|
||||
* [Scenes](#scenes)
|
||||
@ -58,6 +59,8 @@ auth_response = base64_encode(auth_response_hash)
|
||||
+ [TransitionListChanged](#transitionlistchanged)
|
||||
+ [TransitionDurationChanged](#transitiondurationchanged)
|
||||
+ [TransitionBegin](#transitionbegin)
|
||||
+ [TransitionEnd](#transitionend)
|
||||
+ [TransitionVideoEnd](#transitionvideoend)
|
||||
* [Profiles](#profiles)
|
||||
+ [ProfileChanged](#profilechanged)
|
||||
+ [ProfileListChanged](#profilelistchanged)
|
||||
@ -72,6 +75,8 @@ auth_response = base64_encode(auth_response_hash)
|
||||
+ [RecordingStarted](#recordingstarted)
|
||||
+ [RecordingStopping](#recordingstopping)
|
||||
+ [RecordingStopped](#recordingstopped)
|
||||
+ [RecordingPaused](#recordingpaused)
|
||||
+ [RecordingResumed](#recordingresumed)
|
||||
* [Replay Buffer](#replay-buffer)
|
||||
+ [ReplayStarting](#replaystarting)
|
||||
+ [ReplayStarted](#replaystarted)
|
||||
@ -81,6 +86,7 @@ auth_response = base64_encode(auth_response_hash)
|
||||
+ [Exiting](#exiting)
|
||||
* [General](#general)
|
||||
+ [Heartbeat](#heartbeat)
|
||||
+ [BroadcastCustomMessage](#broadcastcustommessage)
|
||||
* [Sources](#sources)
|
||||
+ [SourceCreated](#sourcecreated)
|
||||
+ [SourceDestroyed](#sourcedestroyed)
|
||||
@ -91,11 +97,13 @@ auth_response = base64_encode(auth_response_hash)
|
||||
+ [SourceRenamed](#sourcerenamed)
|
||||
+ [SourceFilterAdded](#sourcefilteradded)
|
||||
+ [SourceFilterRemoved](#sourcefilterremoved)
|
||||
+ [SourceFilterVisibilityChanged](#sourcefiltervisibilitychanged)
|
||||
+ [SourceFiltersReordered](#sourcefiltersreordered)
|
||||
+ [SourceOrderChanged](#sourceorderchanged)
|
||||
+ [SceneItemAdded](#sceneitemadded)
|
||||
+ [SceneItemRemoved](#sceneitemremoved)
|
||||
+ [SceneItemVisibilityChanged](#sceneitemvisibilitychanged)
|
||||
+ [SceneItemLockChanged](#sceneitemlockchanged)
|
||||
+ [SceneItemTransformChanged](#sceneitemtransformchanged)
|
||||
+ [SceneItemSelected](#sceneitemselected)
|
||||
+ [SceneItemDeselected](#sceneitemdeselected)
|
||||
@ -111,7 +119,14 @@ auth_response = base64_encode(auth_response_hash)
|
||||
+ [SetFilenameFormatting](#setfilenameformatting)
|
||||
+ [GetFilenameFormatting](#getfilenameformatting)
|
||||
+ [GetStats](#getstats)
|
||||
+ [BroadcastCustomMessage](#broadcastcustommessage-1)
|
||||
+ [GetVideoInfo](#getvideoinfo)
|
||||
+ [OpenProjector](#openprojector)
|
||||
* [Outputs](#outputs)
|
||||
+ [ListOutputs](#listoutputs)
|
||||
+ [GetOutputInfo](#getoutputinfo)
|
||||
+ [StartOutput](#startoutput)
|
||||
+ [StopOutput](#stopoutput)
|
||||
* [Profiles](#profiles-1)
|
||||
+ [SetCurrentProfile](#setcurrentprofile)
|
||||
+ [GetCurrentProfile](#getcurrentprofile)
|
||||
@ -120,6 +135,8 @@ auth_response = base64_encode(auth_response_hash)
|
||||
+ [StartStopRecording](#startstoprecording)
|
||||
+ [StartRecording](#startrecording)
|
||||
+ [StopRecording](#stoprecording)
|
||||
+ [PauseRecording](#pauserecording)
|
||||
+ [ResumeRecording](#resumerecording)
|
||||
+ [SetRecordingFolder](#setrecordingfolder)
|
||||
+ [GetRecordingFolder](#getrecordingfolder)
|
||||
* [Replay Buffer](#replay-buffer-1)
|
||||
@ -166,11 +183,13 @@ auth_response = base64_encode(auth_response_hash)
|
||||
+ [SetBrowserSourceProperties](#setbrowsersourceproperties)
|
||||
+ [GetSpecialSources](#getspecialsources)
|
||||
+ [GetSourceFilters](#getsourcefilters)
|
||||
+ [GetSourceFilterInfo](#getsourcefilterinfo)
|
||||
+ [AddFilterToSource](#addfiltertosource)
|
||||
+ [RemoveFilterFromSource](#removefilterfromsource)
|
||||
+ [ReorderSourceFilter](#reordersourcefilter)
|
||||
+ [MoveSourceFilter](#movesourcefilter)
|
||||
+ [SetSourceFilterSettings](#setsourcefiltersettings)
|
||||
+ [SetSourceFilterVisibility](#setsourcefiltervisibility)
|
||||
+ [TakeSourceScreenshot](#takesourcescreenshot)
|
||||
* [Streaming](#streaming-1)
|
||||
+ [GetStreamingStatus](#getstreamingstatus)
|
||||
@ -207,9 +226,11 @@ These are complex types, such as `Source` and `Scene`, which are used as argumen
|
||||
| ---- | :---: | ------------|
|
||||
| `cy` | _Number_ | |
|
||||
| `cx` | _Number_ | |
|
||||
| `alignment` | _Number_ | 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. |
|
||||
| `name` | _String_ | The name of this Scene Item. |
|
||||
| `id` | _int_ | Scene item ID |
|
||||
| `render` | _Boolean_ | Whether or not this Scene Item is set to "visible". |
|
||||
| `muted` | _Boolean_ | Whether or not this Scene Item is muted. |
|
||||
| `locked` | _Boolean_ | Whether or not this Scene Item is locked and can't be moved around |
|
||||
| `source_cx` | _Number_ | |
|
||||
| `source_cy` | _Number_ | |
|
||||
@ -256,6 +277,27 @@ These are complex types, such as `Source` and `Scene`, which are used as argumen
|
||||
| `cpu-usage` | _double_ | Current CPU usage (percentage) |
|
||||
| `memory-usage` | _double_ | Current RAM usage (in megabytes) |
|
||||
| `free-disk-space` | _double_ | Free recording disk space (in megabytes) |
|
||||
## Output
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `name` | _String_ | Output name |
|
||||
| `type` | _String_ | Output type/kind |
|
||||
| `width` | _int_ | Video output width |
|
||||
| `height` | _int_ | Video output height |
|
||||
| `flags` | _Object_ | Output flags |
|
||||
| `flags.rawValue` | _int_ | Raw flags value |
|
||||
| `flags.audio` | _boolean_ | Output uses audio |
|
||||
| `flags.video` | _boolean_ | Output uses video |
|
||||
| `flags.encoded` | _boolean_ | Output is encoded |
|
||||
| `flags.multiTrack` | _boolean_ | Output uses several audio tracks |
|
||||
| `flags.service` | _boolean_ | Output uses a service |
|
||||
| `settings` | _Object_ | Output name |
|
||||
| `active` | _boolean_ | Output status (active or not) |
|
||||
| `reconnecting` | _boolean_ | Output reconnection status (reconnecting or not) |
|
||||
| `congestion` | _double_ | Output congestion |
|
||||
| `totalFrames` | _int_ | Number of frames sent |
|
||||
| `droppedFrames` | _int_ | Number of frames dropped |
|
||||
| `totalBytes` | _int_ | Total bytes sent |
|
||||
## Scene
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
@ -396,6 +438,47 @@ A transition (other than "cut") has begun.
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `name` | _String_ | Transition name. |
|
||||
| `type` | _String_ | Transition type. |
|
||||
| `duration` | _int_ | 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. |
|
||||
| `from-scene` | _String_ | Source scene of the transition |
|
||||
| `to-scene` | _String_ | Destination scene of the transition |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### TransitionEnd
|
||||
|
||||
|
||||
- Added in v4.8.0
|
||||
|
||||
A transition (other than "cut") has ended.
|
||||
Please note that the `from-scene` field is not available in TransitionEnd.
|
||||
|
||||
**Response Items:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `name` | _String_ | Transition name. |
|
||||
| `type` | _String_ | Transition type. |
|
||||
| `duration` | _int_ | Transition duration (in milliseconds). |
|
||||
| `to-scene` | _String_ | Destination scene of the transition |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### TransitionVideoEnd
|
||||
|
||||
|
||||
- Added in v4.8.0
|
||||
|
||||
A stinger transition has finished playing its video.
|
||||
|
||||
**Response Items:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `name` | _String_ | Transition name. |
|
||||
| `type` | _String_ | Transition type. |
|
||||
| `duration` | _int_ | Transition duration (in milliseconds). |
|
||||
| `from-scene` | _String_ | Source scene of the transition |
|
||||
| `to-scene` | _String_ | Destination scene of the transition |
|
||||
@ -579,6 +662,32 @@ _No additional response items._
|
||||
|
||||
---
|
||||
|
||||
### RecordingPaused
|
||||
|
||||
|
||||
- Added in v4.7.0
|
||||
|
||||
Current recording paused
|
||||
|
||||
**Response Items:**
|
||||
|
||||
_No additional response items._
|
||||
|
||||
---
|
||||
|
||||
### RecordingResumed
|
||||
|
||||
|
||||
- Added in v4.7.0
|
||||
|
||||
Current recording resumed
|
||||
|
||||
**Response Items:**
|
||||
|
||||
_No additional response items._
|
||||
|
||||
---
|
||||
|
||||
## Replay Buffer
|
||||
|
||||
### ReplayStarting
|
||||
@ -675,6 +784,23 @@ Emitted every 2 seconds after enabling it by calling SetHeartbeat.
|
||||
| `stats` | _OBSStats_ | OBS Stats |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### BroadcastCustomMessage
|
||||
|
||||
|
||||
- Added in v4.7.0
|
||||
|
||||
A custom broadcast message was received
|
||||
|
||||
**Response Items:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `realm` | _String_ | Identifier provided by the sender |
|
||||
| `data` | _Object_ | User-defined data |
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Sources
|
||||
@ -839,6 +965,24 @@ A filter was removed from a source.
|
||||
| `filterType` | _String_ | Filter type |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### SourceFilterVisibilityChanged
|
||||
|
||||
|
||||
- Added in v4.7.0
|
||||
|
||||
The visibility/enabled state of a filter changed
|
||||
|
||||
**Response Items:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `sourceName` | _String_ | Source name |
|
||||
| `filterName` | _String_ | Filter name |
|
||||
| `filterEnabled` | _Boolean_ | New filter state |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### SourceFiltersReordered
|
||||
@ -932,6 +1076,25 @@ An item's visibility has been toggled.
|
||||
| `item-visible` | _boolean_ | New visibility state of the item. |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### SceneItemLockChanged
|
||||
|
||||
|
||||
- Unreleased
|
||||
|
||||
An item's locked status has been toggled.
|
||||
|
||||
**Response Items:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `scene-name` | _String_ | Name of the scene. |
|
||||
| `item-name` | _String_ | Name of the item in the scene. |
|
||||
| `item-id` | _int_ | Scene item ID |
|
||||
| `item-locked` | _boolean_ | New locked state of the item. |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### SceneItemTransformChanged
|
||||
@ -1061,6 +1224,7 @@ _No specified parameters._
|
||||
| `obs-websocket-version` | _String_ | obs-websocket plugin version. |
|
||||
| `obs-studio-version` | _String_ | OBS Studio program version. |
|
||||
| `available-requests` | _String_ | List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3"). |
|
||||
| `supported-image-export-formats` | _String_ | List of supported formats for features that use image export (like the TakeSourceScreenshot request type) formatted as a comma-separated list string |
|
||||
|
||||
|
||||
---
|
||||
@ -1186,6 +1350,27 @@ _No specified parameters._
|
||||
| `stats` | _OBSStats_ | OBS stats |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### BroadcastCustomMessage
|
||||
|
||||
|
||||
- Added in v4.7.0
|
||||
|
||||
Broadcast custom message to all connected WebSocket clients
|
||||
|
||||
**Request Fields:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `realm` | _String_ | Identifier to be choosen by the client |
|
||||
| `data` | _Object_ | User-defined data |
|
||||
|
||||
|
||||
**Response Items:**
|
||||
|
||||
_No additional response items._
|
||||
|
||||
---
|
||||
|
||||
### GetVideoInfo
|
||||
@ -1214,6 +1399,115 @@ _No specified parameters._
|
||||
| `colorRange` | _String_ | Color range (full or partial) |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### OpenProjector
|
||||
|
||||
|
||||
- Unreleased
|
||||
|
||||
Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 or newer.
|
||||
|
||||
**Request Fields:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `type` | _String (Optional)_ | Type of projector: Preview (default), Source, Scene, StudioProgram, or Multiview (case insensitive). |
|
||||
| `monitor` | _int (Optional)_ | Monitor to open the projector on. If -1 or omitted, opens a window. |
|
||||
| `geometry` | _String (Optional)_ | 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. |
|
||||
| `name` | _String (Optional)_ | Name of the source or scene to be displayed (ignored for other projector types). |
|
||||
|
||||
|
||||
**Response Items:**
|
||||
|
||||
_No additional response items._
|
||||
|
||||
---
|
||||
|
||||
## Outputs
|
||||
|
||||
### ListOutputs
|
||||
|
||||
|
||||
- Added in v4.7.0
|
||||
|
||||
List existing outputs
|
||||
|
||||
**Request Fields:**
|
||||
|
||||
_No specified parameters._
|
||||
|
||||
**Response Items:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `outputs` | _Array<Output>_ | Outputs list |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### GetOutputInfo
|
||||
|
||||
|
||||
- Added in v4.7.0
|
||||
|
||||
Get information about a single output
|
||||
|
||||
**Request Fields:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `outputName` | _String_ | Output name |
|
||||
|
||||
|
||||
**Response Items:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `outputInfo` | _Output_ | Output info |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### StartOutput
|
||||
|
||||
|
||||
- Added in v4.7.0
|
||||
|
||||
Start an output
|
||||
|
||||
**Request Fields:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `outputName` | _String_ | Output name |
|
||||
|
||||
|
||||
**Response Items:**
|
||||
|
||||
_No additional response items._
|
||||
|
||||
---
|
||||
|
||||
### StopOutput
|
||||
|
||||
|
||||
- Added in v4.7.0
|
||||
|
||||
Stop an output
|
||||
|
||||
**Request Fields:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `outputName` | _String_ | Output name |
|
||||
| `force` | _boolean (optional)_ | Force stop (default: false) |
|
||||
|
||||
|
||||
**Response Items:**
|
||||
|
||||
_No additional response items._
|
||||
|
||||
---
|
||||
|
||||
## Profiles
|
||||
@ -1333,6 +1627,42 @@ _No additional response items._
|
||||
|
||||
---
|
||||
|
||||
### PauseRecording
|
||||
|
||||
|
||||
- Added in v4.7.0
|
||||
|
||||
Pause the current recording.
|
||||
Returns an error if recording is not active or already paused.
|
||||
|
||||
**Request Fields:**
|
||||
|
||||
_No specified parameters._
|
||||
|
||||
**Response Items:**
|
||||
|
||||
_No additional response items._
|
||||
|
||||
---
|
||||
|
||||
### ResumeRecording
|
||||
|
||||
|
||||
- Added in v4.7.0
|
||||
|
||||
Resume/unpause the current recording (if paused).
|
||||
Returns an error if recording is not active or not paused.
|
||||
|
||||
**Request Fields:**
|
||||
|
||||
_No specified parameters._
|
||||
|
||||
**Response Items:**
|
||||
|
||||
_No additional response items._
|
||||
|
||||
---
|
||||
|
||||
### SetRecordingFolder
|
||||
|
||||
|
||||
@ -1550,6 +1880,7 @@ Coordinates are relative to the item's parent (the scene or group it belongs to)
|
||||
| `crop.bottom` | _int_ | The number of pixels cropped off the bottom of the source before scaling. |
|
||||
| `crop.left` | _int_ | The number of pixels cropped off the left of the source before scaling. |
|
||||
| `visible` | _bool_ | If the source is visible. |
|
||||
| `muted` | _bool_ | If the source is muted. |
|
||||
| `locked` | _bool_ | If the source's transform is locked. |
|
||||
| `bounds.type` | _String_ | 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". |
|
||||
| `bounds.alignment` | _int_ | Alignment of the bounding box. |
|
||||
@ -1559,6 +1890,9 @@ Coordinates are relative to the item's parent (the scene or group it belongs to)
|
||||
| `sourceHeight` | _int_ | Base source (without scaling) of the source |
|
||||
| `width` | _double_ | Scene item width (base source width multiplied by the horizontal scaling factor) |
|
||||
| `height` | _double_ | Scene item height (base source height multiplied by the vertical scaling factor) |
|
||||
| `alignment` | _int_ | 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. |
|
||||
| `parentGroupName` | _String (optional)_ | Name of the item's parent (if this item belongs to a group) |
|
||||
| `groupChildren` | _Array<SceneItemTransform> (optional)_ | List of children (if this item is a group) |
|
||||
|
||||
|
||||
---
|
||||
@ -2400,11 +2734,39 @@ List filters applied to a source
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `filters` | _Array<Object>_ | List of filters for the specified source |
|
||||
| `filters.*.enabled` | _Boolean_ | Filter status (enabled or not) |
|
||||
| `filters.*.type` | _String_ | Filter type |
|
||||
| `filters.*.name` | _String_ | Filter name |
|
||||
| `filters.*.settings` | _Object_ | Filter settings |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### GetSourceFilterInfo
|
||||
|
||||
|
||||
- Added in v4.7.0
|
||||
|
||||
List filters applied to a source
|
||||
|
||||
**Request Fields:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `sourceName` | _String_ | Source name |
|
||||
| `filterName` | _String_ | Source filter name |
|
||||
|
||||
|
||||
**Response Items:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `enabled` | _Boolean_ | Filter status (enabled or not) |
|
||||
| `type` | _String_ | Filter type |
|
||||
| `name` | _String_ | Filter name |
|
||||
| `settings` | _Object_ | Filter settings |
|
||||
|
||||
|
||||
---
|
||||
|
||||
### AddFilterToSource
|
||||
@ -2511,6 +2873,28 @@ Update settings of a filter
|
||||
| `filterSettings` | _Object_ | New settings. These will be merged to the current filter settings. |
|
||||
|
||||
|
||||
**Response Items:**
|
||||
|
||||
_No additional response items._
|
||||
|
||||
---
|
||||
|
||||
### SetSourceFilterVisibility
|
||||
|
||||
|
||||
- Added in v4.7.0
|
||||
|
||||
Change the visibility/enabled state of a filter
|
||||
|
||||
**Request Fields:**
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `sourceName` | _String_ | Source name |
|
||||
| `filterName` | _String_ | Source filter name |
|
||||
| `filterEnabled` | _Boolean_ | New filter state |
|
||||
|
||||
|
||||
**Response Items:**
|
||||
|
||||
_No additional response items._
|
||||
@ -2533,7 +2917,7 @@ preserved if only one of these two parameters is specified.
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | :---: | ------------|
|
||||
| `sourceName` | _String_ | Source name |
|
||||
| `sourceName` | _String_ | Source name. Note that, since scenes are also sources, you can also provide a scene name. |
|
||||
| `embedPictureFormat` | _String (optional)_ | Format of the Data URI encoded picture. Can be "png", "jpg", "jpeg" or "bmp" (or any other value supported by Qt's Image module) |
|
||||
| `saveToFilePath` | _String (optional)_ | Full file path (file extension included) where the captured image is to be saved. Can be in a format different from `pictureFormat`. Can be a relative path. |
|
||||
| `width` | _int (optional)_ | Screenshot width. Defaults to the source's base width. |
|
||||
@ -2612,9 +2996,9 @@ Will return an `error` if streaming is already active.
|
||||
| `stream.settings` | _Object (optional)_ | Settings for the stream. |
|
||||
| `stream.settings.server` | _String (optional)_ | The publish URL. |
|
||||
| `stream.settings.key` | _String (optional)_ | The publish key of the stream. |
|
||||
| `stream.settings.use-auth` | _boolean (optional)_ | Indicates whether authentication should be used when connecting to the streaming server. |
|
||||
| `stream.settings.username` | _String (optional)_ | If authentication is enabled, the username for the streaming server. Ignored if `use-auth` is not set to `true`. |
|
||||
| `stream.settings.password` | _String (optional)_ | If authentication is enabled, the password for the streaming server. Ignored if `use-auth` is not set to `true`. |
|
||||
| `stream.settings.use_auth` | _boolean (optional)_ | Indicates whether authentication should be used when connecting to the streaming server. |
|
||||
| `stream.settings.username` | _String (optional)_ | If authentication is enabled, the username for the streaming server. Ignored if `use_auth` is not set to `true`. |
|
||||
| `stream.settings.password` | _String (optional)_ | If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`. |
|
||||
|
||||
|
||||
**Response Items:**
|
||||
@ -2656,7 +3040,7 @@ Sets one or more attributes of the current streaming server settings. Any option
|
||||
| `settings` | _Object_ | The actual settings of the stream. |
|
||||
| `settings.server` | _String (optional)_ | The publish URL. |
|
||||
| `settings.key` | _String (optional)_ | The publish key. |
|
||||
| `settings.use-auth` | _boolean (optional)_ | Indicates whether authentication should be used when connecting to the streaming server. |
|
||||
| `settings.use_auth` | _boolean (optional)_ | Indicates whether authentication should be used when connecting to the streaming server. |
|
||||
| `settings.username` | _String (optional)_ | The username for the streaming service. |
|
||||
| `settings.password` | _String (optional)_ | The password for the streaming service. |
|
||||
| `save` | _boolean_ | Persist the settings to disk. |
|
||||
@ -2687,9 +3071,9 @@ _No specified parameters._
|
||||
| `settings` | _Object_ | Stream settings object. |
|
||||
| `settings.server` | _String_ | The publish URL. |
|
||||
| `settings.key` | _String_ | The publish key of the stream. |
|
||||
| `settings.use-auth` | _boolean_ | Indicates whether authentication should be used when connecting to the streaming server. |
|
||||
| `settings.username` | _String_ | The username to use when accessing the streaming server. Only present if `use-auth` is `true`. |
|
||||
| `settings.password` | _String_ | The password to use when accessing the streaming server. Only present if `use-auth` is `true`. |
|
||||
| `settings.use_auth` | _boolean_ | Indicates whether authentication should be used when connecting to the streaming server. |
|
||||
| `settings.username` | _String_ | The username to use when accessing the streaming server. Only present if `use_auth` is `true`. |
|
||||
| `settings.password` | _String_ | The password to use when accessing the streaming server. Only present if `use_auth` is `true`. |
|
||||
|
||||
|
||||
---
|
||||
|
@ -1,4 +1,4 @@
|
||||
# obs-websocket 4.6.0 protocol reference
|
||||
# obs-websocket 4.7.0 protocol reference
|
||||
|
||||
# General Introduction
|
||||
Messages are exchanged between the client and the server as JSON objects.
|
||||
|
239
src/Utils.cpp
239
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/>
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <QtWidgets/QMainWindow>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
#include <obs-frontend-api.h>
|
||||
#include <obs.hpp>
|
||||
#include <util/platform.h>
|
||||
|
||||
#include "obs-websocket.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.
|
||||
* @property {Number} `cy`
|
||||
* @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 {int} `id` Scene item ID
|
||||
* @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 {Number} `source_cx`
|
||||
* @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_int(data, "source_cx", (int)item_width);
|
||||
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, "cy", item_height * scale.y);
|
||||
obs_data_set_bool(data, "render", obs_sceneitem_visible(item));
|
||||
@ -173,23 +180,39 @@ obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) {
|
||||
return data;
|
||||
}
|
||||
|
||||
obs_sceneitem_t* Utils::GetSceneItemFromItem(obs_source_t* source, obs_data_t* item) {
|
||||
OBSSceneItem sceneItem;
|
||||
if (obs_data_has_user_value(item, "id")) {
|
||||
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::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::GetSceneItemFromName(obs_source_t* source, QString name) {
|
||||
obs_sceneitem_t* Utils::GetSceneItemFromName(obs_scene_t* scene, QString name) {
|
||||
if (!scene) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct current_search {
|
||||
QString query;
|
||||
obs_sceneitem_t* result;
|
||||
@ -199,11 +222,6 @@ obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, QString name)
|
||||
current_search search;
|
||||
search.query = name;
|
||||
search.result = nullptr;
|
||||
search.enumCallback = nullptr;
|
||||
|
||||
OBSScene scene = obs_scene_from_source(source);
|
||||
if (!scene)
|
||||
return nullptr;
|
||||
|
||||
search.enumCallback = [](
|
||||
obs_scene_t* scene,
|
||||
@ -236,10 +254,13 @@ obs_sceneitem_t* Utils::GetSceneItemFromName(obs_source_t* source, QString name)
|
||||
return search.result;
|
||||
}
|
||||
|
||||
// TODO refactor this to unify it with GetSceneItemFromName
|
||||
obs_sceneitem_t* Utils::GetSceneItemFromId(obs_source_t* source, size_t id) {
|
||||
obs_sceneitem_t* Utils::GetSceneItemFromId(obs_scene_t* scene, int64_t id) {
|
||||
if (!scene) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
struct current_search {
|
||||
size_t query;
|
||||
int query;
|
||||
obs_sceneitem_t* result;
|
||||
bool (*enumCallback)(obs_scene_t*, obs_sceneitem_t*, void*);
|
||||
};
|
||||
@ -247,21 +268,16 @@ obs_sceneitem_t* Utils::GetSceneItemFromId(obs_source_t* source, size_t id) {
|
||||
current_search search;
|
||||
search.query = id;
|
||||
search.result = nullptr;
|
||||
search.enumCallback = nullptr;
|
||||
|
||||
OBSScene scene = obs_scene_from_source(source);
|
||||
if (!scene)
|
||||
return nullptr;
|
||||
|
||||
search.enumCallback = [](
|
||||
obs_scene_t* scene,
|
||||
obs_sceneitem_t* currentItem,
|
||||
void* param)
|
||||
obs_scene_t* scene,
|
||||
obs_sceneitem_t* currentItem,
|
||||
void* param)
|
||||
{
|
||||
current_search* search = reinterpret_cast<current_search*>(param);
|
||||
|
||||
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) {
|
||||
return false;
|
||||
}
|
||||
@ -319,17 +335,19 @@ obs_source_t* Utils::GetTransitionFromName(QString searchName) {
|
||||
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()
|
||||
// do addref on the return source, so no need to use an OBSSource helper
|
||||
obs_source_t* scene = nullptr;
|
||||
// increase the returned source's refcount
|
||||
OBSSourceAutoRelease sceneSource = nullptr;
|
||||
|
||||
if (sceneName.isEmpty() || sceneName.isNull())
|
||||
scene = obs_frontend_get_current_scene();
|
||||
else
|
||||
scene = obs_get_source_by_name(sceneName.toUtf8());
|
||||
if (sceneName.isEmpty() || sceneName.isNull()) {
|
||||
sceneSource = obs_frontend_get_current_scene();
|
||||
}
|
||||
else {
|
||||
sceneSource = obs_get_source_by_name(sceneName.toUtf8());
|
||||
}
|
||||
|
||||
return scene;
|
||||
return obs_scene_from_source(sceneSource);
|
||||
}
|
||||
|
||||
obs_data_array_t* Utils::GetScenes() {
|
||||
@ -362,18 +380,37 @@ QSpinBox* Utils::GetTransitionDurationControl() {
|
||||
return window->findChild<QSpinBox*>("transitionDuration");
|
||||
}
|
||||
|
||||
int Utils::GetTransitionDuration() {
|
||||
QSpinBox* control = GetTransitionDurationControl();
|
||||
if (control)
|
||||
return control->value();
|
||||
else
|
||||
int Utils::GetTransitionDuration(obs_source_t* transition) {
|
||||
if (!transition || obs_source_get_type(transition) != OBS_SOURCE_TYPE_TRANSITION) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void Utils::SetTransitionDuration(int ms) {
|
||||
QSpinBox* control = GetTransitionDurationControl();
|
||||
if (control && ms >= 0)
|
||||
control->setValue(ms);
|
||||
QString transitionKind = obs_source_get_id(transition);
|
||||
if (transitionKind == "cut_transition") {
|
||||
// If this is a Cut transition, return 0
|
||||
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) {
|
||||
@ -387,38 +424,35 @@ bool Utils::SetTransitionByName(QString transitionName) {
|
||||
}
|
||||
}
|
||||
|
||||
QPushButton* Utils::GetPreviewModeButtonControl() {
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChild<QPushButton*>("modeSwitch");
|
||||
}
|
||||
obs_data_t* Utils::GetTransitionData(obs_source_t* transition) {
|
||||
int duration = Utils::GetTransitionDuration(transition);
|
||||
if (duration < 0) {
|
||||
blog(LOG_WARNING, "GetTransitionData: duration is negative !");
|
||||
}
|
||||
|
||||
QLayout* Utils::GetPreviewLayout() {
|
||||
QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window();
|
||||
return main->findChild<QLayout*>("previewLayout");
|
||||
}
|
||||
OBSSourceAutoRelease sourceScene = obs_transition_get_source(transition, OBS_TRANSITION_SOURCE_A);
|
||||
OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition);
|
||||
|
||||
void Utils::TransitionToProgram() {
|
||||
if (!obs_frontend_preview_program_mode_active())
|
||||
return;
|
||||
obs_data_t* transitionData = obs_data_create();
|
||||
obs_data_set_string(transitionData, "name", obs_source_get_name(transition));
|
||||
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
|
||||
// then this won't work as expected
|
||||
// When a transition starts and while it is running, SOURCE_A is the source scene
|
||||
// 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));
|
||||
}
|
||||
|
||||
obs_data_set_string(transitionData, "to-scene", obs_source_get_name(destinationScene));
|
||||
|
||||
// The program options widget is the second item in the left-to-right layout
|
||||
QWidget* programOptions = GetPreviewLayout()->itemAt(1)->widget();
|
||||
|
||||
// The "Transition" button lies in the mainButtonLayout
|
||||
// which is the first itemin the program options' layout
|
||||
QLayout* mainButtonLayout = programOptions->layout()->itemAt(1)->layout();
|
||||
QWidget* transitionBtnWidget = mainButtonLayout->itemAt(0)->widget();
|
||||
|
||||
// Try to cast that widget into a button
|
||||
QPushButton* transitionBtn = qobject_cast<QPushButton*>(transitionBtnWidget);
|
||||
|
||||
// Perform a click on that button
|
||||
transitionBtn->click();
|
||||
return transitionData;
|
||||
}
|
||||
|
||||
QString Utils::OBSVersionString() {
|
||||
@ -588,7 +622,7 @@ void Utils::StartReplayBuffer() {
|
||||
obs_output_t* rpOutput = obs_frontend_get_replay_buffer_output();
|
||||
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, "alt", true);
|
||||
obs_data_set_bool(dummyBinding, "shift", true);
|
||||
@ -744,6 +778,19 @@ obs_data_t* Utils::GetSceneItemPropertiesData(obs_sceneitem_t* sceneItem) {
|
||||
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)
|
||||
{
|
||||
struct enum_params {
|
||||
@ -764,14 +811,36 @@ obs_data_array_t* Utils::GetSourceFiltersList(obs_source_t* source, bool include
|
||||
{
|
||||
auto enumParams = reinterpret_cast<struct enum_params*>(param);
|
||||
|
||||
OBSDataAutoRelease filter = obs_data_create();
|
||||
obs_data_set_string(filter, "type", obs_source_get_id(child));
|
||||
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);
|
||||
OBSDataAutoRelease filterData = Utils::GetSourceFilterInfo(child, enumParams->includeSettings);
|
||||
obs_data_array_push_back(enumParams->filters, filterData);
|
||||
}, &enumParams);
|
||||
|
||||
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);
|
||||
}
|
||||
|
80
src/Utils.h
80
src/Utils.h
@ -31,55 +31,57 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include <obs-module.h>
|
||||
#include <util/config-file.h>
|
||||
|
||||
class Utils {
|
||||
public:
|
||||
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);
|
||||
typedef void(*PauseRecordingFunction)(bool);
|
||||
typedef bool(*RecordingPausedFunction)();
|
||||
|
||||
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 two 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);
|
||||
|
||||
static obs_data_array_t* GetScenes();
|
||||
static obs_data_t* GetSceneData(obs_source_t* source);
|
||||
obs_sceneitem_t* GetSceneItemFromItem(obs_scene_t* scene, obs_data_t* item);
|
||||
obs_scene_t* GetSceneFromNameOrCurrent(QString sceneName);
|
||||
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
|
||||
static QSpinBox* GetTransitionDurationControl();
|
||||
static int GetTransitionDuration();
|
||||
static void SetTransitionDuration(int ms);
|
||||
QSpinBox* GetTransitionDurationControl();
|
||||
int GetTransitionDuration(obs_source_t* transition);
|
||||
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();
|
||||
static QLayout* GetPreviewLayout();
|
||||
|
||||
// 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(
|
||||
QSystemTrayIcon* GetTrayIcon();
|
||||
void SysTrayNotify(
|
||||
QString text,
|
||||
QSystemTrayIcon::MessageIcon n,
|
||||
QString title = QString("obs-websocket"));
|
||||
|
||||
static const char* GetRecordingFolder();
|
||||
static bool SetRecordingFolder(const char* path);
|
||||
const char* GetRecordingFolder();
|
||||
bool SetRecordingFolder(const char* path);
|
||||
|
||||
static QString ParseDataToQueryString(obs_data_t* data);
|
||||
static obs_hotkey_t* FindHotkeyByName(QString name);
|
||||
static bool ReplayBufferEnabled();
|
||||
static void StartReplayBuffer();
|
||||
static bool IsRPHotkeySet();
|
||||
static const char* GetFilenameFormatting();
|
||||
static bool SetFilenameFormatting(const char* filenameFormatting);
|
||||
QString ParseDataToQueryString(obs_data_t* data);
|
||||
obs_hotkey_t* FindHotkeyByName(QString name);
|
||||
|
||||
bool ReplayBufferEnabled();
|
||||
void StartReplayBuffer();
|
||||
bool IsRPHotkeySet();
|
||||
|
||||
const char* GetFilenameFormatting();
|
||||
bool SetFilenameFormatting(const char* filenameFormatting);
|
||||
|
||||
QString nsToTimestamp(uint64_t ns);
|
||||
};
|
||||
|
564
src/WSEvents.cpp
564
src/WSEvents.cpp
@ -17,45 +17,21 @@
|
||||
* with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <util/platform.h>
|
||||
#include <media-io/video-io.h>
|
||||
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
#include "Config.h"
|
||||
#include "Utils.h"
|
||||
#include "WSEvents.h"
|
||||
|
||||
#include "obs-websocket.h"
|
||||
#include "Config.h"
|
||||
#include "Utils.h"
|
||||
#include "rpc/RpcEvent.h"
|
||||
|
||||
#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) {
|
||||
switch (type) {
|
||||
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) :
|
||||
_srv(srv),
|
||||
_streamStarttime(0),
|
||||
_recStarttime(0),
|
||||
_lastBytesSent(0),
|
||||
_lastBytesSentTime(0),
|
||||
HeartbeatIsActive(false),
|
||||
pulse(false)
|
||||
{
|
||||
@ -143,118 +120,131 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private
|
||||
return;
|
||||
}
|
||||
|
||||
if (event == OBS_FRONTEND_EVENT_FINISHED_LOADING) {
|
||||
owner->hookTransitionBeginEvent();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_SCENE_CHANGED) {
|
||||
owner->OnSceneChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED) {
|
||||
owner->OnSceneListChange();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED) {
|
||||
owner->hookTransitionBeginEvent();
|
||||
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->hookTransitionBeginEvent();
|
||||
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();
|
||||
switch (event) {
|
||||
case OBS_FRONTEND_EVENT_FINISHED_LOADING:
|
||||
owner->hookTransitionPlaybackEvents();
|
||||
break;
|
||||
|
||||
case OBS_FRONTEND_EVENT_SCENE_CHANGED:
|
||||
owner->OnSceneChange();
|
||||
break;
|
||||
|
||||
owner->OnStreamStarted();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPING) {
|
||||
owner->streamStatusTimer.stop();
|
||||
owner->OnStreamStopping();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_STREAMING_STOPPED) {
|
||||
owner->OnStreamStopped();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTING) {
|
||||
owner->OnRecordingStarting();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STARTED) {
|
||||
owner->OnRecordingStarted();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPING) {
|
||||
owner->OnRecordingStopping();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_RECORDING_STOPPED) {
|
||||
owner->OnRecordingStopped();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTING) {
|
||||
owner->OnReplayStarting();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STARTED) {
|
||||
owner->OnReplayStarted();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPING) {
|
||||
owner->OnReplayStopping();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_REPLAY_BUFFER_STOPPED) {
|
||||
owner->OnReplayStopped();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_STUDIO_MODE_ENABLED) {
|
||||
owner->OnStudioModeSwitched(true);
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_STUDIO_MODE_DISABLED) {
|
||||
owner->OnStudioModeSwitched(false);
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED) {
|
||||
owner->OnPreviewSceneChanged();
|
||||
}
|
||||
else if (event == OBS_FRONTEND_EVENT_EXIT) {
|
||||
owner->unhookTransitionBeginEvent();
|
||||
owner->OnExit();
|
||||
case OBS_FRONTEND_EVENT_SCENE_LIST_CHANGED:
|
||||
owner->OnSceneListChange();
|
||||
break;
|
||||
|
||||
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED:
|
||||
owner->hookTransitionPlaybackEvents();
|
||||
owner->OnSceneCollectionChange();
|
||||
break;
|
||||
|
||||
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED:
|
||||
owner->OnSceneCollectionListChange();
|
||||
break;
|
||||
|
||||
case OBS_FRONTEND_EVENT_TRANSITION_CHANGED:
|
||||
owner->OnTransitionChange();
|
||||
break;
|
||||
|
||||
case OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED:
|
||||
owner->hookTransitionPlaybackEvents();
|
||||
owner->OnTransitionListChange();
|
||||
break;
|
||||
|
||||
case OBS_FRONTEND_EVENT_PROFILE_CHANGED:
|
||||
owner->OnProfileChange();
|
||||
break;
|
||||
|
||||
case OBS_FRONTEND_EVENT_PROFILE_LIST_CHANGED:
|
||||
owner->OnProfileListChange();
|
||||
break;
|
||||
|
||||
case OBS_FRONTEND_EVENT_STREAMING_STARTING:
|
||||
owner->OnStreamStarting();
|
||||
break;
|
||||
|
||||
case OBS_FRONTEND_EVENT_STREAMING_STARTED:
|
||||
owner->streamStatusTimer.start(STATUS_INTERVAL);
|
||||
owner->StreamStatus();
|
||||
owner->OnStreamStarted();
|
||||
break;
|
||||
|
||||
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,
|
||||
obs_data_t* additionalFields = nullptr)
|
||||
{
|
||||
OBSDataAutoRelease update = obs_data_create();
|
||||
obs_data_set_string(update, "update-type", updateType);
|
||||
uint64_t streamTime = getStreamingTime();
|
||||
uint64_t recordingTime = getRecordingTime();
|
||||
RpcEvent event(QString(updateType), streamTime, recordingTime, additionalFields);
|
||||
|
||||
const char* ts = nullptr;
|
||||
if (obs_frontend_streaming_active()) {
|
||||
ts = nsToTimestamp(os_gettime_ns() - _streamStarttime);
|
||||
obs_data_set_string(update, "stream-timecode", ts);
|
||||
bfree((void*)ts);
|
||||
}
|
||||
|
||||
if (obs_frontend_recording_active()) {
|
||||
ts = nsToTimestamp(os_gettime_ns() - _recStarttime);
|
||||
obs_data_set_string(update, "rec-timecode", ts);
|
||||
bfree((void*)ts);
|
||||
}
|
||||
|
||||
if (additionalFields)
|
||||
obs_data_apply(update, additionalFields);
|
||||
|
||||
QString json = obs_data_get_json(update);
|
||||
_srv->broadcast(json.toStdString());
|
||||
|
||||
if (GetConfig()->DebugEnabled) {
|
||||
blog(LOG_INFO, "Update << '%s'", json.toUtf8().constData());
|
||||
}
|
||||
_srv->broadcast(event);
|
||||
}
|
||||
|
||||
void WSEvents::connectSourceSignals(obs_source_t* source) {
|
||||
@ -285,6 +275,8 @@ void WSEvents::connectSourceSignals(obs_source_t* source) {
|
||||
signal_handler_connect(sh, "item_remove", OnSceneItemDelete, this);
|
||||
signal_handler_connect(sh,
|
||||
"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_select", OnSceneItemSelected, this);
|
||||
signal_handler_connect(sh, "item_deselect", OnSceneItemDeselected, this);
|
||||
@ -314,14 +306,38 @@ void WSEvents::disconnectSourceSignals(obs_source_t* source) {
|
||||
signal_handler_disconnect(sh, "item_remove", OnSceneItemDelete, this);
|
||||
signal_handler_disconnect(sh,
|
||||
"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_select", OnSceneItemSelected, this);
|
||||
signal_handler_disconnect(sh, "item_deselect", OnSceneItemDeselected, 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);
|
||||
}
|
||||
|
||||
void WSEvents::hookTransitionBeginEvent() {
|
||||
void WSEvents::connectFilterSignals(obs_source_t* filter) {
|
||||
if (!filter) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@ -330,12 +346,16 @@ void WSEvents::hookTransitionBeginEvent() {
|
||||
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::unhookTransitionBeginEvent() {
|
||||
void WSEvents::unhookTransitionPlaybackEvents() {
|
||||
obs_frontend_source_list transitions = {};
|
||||
obs_frontend_get_transitions(&transitions);
|
||||
|
||||
@ -343,31 +363,41 @@ void WSEvents::unhookTransitionBeginEvent() {
|
||||
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 WSEvents::GetStreamingTime() {
|
||||
if (obs_frontend_streaming_active())
|
||||
return (os_gettime_ns() - _streamStarttime);
|
||||
else
|
||||
uint64_t getOutputRunningTime(obs_output_t* output) {
|
||||
if (!output || !obs_output_active(output)) {
|
||||
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() {
|
||||
return nsToTimestamp(GetStreamingTime());
|
||||
uint64_t WSEvents::getStreamingTime() {
|
||||
OBSOutputAutoRelease streamingOutput = obs_frontend_get_streaming_output();
|
||||
return getOutputRunningTime(streamingOutput);
|
||||
}
|
||||
|
||||
uint64_t WSEvents::GetRecordingTime() {
|
||||
if (obs_frontend_recording_active())
|
||||
return (os_gettime_ns() - _recStarttime);
|
||||
else
|
||||
return 0;
|
||||
uint64_t WSEvents::getRecordingTime() {
|
||||
OBSOutputAutoRelease recordingOutput = obs_frontend_get_recording_output();
|
||||
return getOutputRunningTime(recordingOutput);
|
||||
}
|
||||
|
||||
const char* WSEvents::GetRecordingTimecode() {
|
||||
return nsToTimestamp(GetRecordingTime());
|
||||
QString WSEvents::getStreamingTimecode() {
|
||||
return Utils::nsToTimestamp(getStreamingTime());
|
||||
}
|
||||
|
||||
QString WSEvents::getRecordingTimecode() {
|
||||
return Utils::nsToTimestamp(getRecordingTime());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -520,6 +550,7 @@ void WSEvents::OnStreamStarting() {
|
||||
void WSEvents::OnStreamStarted() {
|
||||
_streamStarttime = os_gettime_ns();
|
||||
_lastBytesSent = 0;
|
||||
|
||||
broadcastUpdate("StreamStarted");
|
||||
}
|
||||
|
||||
@ -536,7 +567,6 @@ void WSEvents::OnStreamStarted() {
|
||||
void WSEvents::OnStreamStopping() {
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_bool(data, "preview-only", false);
|
||||
|
||||
broadcastUpdate("StreamStopping", data);
|
||||
}
|
||||
|
||||
@ -550,6 +580,7 @@ void WSEvents::OnStreamStopping() {
|
||||
*/
|
||||
void WSEvents::OnStreamStopped() {
|
||||
_streamStarttime = 0;
|
||||
|
||||
broadcastUpdate("StreamStopped");
|
||||
}
|
||||
|
||||
@ -574,7 +605,6 @@ void WSEvents::OnRecordingStarting() {
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnRecordingStarted() {
|
||||
_recStarttime = os_gettime_ns();
|
||||
broadcastUpdate("RecordingStarted");
|
||||
}
|
||||
|
||||
@ -599,10 +629,33 @@ void WSEvents::OnRecordingStopping() {
|
||||
* @since 0.3
|
||||
*/
|
||||
void WSEvents::OnRecordingStopped() {
|
||||
_recStarttime = 0;
|
||||
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.
|
||||
*
|
||||
@ -694,6 +747,7 @@ void WSEvents::OnExit() {
|
||||
void WSEvents::StreamStatus() {
|
||||
bool streamingActive = obs_frontend_streaming_active();
|
||||
bool recordingActive = obs_frontend_recording_active();
|
||||
bool recordingPaused = obs_frontend_recording_paused();
|
||||
bool replayBufferActive = obs_frontend_replay_buffer_active();
|
||||
|
||||
OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output();
|
||||
@ -720,9 +774,7 @@ void WSEvents::StreamStatus() {
|
||||
_lastBytesSent = bytesSent;
|
||||
_lastBytesSentTime = bytesSentTime;
|
||||
|
||||
uint64_t totalStreamTime =
|
||||
(os_gettime_ns() - _streamStarttime) / 1000000000;
|
||||
|
||||
uint64_t totalStreamTime = (getStreamingTime() / 1000000000ULL);
|
||||
int totalFrames = obs_output_get_total_frames(streamOutput);
|
||||
int droppedFrames = obs_output_get_frames_dropped(streamOutput);
|
||||
|
||||
@ -732,6 +784,7 @@ void WSEvents::StreamStatus() {
|
||||
|
||||
obs_data_set_bool(data, "streaming", streamingActive);
|
||||
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_int(data, "bytes-per-sec", bytesPerSec);
|
||||
@ -778,6 +831,7 @@ void WSEvents::Heartbeat() {
|
||||
|
||||
bool streamingActive = obs_frontend_streaming_active();
|
||||
bool recordingActive = obs_frontend_recording_active();
|
||||
bool recordingPaused = obs_frontend_recording_paused();
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output();
|
||||
@ -786,23 +840,24 @@ void WSEvents::Heartbeat() {
|
||||
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();
|
||||
obs_data_set_string(data, "current-scene", obs_source_get_name(currentScene));
|
||||
|
||||
obs_data_set_bool(data, "streaming", streamingActive);
|
||||
if (streamingActive) {
|
||||
uint64_t totalStreamTime = (os_gettime_ns() - _streamStarttime) / 1000000000;
|
||||
obs_data_set_int(data, "total-stream-time", totalStreamTime);
|
||||
obs_data_set_int(data, "total-stream-time", (getStreamingTime() / 1000000000ULL));
|
||||
obs_data_set_int(data, "total-stream-bytes", (uint64_t)obs_output_get_total_bytes(streamOutput));
|
||||
obs_data_set_int(data, "total-stream-frames", obs_output_get_total_frames(streamOutput));
|
||||
}
|
||||
|
||||
obs_data_set_bool(data, "recording", recordingActive);
|
||||
obs_data_set_bool(data, "recording-paused", recordingPaused);
|
||||
if (recordingActive) {
|
||||
uint64_t totalRecordTime = (os_gettime_ns() - _recStarttime) / 1000000000;
|
||||
obs_data_set_int(data, "total-record-time", totalRecordTime);
|
||||
obs_data_set_int(data, "total-record-time", (getRecordingTime() / 1000000000ULL));
|
||||
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));
|
||||
}
|
||||
@ -834,7 +889,10 @@ void WSEvents::TransitionDurationChanged(int ms) {
|
||||
* A transition (other than "cut") has begun.
|
||||
*
|
||||
* @return {String} `name` Transition name.
|
||||
* @return {int} `duration` Transition duration (in milliseconds).
|
||||
* @return {String} `type` Transition type.
|
||||
* @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} `to-scene` Destination scene of the transition
|
||||
*
|
||||
@ -847,40 +905,66 @@ void WSEvents::OnTransitionBegin(void* param, calldata_t* data) {
|
||||
auto instance = reinterpret_cast<WSEvents*>(param);
|
||||
|
||||
OBSSource transition = calldata_get_pointer<obs_source_t>(data, "source");
|
||||
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));
|
||||
if (!transition) {
|
||||
return;
|
||||
}
|
||||
|
||||
OBSDataAutoRelease fields = Utils::GetTransitionData(transition);
|
||||
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.
|
||||
*
|
||||
@ -1138,6 +1222,8 @@ void WSEvents::OnSourceFilterAdded(void* param, calldata_t* data) {
|
||||
if (!filter) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->connectFilterSignals(filter);
|
||||
|
||||
OBSDataAutoRelease filterSettings = obs_source_get_settings(filter);
|
||||
|
||||
@ -1170,6 +1256,11 @@ void WSEvents::OnSourceFilterRemoved(void* param, calldata_t* data) {
|
||||
}
|
||||
|
||||
obs_source_t* filter = calldata_get_pointer<obs_source_t>(data, "filter");
|
||||
if (!filter) {
|
||||
return;
|
||||
}
|
||||
|
||||
self->disconnectFilterSignals(filter);
|
||||
|
||||
OBSDataAutoRelease fields = obs_data_create();
|
||||
obs_data_set_string(fields, "sourceName", obs_source_get_name(source));
|
||||
@ -1178,6 +1269,36 @@ void WSEvents::OnSourceFilterRemoved(void* param, calldata_t* data) {
|
||||
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.
|
||||
*
|
||||
@ -1354,6 +1475,44 @@ void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) {
|
||||
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 sources
|
||||
* @since unreleased
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@ -1505,6 +1664,25 @@ void WSEvents::OnStudioModeSwitched(bool checked) {
|
||||
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`
|
||||
* @property {double} `fps` Current framerate.
|
||||
@ -1530,12 +1708,12 @@ obs_data_t* WSEvents::GetStats() {
|
||||
double averageFrameTime = (double)obs_get_average_frame_time_ns() / 1000000.0;
|
||||
|
||||
config_t* currentProfile = obs_frontend_get_profile_config();
|
||||
QString outputMode = config_get_string(currentProfile, "Output", "Mode");
|
||||
QString path = (outputMode == "Advanced") ?
|
||||
const char* outputMode = config_get_string(currentProfile, "Output", "Mode");
|
||||
const char* path = strcmp(outputMode, "Advanced") ?
|
||||
config_get_string(currentProfile, "SimpleOutput", "FilePath") :
|
||||
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_int(stats, "render-total-frames", obs_get_total_frames());
|
||||
|
@ -40,15 +40,22 @@ public:
|
||||
void connectSourceSignals(obs_source_t* source);
|
||||
void disconnectSourceSignals(obs_source_t* source);
|
||||
|
||||
void hookTransitionBeginEvent();
|
||||
void unhookTransitionBeginEvent();
|
||||
void connectFilterSignals(obs_source_t* filter);
|
||||
void disconnectFilterSignals(obs_source_t* filter);
|
||||
|
||||
uint64_t GetStreamingTime();
|
||||
const char* GetStreamingTimecode();
|
||||
uint64_t GetRecordingTime();
|
||||
const char* GetRecordingTimecode();
|
||||
void hookTransitionPlaybackEvents();
|
||||
void unhookTransitionPlaybackEvents();
|
||||
|
||||
uint64_t getStreamingTime();
|
||||
uint64_t getRecordingTime();
|
||||
|
||||
QString getStreamingTimecode();
|
||||
QString getRecordingTimecode();
|
||||
|
||||
obs_data_t* GetStats();
|
||||
|
||||
void OnBroadcastCustomMessage(QString realm, obs_data_t* data);
|
||||
|
||||
bool HeartbeatIsActive;
|
||||
|
||||
private slots:
|
||||
@ -65,7 +72,6 @@ private:
|
||||
bool pulse;
|
||||
|
||||
uint64_t _streamStarttime;
|
||||
uint64_t _recStarttime;
|
||||
|
||||
uint64_t _lastBytesSent;
|
||||
uint64_t _lastBytesSentTime;
|
||||
@ -93,6 +99,8 @@ private:
|
||||
void OnRecordingStarted();
|
||||
void OnRecordingStopping();
|
||||
void OnRecordingStopped();
|
||||
void OnRecordingPaused();
|
||||
void OnRecordingResumed();
|
||||
|
||||
void OnReplayStarting();
|
||||
void OnReplayStarted();
|
||||
@ -108,6 +116,8 @@ private:
|
||||
enum obs_frontend_event event, void* privateData);
|
||||
|
||||
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 OnSourceDestroy(void* param, calldata_t* data);
|
||||
@ -121,12 +131,14 @@ private:
|
||||
|
||||
static void OnSourceFilterAdded(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 OnSceneReordered(void* param, calldata_t* data);
|
||||
static void OnSceneItemAdd(void* param, calldata_t* data);
|
||||
static void OnSceneItemDelete(void* param, calldata_t* data);
|
||||
static void OnSceneItemVisibilityChanged(void* param, calldata_t* data);
|
||||
static void OnSceneItemLockChanged(void* param, calldata_t* data);
|
||||
static void OnSceneItemTransform(void* param, calldata_t* data);
|
||||
static void OnSceneItemSelected(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/>
|
||||
*/
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <obs-data.h>
|
||||
|
||||
#include "Config.h"
|
||||
@ -24,204 +26,149 @@
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
QHash<QString, HandlerResponse(*)(WSRequestHandler*)> WSRequestHandler::messageMap {
|
||||
{ "GetVersion", WSRequestHandler::HandleGetVersion },
|
||||
{ "GetAuthRequired", WSRequestHandler::HandleGetAuthRequired },
|
||||
{ "Authenticate", WSRequestHandler::HandleAuthenticate },
|
||||
using namespace std::placeholders;
|
||||
|
||||
{ "GetStats", WSRequestHandler::HandleGetStats },
|
||||
{ "SetHeartbeat", WSRequestHandler::HandleSetHeartbeat },
|
||||
{ "GetVideoInfo", WSRequestHandler::HandleGetVideoInfo },
|
||||
const QHash<QString, RpcMethodHandler> WSRequestHandler::messageMap {
|
||||
{ "GetVersion", &WSRequestHandler::GetVersion },
|
||||
{ "GetAuthRequired", &WSRequestHandler::GetAuthRequired },
|
||||
{ "Authenticate", &WSRequestHandler::Authenticate },
|
||||
|
||||
{ "SetFilenameFormatting", WSRequestHandler::HandleSetFilenameFormatting },
|
||||
{ "GetFilenameFormatting", WSRequestHandler::HandleGetFilenameFormatting },
|
||||
{ "GetStats", &WSRequestHandler::GetStats },
|
||||
{ "SetHeartbeat", &WSRequestHandler::SetHeartbeat },
|
||||
{ "GetVideoInfo", &WSRequestHandler::GetVideoInfo },
|
||||
{ "OpenProjector", &WSRequestHandler::OpenProjector },
|
||||
|
||||
{ "SetCurrentScene", WSRequestHandler::HandleSetCurrentScene },
|
||||
{ "GetCurrentScene", WSRequestHandler::HandleGetCurrentScene },
|
||||
{ "GetSceneList", WSRequestHandler::HandleGetSceneList },
|
||||
{ "SetFilenameFormatting", &WSRequestHandler::SetFilenameFormatting },
|
||||
{ "GetFilenameFormatting", &WSRequestHandler::GetFilenameFormatting },
|
||||
|
||||
{ "SetSourceRender", WSRequestHandler::HandleSetSceneItemRender }, // Retrocompat
|
||||
{ "SetSceneItemRender", WSRequestHandler::HandleSetSceneItemRender },
|
||||
{ "SetSceneItemPosition", WSRequestHandler::HandleSetSceneItemPosition },
|
||||
{ "SetSceneItemTransform", WSRequestHandler::HandleSetSceneItemTransform },
|
||||
{ "SetSceneItemCrop", WSRequestHandler::HandleSetSceneItemCrop },
|
||||
{ "GetSceneItemProperties", WSRequestHandler::HandleGetSceneItemProperties },
|
||||
{ "SetSceneItemProperties", WSRequestHandler::HandleSetSceneItemProperties },
|
||||
{ "ResetSceneItem", WSRequestHandler::HandleResetSceneItem },
|
||||
{ "DeleteSceneItem", WSRequestHandler::HandleDeleteSceneItem },
|
||||
{ "DuplicateSceneItem", WSRequestHandler::HandleDuplicateSceneItem },
|
||||
{ "ReorderSceneItems", WSRequestHandler::HandleReorderSceneItems },
|
||||
{ "BroadcastCustomMessage", &WSRequestHandler::BroadcastCustomMessage },
|
||||
|
||||
{ "GetStreamingStatus", WSRequestHandler::HandleGetStreamingStatus },
|
||||
{ "StartStopStreaming", WSRequestHandler::HandleStartStopStreaming },
|
||||
{ "StartStopRecording", WSRequestHandler::HandleStartStopRecording },
|
||||
{ "StartStreaming", WSRequestHandler::HandleStartStreaming },
|
||||
{ "StopStreaming", WSRequestHandler::HandleStopStreaming },
|
||||
{ "StartRecording", WSRequestHandler::HandleStartRecording },
|
||||
{ "StopRecording", WSRequestHandler::HandleStopRecording },
|
||||
{ "SetCurrentScene", &WSRequestHandler::SetCurrentScene },
|
||||
{ "GetCurrentScene", &WSRequestHandler::GetCurrentScene },
|
||||
{ "GetSceneList", &WSRequestHandler::GetSceneList },
|
||||
|
||||
{ "StartStopReplayBuffer", WSRequestHandler::HandleStartStopReplayBuffer },
|
||||
{ "StartReplayBuffer", WSRequestHandler::HandleStartReplayBuffer },
|
||||
{ "StopReplayBuffer", WSRequestHandler::HandleStopReplayBuffer },
|
||||
{ "SaveReplayBuffer", WSRequestHandler::HandleSaveReplayBuffer },
|
||||
{ "SetSourceRender", &WSRequestHandler::SetSceneItemRender }, // Retrocompat
|
||||
{ "SetSceneItemRender", &WSRequestHandler::SetSceneItemRender },
|
||||
{ "SetSceneItemPosition", &WSRequestHandler::SetSceneItemPosition },
|
||||
{ "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 },
|
||||
{ "GetRecordingFolder", WSRequestHandler::HandleGetRecordingFolder },
|
||||
{ "GetStreamingStatus", &WSRequestHandler::GetStreamingStatus },
|
||||
{ "StartStopStreaming", &WSRequestHandler::StartStopStreaming },
|
||||
{ "StartStopRecording", &WSRequestHandler::StartStopRecording },
|
||||
|
||||
{ "GetTransitionList", WSRequestHandler::HandleGetTransitionList },
|
||||
{ "GetCurrentTransition", WSRequestHandler::HandleGetCurrentTransition },
|
||||
{ "SetCurrentTransition", WSRequestHandler::HandleSetCurrentTransition },
|
||||
{ "SetTransitionDuration", WSRequestHandler::HandleSetTransitionDuration },
|
||||
{ "GetTransitionDuration", WSRequestHandler::HandleGetTransitionDuration },
|
||||
{ "StartStreaming", &WSRequestHandler::StartStreaming },
|
||||
{ "StopStreaming", &WSRequestHandler::StopStreaming },
|
||||
|
||||
{ "SetVolume", WSRequestHandler::HandleSetVolume },
|
||||
{ "GetVolume", WSRequestHandler::HandleGetVolume },
|
||||
{ "ToggleMute", WSRequestHandler::HandleToggleMute },
|
||||
{ "SetMute", WSRequestHandler::HandleSetMute },
|
||||
{ "GetMute", WSRequestHandler::HandleGetMute },
|
||||
{ "SetSyncOffset", WSRequestHandler::HandleSetSyncOffset },
|
||||
{ "GetSyncOffset", WSRequestHandler::HandleGetSyncOffset },
|
||||
{ "GetSpecialSources", WSRequestHandler::HandleGetSpecialSources },
|
||||
{ "GetSourcesList", WSRequestHandler::HandleGetSourcesList },
|
||||
{ "GetSourceTypesList", WSRequestHandler::HandleGetSourceTypesList },
|
||||
{ "GetSourceSettings", WSRequestHandler::HandleGetSourceSettings },
|
||||
{ "SetSourceSettings", WSRequestHandler::HandleSetSourceSettings },
|
||||
{ "TakeSourceScreenshot", WSRequestHandler::HandleTakeSourceScreenshot },
|
||||
{ "StartRecording", &WSRequestHandler::StartRecording },
|
||||
{ "StopRecording", &WSRequestHandler::StopRecording },
|
||||
{ "PauseRecording", &WSRequestHandler::PauseRecording },
|
||||
{ "ResumeRecording", &WSRequestHandler::ResumeRecording },
|
||||
|
||||
{ "GetSourceFilters", WSRequestHandler::HandleGetSourceFilters },
|
||||
{ "AddFilterToSource", WSRequestHandler::HandleAddFilterToSource },
|
||||
{ "RemoveFilterFromSource", WSRequestHandler::HandleRemoveFilterFromSource },
|
||||
{ "ReorderSourceFilter", WSRequestHandler::HandleReorderSourceFilter },
|
||||
{ "MoveSourceFilter", WSRequestHandler::HandleMoveSourceFilter },
|
||||
{ "SetSourceFilterSettings", WSRequestHandler::HandleSetSourceFilterSettings },
|
||||
{ "StartStopReplayBuffer", &WSRequestHandler::StartStopReplayBuffer },
|
||||
{ "StartReplayBuffer", &WSRequestHandler::StartReplayBuffer },
|
||||
{ "StopReplayBuffer", &WSRequestHandler::StopReplayBuffer },
|
||||
{ "SaveReplayBuffer", &WSRequestHandler::SaveReplayBuffer },
|
||||
|
||||
{ "SetCurrentSceneCollection", WSRequestHandler::HandleSetCurrentSceneCollection },
|
||||
{ "GetCurrentSceneCollection", WSRequestHandler::HandleGetCurrentSceneCollection },
|
||||
{ "ListSceneCollections", WSRequestHandler::HandleListSceneCollections },
|
||||
{ "SetRecordingFolder", &WSRequestHandler::SetRecordingFolder },
|
||||
{ "GetRecordingFolder", &WSRequestHandler::GetRecordingFolder },
|
||||
|
||||
{ "SetCurrentProfile", WSRequestHandler::HandleSetCurrentProfile },
|
||||
{ "GetCurrentProfile", WSRequestHandler::HandleGetCurrentProfile },
|
||||
{ "ListProfiles", WSRequestHandler::HandleListProfiles },
|
||||
{ "GetTransitionList", &WSRequestHandler::GetTransitionList },
|
||||
{ "GetCurrentTransition", &WSRequestHandler::GetCurrentTransition },
|
||||
{ "SetCurrentTransition", &WSRequestHandler::SetCurrentTransition },
|
||||
{ "SetTransitionDuration", &WSRequestHandler::SetTransitionDuration },
|
||||
{ "GetTransitionDuration", &WSRequestHandler::GetTransitionDuration },
|
||||
|
||||
{ "SetStreamSettings", WSRequestHandler::HandleSetStreamSettings },
|
||||
{ "GetStreamSettings", WSRequestHandler::HandleGetStreamSettings },
|
||||
{ "SaveStreamSettings", WSRequestHandler::HandleSaveStreamSettings },
|
||||
{ "SetVolume", &WSRequestHandler::SetVolume },
|
||||
{ "GetVolume", &WSRequestHandler::GetVolume },
|
||||
{ "ToggleMute", &WSRequestHandler::ToggleMute },
|
||||
{ "SetMute", &WSRequestHandler::SetMute },
|
||||
{ "GetMute", &WSRequestHandler::GetMute },
|
||||
{ "SetSyncOffset", &WSRequestHandler::SetSyncOffset },
|
||||
{ "GetSyncOffset", &WSRequestHandler::GetSyncOffset },
|
||||
{ "GetSpecialSources", &WSRequestHandler::GetSpecialSources },
|
||||
{ "GetSourcesList", &WSRequestHandler::GetSourcesList },
|
||||
{ "GetSourceTypesList", &WSRequestHandler::GetSourceTypesList },
|
||||
{ "GetSourceSettings", &WSRequestHandler::GetSourceSettings },
|
||||
{ "SetSourceSettings", &WSRequestHandler::SetSourceSettings },
|
||||
{ "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
|
||||
{ "SendCaptions", WSRequestHandler::HandleSendCaptions },
|
||||
{ "SendCaptions", &WSRequestHandler::SendCaptions },
|
||||
#endif
|
||||
|
||||
{ "GetStudioModeStatus", WSRequestHandler::HandleGetStudioModeStatus },
|
||||
{ "GetPreviewScene", WSRequestHandler::HandleGetPreviewScene },
|
||||
{ "SetPreviewScene", WSRequestHandler::HandleSetPreviewScene },
|
||||
{ "TransitionToProgram", WSRequestHandler::HandleTransitionToProgram },
|
||||
{ "EnableStudioMode", WSRequestHandler::HandleEnableStudioMode },
|
||||
{ "DisableStudioMode", WSRequestHandler::HandleDisableStudioMode },
|
||||
{ "ToggleStudioMode", WSRequestHandler::HandleToggleStudioMode },
|
||||
{ "GetStudioModeStatus", &WSRequestHandler::GetStudioModeStatus },
|
||||
{ "GetPreviewScene", &WSRequestHandler::GetPreviewScene },
|
||||
{ "SetPreviewScene", &WSRequestHandler::SetPreviewScene },
|
||||
{ "TransitionToProgram", &WSRequestHandler::TransitionToProgram },
|
||||
{ "EnableStudioMode", &WSRequestHandler::EnableStudioMode },
|
||||
{ "DisableStudioMode", &WSRequestHandler::DisableStudioMode },
|
||||
{ "ToggleStudioMode", &WSRequestHandler::ToggleStudioMode },
|
||||
|
||||
{ "SetTextGDIPlusProperties", WSRequestHandler::HandleSetTextGDIPlusProperties },
|
||||
{ "GetTextGDIPlusProperties", WSRequestHandler::HandleGetTextGDIPlusProperties },
|
||||
{ "SetTextGDIPlusProperties", &WSRequestHandler::SetTextGDIPlusProperties },
|
||||
{ "GetTextGDIPlusProperties", &WSRequestHandler::GetTextGDIPlusProperties },
|
||||
|
||||
{ "SetTextFreetype2Properties", WSRequestHandler::HandleSetTextFreetype2Properties },
|
||||
{ "GetTextFreetype2Properties", WSRequestHandler::HandleGetTextFreetype2Properties },
|
||||
{ "SetTextFreetype2Properties", &WSRequestHandler::SetTextFreetype2Properties },
|
||||
{ "GetTextFreetype2Properties", &WSRequestHandler::GetTextFreetype2Properties },
|
||||
|
||||
{ "GetBrowserSourceProperties", WSRequestHandler::HandleGetBrowserSourceProperties },
|
||||
{ "SetBrowserSourceProperties", WSRequestHandler::HandleSetBrowserSourceProperties }
|
||||
{ "GetBrowserSourceProperties", &WSRequestHandler::GetBrowserSourceProperties },
|
||||
{ "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",
|
||||
"GetAuthRequired",
|
||||
"Authenticate"
|
||||
};
|
||||
|
||||
WSRequestHandler::WSRequestHandler(ConnectionProperties& connProperties) :
|
||||
_messageId(0),
|
||||
_requestType(""),
|
||||
data(nullptr),
|
||||
_connProperties(connProperties)
|
||||
{
|
||||
}
|
||||
|
||||
std::string WSRequestHandler::processIncomingMessage(std::string& textMessage) {
|
||||
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");
|
||||
|
||||
RpcResponse WSRequestHandler::processRequest(const RpcRequest& request){
|
||||
if (GetConfig()->AuthRequired
|
||||
&& (!authNotRequired.contains(_requestType))
|
||||
&& (!authNotRequired.contains(request.methodName()))
|
||||
&& (!_connProperties.isAuthenticated()))
|
||||
{
|
||||
return SendErrorResponse("Not Authenticated");
|
||||
return RpcResponse::fail(request, "Not Authenticated");
|
||||
}
|
||||
|
||||
HandlerResponse (*handlerFunc)(WSRequestHandler*) = (messageMap[_requestType]);
|
||||
RpcMethodHandler handlerFunc = messageMap[request.methodName()];
|
||||
if (!handlerFunc) {
|
||||
return SendErrorResponse("invalid request type");
|
||||
return RpcResponse::fail(request, "invalid request type");
|
||||
}
|
||||
|
||||
return handlerFunc(this);
|
||||
}
|
||||
|
||||
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());
|
||||
return std::bind(handlerFunc, this, _1)(request);
|
||||
}
|
||||
|
@ -19,145 +19,147 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QSet>
|
||||
#include <QtCore/QVariantHash>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QSharedPointer>
|
||||
|
||||
#include <obs.hpp>
|
||||
#include <obs-frontend-api.h>
|
||||
|
||||
#include "ConnectionProperties.h"
|
||||
|
||||
#include "rpc/RpcRequest.h"
|
||||
#include "rpc/RpcResponse.h"
|
||||
|
||||
#include "obs-websocket.h"
|
||||
|
||||
typedef obs_data_t* HandlerResponse;
|
||||
|
||||
class WSRequestHandler : public QObject {
|
||||
Q_OBJECT
|
||||
class WSRequestHandler;
|
||||
typedef RpcResponse(WSRequestHandler::*RpcMethodHandler)(const RpcRequest&);
|
||||
|
||||
class WSRequestHandler {
|
||||
public:
|
||||
explicit WSRequestHandler(ConnectionProperties& connProperties);
|
||||
~WSRequestHandler();
|
||||
std::string processIncomingMessage(std::string& textMessage);
|
||||
bool hasField(QString name);
|
||||
RpcResponse processRequest(const RpcRequest& textMessage);
|
||||
|
||||
private:
|
||||
const char* _messageId;
|
||||
const char* _requestType;
|
||||
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);
|
||||
HandlerResponse SendErrorResponse(const char* errorMessage);
|
||||
HandlerResponse SendErrorResponse(obs_data_t* additionalFields = nullptr);
|
||||
HandlerResponse SendResponse(const char* status, obs_data_t* additionalFields = nullptr);
|
||||
RpcResponse GetVersion(const RpcRequest&);
|
||||
RpcResponse GetAuthRequired(const RpcRequest&);
|
||||
RpcResponse Authenticate(const RpcRequest&);
|
||||
|
||||
static QHash<QString, HandlerResponse(*)(WSRequestHandler*)> messageMap;
|
||||
static QSet<QString> authNotRequired;
|
||||
RpcResponse GetStats(const RpcRequest&);
|
||||
RpcResponse SetHeartbeat(const RpcRequest&);
|
||||
RpcResponse GetVideoInfo(const RpcRequest&);
|
||||
RpcResponse OpenProjector(const RpcRequest&);
|
||||
|
||||
static HandlerResponse HandleGetVersion(WSRequestHandler* req);
|
||||
static HandlerResponse HandleGetAuthRequired(WSRequestHandler* req);
|
||||
static HandlerResponse HandleAuthenticate(WSRequestHandler* req);
|
||||
RpcResponse SetFilenameFormatting(const RpcRequest&);
|
||||
RpcResponse GetFilenameFormatting(const RpcRequest&);
|
||||
|
||||
static HandlerResponse HandleGetStats(WSRequestHandler* req);
|
||||
static HandlerResponse HandleSetHeartbeat(WSRequestHandler* req);
|
||||
static HandlerResponse HandleGetVideoInfo(WSRequestHandler* req);
|
||||
RpcResponse BroadcastCustomMessage(const RpcRequest&);
|
||||
|
||||
static HandlerResponse HandleSetFilenameFormatting(WSRequestHandler* req);
|
||||
static HandlerResponse HandleGetFilenameFormatting(WSRequestHandler* req);
|
||||
RpcResponse SetCurrentScene(const RpcRequest&);
|
||||
RpcResponse GetCurrentScene(const RpcRequest&);
|
||||
RpcResponse GetSceneList(const RpcRequest&);
|
||||
|
||||
static HandlerResponse HandleSetCurrentScene(WSRequestHandler* req);
|
||||
static HandlerResponse HandleGetCurrentScene(WSRequestHandler* req);
|
||||
static HandlerResponse HandleGetSceneList(WSRequestHandler* req);
|
||||
RpcResponse SetSceneItemRender(const RpcRequest&);
|
||||
RpcResponse SetSceneItemPosition(const RpcRequest&);
|
||||
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);
|
||||
static HandlerResponse HandleSetSceneItemPosition(WSRequestHandler* req);
|
||||
static HandlerResponse HandleSetSceneItemTransform(WSRequestHandler* req);
|
||||
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);
|
||||
RpcResponse GetStreamingStatus(const RpcRequest&);
|
||||
RpcResponse StartStopStreaming(const RpcRequest&);
|
||||
RpcResponse StartStopRecording(const RpcRequest&);
|
||||
|
||||
static HandlerResponse HandleGetStreamingStatus(WSRequestHandler* req);
|
||||
static HandlerResponse HandleStartStopStreaming(WSRequestHandler* req);
|
||||
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);
|
||||
RpcResponse StartStreaming(const RpcRequest&);
|
||||
RpcResponse StopStreaming(const RpcRequest&);
|
||||
|
||||
static HandlerResponse HandleStartStopReplayBuffer(WSRequestHandler* req);
|
||||
static HandlerResponse HandleStartReplayBuffer(WSRequestHandler* req);
|
||||
static HandlerResponse HandleStopReplayBuffer(WSRequestHandler* req);
|
||||
static HandlerResponse HandleSaveReplayBuffer(WSRequestHandler* req);
|
||||
RpcResponse StartRecording(const RpcRequest&);
|
||||
RpcResponse StopRecording(const RpcRequest&);
|
||||
RpcResponse PauseRecording(const RpcRequest&);
|
||||
RpcResponse ResumeRecording(const RpcRequest&);
|
||||
|
||||
static HandlerResponse HandleSetRecordingFolder(WSRequestHandler* req);
|
||||
static HandlerResponse HandleGetRecordingFolder(WSRequestHandler* req);
|
||||
RpcResponse StartStopReplayBuffer(const RpcRequest&);
|
||||
RpcResponse StartReplayBuffer(const RpcRequest&);
|
||||
RpcResponse StopReplayBuffer(const RpcRequest&);
|
||||
RpcResponse SaveReplayBuffer(const RpcRequest&);
|
||||
|
||||
static HandlerResponse HandleGetTransitionList(WSRequestHandler* req);
|
||||
static HandlerResponse HandleGetCurrentTransition(WSRequestHandler* req);
|
||||
static HandlerResponse HandleSetCurrentTransition(WSRequestHandler* req);
|
||||
RpcResponse SetRecordingFolder(const RpcRequest&);
|
||||
RpcResponse GetRecordingFolder(const RpcRequest&);
|
||||
|
||||
static HandlerResponse HandleSetVolume(WSRequestHandler* req);
|
||||
static HandlerResponse HandleGetVolume(WSRequestHandler* req);
|
||||
static HandlerResponse HandleToggleMute(WSRequestHandler* req);
|
||||
static HandlerResponse HandleSetMute(WSRequestHandler* req);
|
||||
static HandlerResponse HandleGetMute(WSRequestHandler* req);
|
||||
static HandlerResponse HandleSetSyncOffset(WSRequestHandler* req);
|
||||
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);
|
||||
RpcResponse GetTransitionList(const RpcRequest&);
|
||||
RpcResponse GetCurrentTransition(const RpcRequest&);
|
||||
RpcResponse SetCurrentTransition(const RpcRequest&);
|
||||
|
||||
static HandlerResponse HandleGetSourceFilters(WSRequestHandler* req);
|
||||
static HandlerResponse HandleAddFilterToSource(WSRequestHandler* req);
|
||||
static HandlerResponse HandleRemoveFilterFromSource(WSRequestHandler* req);
|
||||
static HandlerResponse HandleReorderSourceFilter(WSRequestHandler* req);
|
||||
static HandlerResponse HandleMoveSourceFilter(WSRequestHandler* req);
|
||||
static HandlerResponse HandleSetSourceFilterSettings(WSRequestHandler* req);
|
||||
RpcResponse SetVolume(const RpcRequest&);
|
||||
RpcResponse GetVolume(const RpcRequest&);
|
||||
RpcResponse ToggleMute(const RpcRequest&);
|
||||
RpcResponse SetMute(const RpcRequest&);
|
||||
RpcResponse GetMute(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 TakeSourceScreenshot(const RpcRequest&);
|
||||
|
||||
static HandlerResponse HandleSetCurrentSceneCollection(WSRequestHandler* req);
|
||||
static HandlerResponse HandleGetCurrentSceneCollection(WSRequestHandler* req);
|
||||
static HandlerResponse HandleListSceneCollections(WSRequestHandler* req);
|
||||
RpcResponse GetSourceFilters(const RpcRequest&);
|
||||
RpcResponse GetSourceFilterInfo(const RpcRequest&);
|
||||
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);
|
||||
static HandlerResponse HandleGetCurrentProfile(WSRequestHandler* req);
|
||||
static HandlerResponse HandleListProfiles(WSRequestHandler* req);
|
||||
RpcResponse SetCurrentSceneCollection(const RpcRequest&);
|
||||
RpcResponse GetCurrentSceneCollection(const RpcRequest&);
|
||||
RpcResponse ListSceneCollections(const RpcRequest&);
|
||||
|
||||
static HandlerResponse HandleSetStreamSettings(WSRequestHandler* req);
|
||||
static HandlerResponse HandleGetStreamSettings(WSRequestHandler* req);
|
||||
static HandlerResponse HandleSaveStreamSettings(WSRequestHandler* req);
|
||||
RpcResponse SetCurrentProfile(const RpcRequest&);
|
||||
RpcResponse GetCurrentProfile(const RpcRequest&);
|
||||
RpcResponse ListProfiles(const RpcRequest&);
|
||||
|
||||
RpcResponse SetStreamSettings(const RpcRequest&);
|
||||
RpcResponse GetStreamSettings(const RpcRequest&);
|
||||
RpcResponse SaveStreamSettings(const RpcRequest&);
|
||||
#if BUILD_CAPTIONS
|
||||
static HandlerResponse HandleSendCaptions(WSRequestHandler * req);
|
||||
RpcResponse SendCaptions(const RpcRequest&);
|
||||
#endif
|
||||
|
||||
static HandlerResponse HandleSetTransitionDuration(WSRequestHandler* req);
|
||||
static HandlerResponse HandleGetTransitionDuration(WSRequestHandler* req);
|
||||
RpcResponse SetTransitionDuration(const RpcRequest&);
|
||||
RpcResponse GetTransitionDuration(const RpcRequest&);
|
||||
|
||||
static HandlerResponse HandleGetStudioModeStatus(WSRequestHandler* req);
|
||||
static HandlerResponse HandleGetPreviewScene(WSRequestHandler* req);
|
||||
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);
|
||||
RpcResponse GetStudioModeStatus(const RpcRequest&);
|
||||
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 HandleSetTextGDIPlusProperties(WSRequestHandler* req);
|
||||
static HandlerResponse HandleGetTextGDIPlusProperties(WSRequestHandler* req);
|
||||
RpcResponse SetTextGDIPlusProperties(const RpcRequest&);
|
||||
RpcResponse GetTextGDIPlusProperties(const RpcRequest&);
|
||||
|
||||
static HandlerResponse HandleSetTextFreetype2Properties(WSRequestHandler* req);
|
||||
static HandlerResponse HandleGetTextFreetype2Properties(WSRequestHandler* req);
|
||||
RpcResponse SetTextFreetype2Properties(const RpcRequest&);
|
||||
RpcResponse GetTextFreetype2Properties(const RpcRequest&);
|
||||
|
||||
static HandlerResponse HandleSetBrowserSourceProperties(WSRequestHandler* req);
|
||||
static HandlerResponse HandleGetBrowserSourceProperties(WSRequestHandler* req);
|
||||
RpcResponse SetBrowserSourceProperties(const RpcRequest&);
|
||||
RpcResponse GetBrowserSourceProperties(const RpcRequest&);
|
||||
|
||||
RpcResponse ListOutputs(const RpcRequest&);
|
||||
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 "Config.h"
|
||||
#include "Utils.h"
|
||||
#include "WSEvents.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
#define CASE(x) case x: return #x;
|
||||
const char *describe_output_format(int 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-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} `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
|
||||
* @name GetVersion
|
||||
* @category general
|
||||
* @since 0.3
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetVersion(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::GetVersion(const RpcRequest& request) {
|
||||
QString obsVersion = Utils::OBSVersionString();
|
||||
|
||||
QList<QString> names = req->messageMap.keys();
|
||||
names.sort(Qt::CaseInsensitive);
|
||||
QList<QString> names = messageMap.keys();
|
||||
QList<QByteArray> imageWriterFormats = QImageWriter::supportedImageFormats();
|
||||
|
||||
// (Palakis) OBS' data arrays only support object arrays, so I improvised.
|
||||
QString requests;
|
||||
names.sort(Qt::CaseInsensitive);
|
||||
requests += names.takeFirst();
|
||||
for (QString reqName : names) {
|
||||
for (const QString& reqName : names) {
|
||||
requests += ("," + reqName);
|
||||
}
|
||||
|
||||
QString supportedImageExportFormats;
|
||||
supportedImageExportFormats += QString::fromUtf8(imageWriterFormats.takeFirst());
|
||||
for (const QByteArray& format : imageWriterFormats) {
|
||||
supportedImageExportFormats += ("," + QString::fromUtf8(format));
|
||||
}
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
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-studio-version", obsVersion.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
|
||||
* @since 0.3
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetAuthRequired(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::GetAuthRequired(const RpcRequest& request) {
|
||||
bool authRequired = GetConfig()->AuthRequired;
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
@ -115,7 +127,7 @@ HandlerResponse WSRequestHandler::HandleGetAuthRequired(WSRequestHandler* req) {
|
||||
config->Salt.toUtf8());
|
||||
}
|
||||
|
||||
return req->SendOKResponse(data);
|
||||
return request.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -128,26 +140,26 @@ HandlerResponse WSRequestHandler::HandleGetAuthRequired(WSRequestHandler* req) {
|
||||
* @category general
|
||||
* @since 0.3
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleAuthenticate(WSRequestHandler* req) {
|
||||
if (!req->hasField("auth")) {
|
||||
return req->SendErrorResponse("missing request parameters");
|
||||
RpcResponse WSRequestHandler::Authenticate(const RpcRequest& request) {
|
||||
if (!request.hasField("auth")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
if (req->_connProperties.isAuthenticated()) {
|
||||
return req->SendErrorResponse("already authenticated");
|
||||
if (_connProperties.isAuthenticated()) {
|
||||
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()) {
|
||||
return req->SendErrorResponse("auth not specified!");
|
||||
return request.failed("auth not specified!");
|
||||
}
|
||||
|
||||
if (GetConfig()->CheckAuth(auth) == false) {
|
||||
return req->SendErrorResponse("Authentication Failed.");
|
||||
return request.failed("Authentication Failed.");
|
||||
}
|
||||
|
||||
req->_connProperties.setAuthenticated(true);
|
||||
return req->SendOKResponse();
|
||||
_connProperties.setAuthenticated(true);
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -160,17 +172,18 @@ HandlerResponse WSRequestHandler::HandleAuthenticate(WSRequestHandler* req) {
|
||||
* @category general
|
||||
* @since 4.3.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleSetHeartbeat(WSRequestHandler* req) {
|
||||
if (!req->hasField("enable")) {
|
||||
return req->SendErrorResponse("Heartbeat <enable> parameter missing");
|
||||
RpcResponse WSRequestHandler::SetHeartbeat(const RpcRequest& request) {
|
||||
if (!request.hasField("enable")) {
|
||||
return request.failed("Heartbeat <enable> parameter missing");
|
||||
}
|
||||
|
||||
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();
|
||||
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
|
||||
* @since 4.3.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleSetFilenameFormatting(WSRequestHandler* req) {
|
||||
if (!req->hasField("filename-formatting")) {
|
||||
return req->SendErrorResponse("<filename-formatting> parameter missing");
|
||||
RpcResponse WSRequestHandler::SetFilenameFormatting(const RpcRequest& request) {
|
||||
if (!request.hasField("filename-formatting")) {
|
||||
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()) {
|
||||
return req->SendErrorResponse("invalid request parameters");
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
Utils::SetFilenameFormatting(filenameFormatting.toUtf8());
|
||||
return req->SendOKResponse();
|
||||
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -207,10 +221,11 @@ HandlerResponse WSRequestHandler::HandleSetFilenameFormatting(WSRequestHandler*
|
||||
* @category general
|
||||
* @since 4.3.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetFilenameFormatting(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::GetFilenameFormatting(const RpcRequest& request) {
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
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
|
||||
* @since 4.6.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetStats(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::GetStats(const RpcRequest& request) {
|
||||
OBSDataAutoRelease stats = GetEventsSystem()->GetStats();
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
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
|
||||
*
|
||||
@ -249,9 +299,10 @@ HandlerResponse WSRequestHandler::HandleGetStats(WSRequestHandler* req) {
|
||||
* @category general
|
||||
* @since 4.6.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetVideoInfo(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::GetVideoInfo(const RpcRequest& request) {
|
||||
obs_video_info ovi;
|
||||
obs_get_video_info(&ovi);
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_int(response, "baseWidth", ovi.base_width);
|
||||
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, "colorRange", describe_color_range(ovi.range));
|
||||
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 unreleased
|
||||
*/
|
||||
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
|
||||
* @since 4.0.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleSetCurrentProfile(WSRequestHandler* req) {
|
||||
if (!req->hasField("profile-name")) {
|
||||
return req->SendErrorResponse("missing request parameters");
|
||||
RpcResponse WSRequestHandler::SetCurrentProfile(const RpcRequest& request) {
|
||||
if (!request.hasField("profile-name")) {
|
||||
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()) {
|
||||
return req->SendErrorResponse("invalid request parameters");
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
// TODO : check if profile exists
|
||||
obs_frontend_set_current_profile(profileName.toUtf8());
|
||||
return req->SendOKResponse();
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,10 +37,12 @@ HandlerResponse WSRequestHandler::HandleSetCurrentProfile(WSRequestHandler* req)
|
||||
* @category profiles
|
||||
* @since 4.0.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetCurrentProfile(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::GetCurrentProfile(const RpcRequest& request) {
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_string(response, "profile-name", obs_frontend_get_current_profile());
|
||||
return req->SendOKResponse(response);
|
||||
char* currentProfile = obs_frontend_get_current_profile();
|
||||
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
|
||||
* @since 4.0.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleListProfiles(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::ListProfiles(const RpcRequest& request) {
|
||||
char** profiles = obs_frontend_get_profiles();
|
||||
OBSDataArrayAutoRelease list = Utils::StringListToArray(profiles, "profile-name");
|
||||
bfree(profiles);
|
||||
@ -61,5 +63,5 @@ HandlerResponse WSRequestHandler::HandleListProfiles(WSRequestHandler* req) {
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
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 "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.
|
||||
@ -10,13 +21,9 @@
|
||||
* @category recording
|
||||
* @since 0.3
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleStartStopRecording(WSRequestHandler* req) {
|
||||
if (obs_frontend_recording_active())
|
||||
obs_frontend_recording_stop();
|
||||
else
|
||||
obs_frontend_recording_start();
|
||||
|
||||
return req->SendOKResponse();
|
||||
RpcResponse WSRequestHandler::StartStopRecording(const RpcRequest& request) {
|
||||
(obs_frontend_recording_active() ? obs_frontend_recording_stop() : obs_frontend_recording_start());
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -28,13 +35,13 @@ HandlerResponse WSRequestHandler::HandleStartStopRecording(WSRequestHandler* req
|
||||
* @category recording
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
|
||||
if (obs_frontend_recording_active() == false) {
|
||||
obs_frontend_recording_start();
|
||||
return req->SendOKResponse();
|
||||
} else {
|
||||
return req->SendErrorResponse("recording already active");
|
||||
RpcResponse WSRequestHandler::StartRecording(const RpcRequest& request) {
|
||||
if (obs_frontend_recording_active()) {
|
||||
return request.failed("recording already active");
|
||||
}
|
||||
|
||||
obs_frontend_recording_start();
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,13 +53,53 @@ HandlerResponse WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
|
||||
* @category recording
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleStopRecording(WSRequestHandler* req) {
|
||||
if (obs_frontend_recording_active() == true) {
|
||||
obs_frontend_recording_stop();
|
||||
return req->SendOKResponse();
|
||||
} else {
|
||||
return req->SendErrorResponse("recording not active");
|
||||
RpcResponse WSRequestHandler::StopRecording(const RpcRequest& request) {
|
||||
if (!obs_frontend_recording_active()) {
|
||||
return request.failed("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
|
||||
* effective on the next recording.
|
||||
*
|
||||
*
|
||||
* @param {String} `rec-folder` Path of the recording folder.
|
||||
*
|
||||
* @api requests
|
||||
@ -71,18 +117,18 @@ HandlerResponse WSRequestHandler::HandleStartRecording(WSRequestHandler* req) {
|
||||
* @category recording
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleSetRecordingFolder(WSRequestHandler* req) {
|
||||
if (!req->hasField("rec-folder")) {
|
||||
return req->SendErrorResponse("missing request parameters");
|
||||
RpcResponse WSRequestHandler::SetRecordingFolder(const RpcRequest& request) {
|
||||
if (!request.hasField("rec-folder")) {
|
||||
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);
|
||||
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
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetRecordingFolder(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::GetRecordingFolder(const RpcRequest& request) {
|
||||
const char* recFolder = Utils::GetRecordingFolder();
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_string(response, "rec-folder", recFolder);
|
||||
|
||||
return req->SendOKResponse(response);
|
||||
return request.success(response);
|
||||
}
|
||||
|
@ -10,13 +10,13 @@
|
||||
* @category replay buffer
|
||||
* @since 4.2.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleStartStopReplayBuffer(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::StartStopReplayBuffer(const RpcRequest& request) {
|
||||
if (obs_frontend_replay_buffer_active()) {
|
||||
obs_frontend_replay_buffer_stop();
|
||||
} else {
|
||||
Utils::StartReplayBuffer();
|
||||
}
|
||||
return req->SendOKResponse();
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -31,17 +31,17 @@ HandlerResponse WSRequestHandler::HandleStartStopReplayBuffer(WSRequestHandler*
|
||||
* @category replay buffer
|
||||
* @since 4.2.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleStartReplayBuffer(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::StartReplayBuffer(const RpcRequest& request) {
|
||||
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) {
|
||||
return req->SendErrorResponse("replay buffer already active");
|
||||
return request.failed("replay buffer already active");
|
||||
}
|
||||
|
||||
Utils::StartReplayBuffer();
|
||||
return req->SendOKResponse();
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -53,12 +53,12 @@ HandlerResponse WSRequestHandler::HandleStartReplayBuffer(WSRequestHandler* req)
|
||||
* @category replay buffer
|
||||
* @since 4.2.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleStopReplayBuffer(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::StopReplayBuffer(const RpcRequest& request) {
|
||||
if (obs_frontend_replay_buffer_active() == true) {
|
||||
obs_frontend_replay_buffer_stop();
|
||||
return req->SendOKResponse();
|
||||
return request.success();
|
||||
} 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
|
||||
* @since 4.2.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleSaveReplayBuffer(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::SaveReplayBuffer(const RpcRequest& request) {
|
||||
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();
|
||||
@ -84,5 +84,5 @@ HandlerResponse WSRequestHandler::HandleSaveReplayBuffer(WSRequestHandler* req)
|
||||
proc_handler_call(ph, "save", &cd);
|
||||
calldata_free(&cd);
|
||||
|
||||
return req->SendOKResponse();
|
||||
return request.success();
|
||||
}
|
||||
|
@ -12,19 +12,19 @@
|
||||
* @category scene collections
|
||||
* @since 4.0.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleSetCurrentSceneCollection(WSRequestHandler* req) {
|
||||
if (!req->hasField("sc-name")) {
|
||||
return req->SendErrorResponse("missing request parameters");
|
||||
RpcResponse WSRequestHandler::SetCurrentSceneCollection(const RpcRequest& request) {
|
||||
if (!request.hasField("sc-name")) {
|
||||
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()) {
|
||||
return req->SendErrorResponse("invalid request parameters");
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
// TODO : Check if specified profile exists and if changing is allowed
|
||||
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
|
||||
* @since 4.0.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetCurrentSceneCollection(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::GetCurrentSceneCollection(const RpcRequest& request) {
|
||||
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
|
||||
* @since 4.0.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleListSceneCollections(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::ListSceneCollections(const RpcRequest& request) {
|
||||
char** sceneCollections = obs_frontend_get_scene_collections();
|
||||
OBSDataArrayAutoRelease list =
|
||||
Utils::StringListToArray(sceneCollections, "sc-name");
|
||||
@ -64,5 +66,5 @@ HandlerResponse WSRequestHandler::HandleListSceneCollections(WSRequestHandler* r
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_array(response, "scene-collections", list);
|
||||
|
||||
return req->SendOKResponse(response);
|
||||
return request.success(response);
|
||||
}
|
||||
|
@ -21,6 +21,7 @@
|
||||
* @return {int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling.
|
||||
* @return {int} `crop.left` The number of pixels cropped off the left of the source before scaling.
|
||||
* @return {bool} `visible` If the source is visible.
|
||||
* @return {bool} `muted` If the source is muted.
|
||||
* @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 {int} `bounds.alignment` Alignment of the bounding box.
|
||||
@ -30,40 +31,40 @@
|
||||
* @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} `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)
|
||||
* @property {Array<SceneItemTransform> (optional)} `groupChildren` List of children (if this item is 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.
|
||||
* @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
|
||||
* @name GetSceneItemProperties
|
||||
* @category scene items
|
||||
* @since 4.3.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetSceneItemProperties(WSRequestHandler* req) {
|
||||
if (!req->hasField("item")) {
|
||||
return req->SendErrorResponse("missing request parameters");
|
||||
RpcResponse WSRequestHandler::GetSceneItemProperties(const RpcRequest& request) {
|
||||
if (!request.hasField("item")) {
|
||||
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()) {
|
||||
return req->SendErrorResponse("invalid request parameters");
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
QString sceneName = obs_data_get_string(request.parameters(), "scene-name");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
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) {
|
||||
return req->SendErrorResponse("specified scene item doesn't exist");
|
||||
return request.failed("specified scene item doesn't exist");
|
||||
}
|
||||
|
||||
OBSDataAutoRelease data = Utils::GetSceneItemPropertiesData(sceneItem);
|
||||
obs_data_set_string(data, "name", itemName.toUtf8());
|
||||
|
||||
return req->SendOKResponse(data);
|
||||
return request.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -94,38 +95,38 @@ HandlerResponse WSRequestHandler::HandleGetSceneItemProperties(WSRequestHandler*
|
||||
* @category scene items
|
||||
* @since 4.3.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler* req) {
|
||||
if (!req->hasField("item")) {
|
||||
return req->SendErrorResponse("missing request parameters");
|
||||
RpcResponse WSRequestHandler::SetSceneItemProperties(const RpcRequest& request) {
|
||||
if (!request.hasField("item")) {
|
||||
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()) {
|
||||
return req->SendErrorResponse("invalid request parameters");
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
QString sceneName = obs_data_get_string(request.parameters(), "scene-name");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
return req->SendErrorResponse("requested scene doesn't exist");
|
||||
return request.failed("requested scene doesn't exist");
|
||||
}
|
||||
|
||||
OBSSceneItemAutoRelease sceneItem =
|
||||
Utils::GetSceneItemFromName(scene, itemName);
|
||||
if (!sceneItem) {
|
||||
return req->SendErrorResponse("specified scene item doesn't exist");
|
||||
return request.failed("specified scene item doesn't exist");
|
||||
}
|
||||
|
||||
bool badRequest = false;
|
||||
OBSDataAutoRelease errorMessage = obs_data_create();
|
||||
OBSDataAutoRelease errorData = obs_data_create();
|
||||
|
||||
obs_sceneitem_defer_update_begin(sceneItem);
|
||||
|
||||
if (req->hasField("position")) {
|
||||
if (request.hasField("position")) {
|
||||
vec2 oldPosition;
|
||||
OBSDataAutoRelease positionError = obs_data_create();
|
||||
obs_sceneitem_get_pos(sceneItem, &oldPosition);
|
||||
OBSDataAutoRelease reqPosition = obs_data_get_obj(req->data, "position");
|
||||
OBSDataAutoRelease reqPosition = obs_data_get_obj(request.parameters(), "position");
|
||||
vec2 newPosition = oldPosition;
|
||||
if (obs_data_has_user_value(reqPosition, "x")) {
|
||||
newPosition.x = obs_data_get_int(reqPosition, "x");
|
||||
@ -141,20 +142,20 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler*
|
||||
else {
|
||||
badRequest = true;
|
||||
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);
|
||||
}
|
||||
|
||||
if (req->hasField("rotation")) {
|
||||
obs_sceneitem_set_rot(sceneItem, (float)obs_data_get_double(req->data, "rotation"));
|
||||
if (request.hasField("rotation")) {
|
||||
obs_sceneitem_set_rot(sceneItem, (float)obs_data_get_double(request.parameters(), "rotation"));
|
||||
}
|
||||
|
||||
if (req->hasField("scale")) {
|
||||
if (request.hasField("scale")) {
|
||||
vec2 oldScale;
|
||||
obs_sceneitem_get_scale(sceneItem, &oldScale);
|
||||
OBSDataAutoRelease reqScale = obs_data_get_obj(req->data, "scale");
|
||||
OBSDataAutoRelease reqScale = obs_data_get_obj(request.parameters(), "scale");
|
||||
vec2 newScale = oldScale;
|
||||
if (obs_data_has_user_value(reqScale, "x")) {
|
||||
newScale.x = obs_data_get_double(reqScale, "x");
|
||||
@ -165,10 +166,10 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler*
|
||||
obs_sceneitem_set_scale(sceneItem, &newScale);
|
||||
}
|
||||
|
||||
if (req->hasField("crop")) {
|
||||
if (request.hasField("crop")) {
|
||||
obs_sceneitem_crop oldCrop;
|
||||
obs_sceneitem_get_crop(sceneItem, &oldCrop);
|
||||
OBSDataAutoRelease reqCrop = obs_data_get_obj(req->data, "crop");
|
||||
OBSDataAutoRelease reqCrop = obs_data_get_obj(request.parameters(), "crop");
|
||||
obs_sceneitem_crop newCrop = oldCrop;
|
||||
if (obs_data_has_user_value(reqCrop, "top")) {
|
||||
newCrop.top = obs_data_get_int(reqCrop, "top");
|
||||
@ -185,18 +186,18 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler*
|
||||
obs_sceneitem_set_crop(sceneItem, &newCrop);
|
||||
}
|
||||
|
||||
if (req->hasField("visible")) {
|
||||
obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(req->data, "visible"));
|
||||
if (request.hasField("visible")) {
|
||||
obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(request.parameters(), "visible"));
|
||||
}
|
||||
|
||||
if (req->hasField("locked")) {
|
||||
obs_sceneitem_set_locked(sceneItem, obs_data_get_bool(req->data, "locked"));
|
||||
if (request.hasField("locked")) {
|
||||
obs_sceneitem_set_locked(sceneItem, obs_data_get_bool(request.parameters(), "locked"));
|
||||
}
|
||||
|
||||
if (req->hasField("bounds")) {
|
||||
if (request.hasField("bounds")) {
|
||||
bool badBounds = false;
|
||||
OBSDataAutoRelease boundsError = obs_data_create();
|
||||
OBSDataAutoRelease reqBounds = obs_data_get_obj(req->data, "bounds");
|
||||
OBSDataAutoRelease reqBounds = obs_data_get_obj(request.parameters(), "bounds");
|
||||
if (obs_data_has_user_value(reqBounds, "type")) {
|
||||
QString newBoundsType = obs_data_get_string(reqBounds, "type");
|
||||
if (newBoundsType == "OBS_BOUNDS_NONE") {
|
||||
@ -246,17 +247,17 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler*
|
||||
}
|
||||
}
|
||||
if (badBounds) {
|
||||
obs_data_set_obj(errorMessage, "bounds", boundsError);
|
||||
obs_data_set_obj(errorData, "bounds", boundsError);
|
||||
}
|
||||
}
|
||||
|
||||
obs_sceneitem_defer_update_end(sceneItem);
|
||||
|
||||
if (badRequest) {
|
||||
return req->SendErrorResponse(errorMessage);
|
||||
return request.failed("error", errorData);
|
||||
}
|
||||
|
||||
return req->SendOKResponse();
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -270,27 +271,27 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemProperties(WSRequestHandler*
|
||||
* @category scene items
|
||||
* @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 (!req->hasField("item")) {
|
||||
return req->SendErrorResponse("missing request parameters");
|
||||
if (!request.hasField("item")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
const char* itemName = obs_data_get_string(req->data, "item");
|
||||
const char* itemName = obs_data_get_string(request.parameters(), "item");
|
||||
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");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
const char* sceneName = obs_data_get_string(request.parameters(), "scene-name");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
return req->SendErrorResponse("requested scene doesn't exist");
|
||||
return request.failed("requested scene doesn't exist");
|
||||
}
|
||||
|
||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||
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);
|
||||
@ -298,7 +299,7 @@ HandlerResponse WSRequestHandler::HandleResetSceneItem(WSRequestHandler* req) {
|
||||
OBSDataAutoRelease settings = obs_source_get_settings(sceneItemSource);
|
||||
obs_source_update(sceneItemSource, settings);
|
||||
|
||||
return req->SendOKResponse();
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -314,34 +315,34 @@ HandlerResponse WSRequestHandler::HandleResetSceneItem(WSRequestHandler* req) {
|
||||
* @since 0.3
|
||||
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleSetSceneItemRender(WSRequestHandler* req) {
|
||||
if (!req->hasField("source") ||
|
||||
!req->hasField("render"))
|
||||
RpcResponse WSRequestHandler::SetSceneItemRender(const RpcRequest& request) {
|
||||
if (!request.hasField("source") ||
|
||||
!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");
|
||||
bool isVisible = obs_data_get_bool(req->data, "render");
|
||||
const char* itemName = obs_data_get_string(request.parameters(), "source");
|
||||
bool isVisible = obs_data_get_bool(request.parameters(), "render");
|
||||
|
||||
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");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
const char* sceneName = obs_data_get_string(request.parameters(), "scene-name");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
return req->SendErrorResponse("requested scene doesn't exist");
|
||||
return request.failed("requested scene doesn't exist");
|
||||
}
|
||||
|
||||
OBSSceneItemAutoRelease sceneItem =
|
||||
Utils::GetSceneItemFromName(scene, itemName);
|
||||
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);
|
||||
return req->SendOKResponse();
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -359,34 +360,34 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemRender(WSRequestHandler* req
|
||||
* @since 4.0.0
|
||||
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleSetSceneItemPosition(WSRequestHandler* req) {
|
||||
if (!req->hasField("item") ||
|
||||
!req->hasField("x") || !req->hasField("y")) {
|
||||
return req->SendErrorResponse("missing request parameters");
|
||||
RpcResponse WSRequestHandler::SetSceneItemPosition(const RpcRequest& request) {
|
||||
if (!request.hasField("item") ||
|
||||
!request.hasField("x") || !request.hasField("y")) {
|
||||
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()) {
|
||||
return req->SendErrorResponse("invalid request parameters");
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
QString sceneName = obs_data_get_string(request.parameters(), "scene-name");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
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);
|
||||
if (!sceneItem) {
|
||||
return req->SendErrorResponse("specified scene item doesn't exist");
|
||||
return request.failed("specified scene item doesn't exist");
|
||||
}
|
||||
|
||||
vec2 item_position = { 0 };
|
||||
item_position.x = obs_data_get_double(req->data, "x");
|
||||
item_position.y = obs_data_get_double(req->data, "y");
|
||||
item_position.x = obs_data_get_double(request.parameters(), "x");
|
||||
item_position.y = obs_data_get_double(request.parameters(), "y");
|
||||
obs_sceneitem_set_pos(sceneItem, &item_position);
|
||||
|
||||
return req->SendOKResponse();
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -404,34 +405,34 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemPosition(WSRequestHandler* r
|
||||
* @since 4.0.0
|
||||
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler* req) {
|
||||
if (!req->hasField("item") ||
|
||||
!req->hasField("x-scale") ||
|
||||
!req->hasField("y-scale") ||
|
||||
!req->hasField("rotation"))
|
||||
RpcResponse WSRequestHandler::SetSceneItemTransform(const RpcRequest& request) {
|
||||
if (!request.hasField("item") ||
|
||||
!request.hasField("x-scale") ||
|
||||
!request.hasField("y-scale") ||
|
||||
!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()) {
|
||||
return req->SendErrorResponse("invalid request parameters");
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
QString sceneName = obs_data_get_string(request.parameters(), "scene-name");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
return req->SendErrorResponse("requested scene doesn't exist");
|
||||
return request.failed("requested scene doesn't exist");
|
||||
}
|
||||
|
||||
vec2 scale;
|
||||
scale.x = obs_data_get_double(req->data, "x-scale");
|
||||
scale.y = obs_data_get_double(req->data, "y-scale");
|
||||
float rotation = obs_data_get_double(req->data, "rotation");
|
||||
scale.x = obs_data_get_double(request.parameters(), "x-scale");
|
||||
scale.y = obs_data_get_double(request.parameters(), "y-scale");
|
||||
float rotation = obs_data_get_double(request.parameters(), "rotation");
|
||||
|
||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||
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);
|
||||
@ -441,7 +442,7 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler*
|
||||
|
||||
obs_sceneitem_defer_update_end(sceneItem);
|
||||
|
||||
return req->SendOKResponse();
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -460,36 +461,36 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemTransform(WSRequestHandler*
|
||||
* @since 4.1.0
|
||||
* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties.
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleSetSceneItemCrop(WSRequestHandler* req) {
|
||||
if (!req->hasField("item")) {
|
||||
return req->SendErrorResponse("missing request parameters");
|
||||
RpcResponse WSRequestHandler::SetSceneItemCrop(const RpcRequest& request) {
|
||||
if (!request.hasField("item")) {
|
||||
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()) {
|
||||
return req->SendErrorResponse("invalid request parameters");
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
QString sceneName = obs_data_get_string(req->data, "scene-name");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
QString sceneName = obs_data_get_string(request.parameters(), "scene-name");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
if (!scene) {
|
||||
return req->SendErrorResponse("requested scene doesn't exist");
|
||||
return request.failed("requested scene doesn't exist");
|
||||
}
|
||||
|
||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName);
|
||||
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 };
|
||||
crop.top = obs_data_get_int(req->data, "top");
|
||||
crop.bottom = obs_data_get_int(req->data, "bottom");
|
||||
crop.left = obs_data_get_int(req->data, "left");
|
||||
crop.right = obs_data_get_int(req->data, "right");
|
||||
crop.top = obs_data_get_int(request.parameters(), "top");
|
||||
crop.bottom = obs_data_get_int(request.parameters(), "bottom");
|
||||
crop.left = obs_data_get_int(request.parameters(), "left");
|
||||
crop.right = obs_data_get_int(request.parameters(), "right");
|
||||
|
||||
obs_sceneitem_set_crop(sceneItem, &crop);
|
||||
|
||||
return req->SendOKResponse();
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -505,38 +506,26 @@ HandlerResponse WSRequestHandler::HandleSetSceneItemCrop(WSRequestHandler* req)
|
||||
* @category scene items
|
||||
* @since 4.5.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleDeleteSceneItem(WSRequestHandler* req) {
|
||||
if (!req->hasField("item")) {
|
||||
return req->SendErrorResponse("missing request parameters");
|
||||
RpcResponse WSRequestHandler::DeleteSceneItem(const RpcRequest& request) {
|
||||
if (!request.hasField("item")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
const char* sceneName = obs_data_get_string(req->data, "scene");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
const char* sceneName = obs_data_get_string(request.parameters(), "scene");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
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");
|
||||
OBSDataAutoRelease item = obs_data_get_obj(request.parameters(), "item");
|
||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item);
|
||||
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);
|
||||
|
||||
return req->SendOKResponse();
|
||||
}
|
||||
|
||||
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));
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -558,27 +547,33 @@ static void DuplicateSceneItem(void *_data, obs_scene_t *scene) {
|
||||
* @category scene items
|
||||
* @since 4.5.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleDuplicateSceneItem(WSRequestHandler* req) {
|
||||
if (!req->hasField("item")) {
|
||||
return req->SendErrorResponse("missing request parameters");
|
||||
RpcResponse WSRequestHandler::DuplicateSceneItem(const RpcRequest& request) {
|
||||
struct DuplicateSceneItemData {
|
||||
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");
|
||||
OBSSourceAutoRelease fromScene = Utils::GetSceneFromNameOrCurrent(fromSceneName);
|
||||
const char* fromSceneName = obs_data_get_string(request.parameters(), "fromScene");
|
||||
OBSScene fromScene = Utils::GetSceneFromNameOrCurrent(fromSceneName);
|
||||
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");
|
||||
OBSSourceAutoRelease toScene = Utils::GetSceneFromNameOrCurrent(toSceneName);
|
||||
const char* toSceneName = obs_data_get_string(request.parameters(), "toScene");
|
||||
OBSScene toScene = Utils::GetSceneFromNameOrCurrent(toSceneName);
|
||||
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");
|
||||
OBSDataAutoRelease item = obs_data_get_obj(request.parameters(), "item");
|
||||
OBSSceneItemAutoRelease referenceItem = Utils::GetSceneItemFromItem(fromScene, item);
|
||||
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;
|
||||
@ -586,12 +581,16 @@ HandlerResponse WSRequestHandler::HandleDuplicateSceneItem(WSRequestHandler* req
|
||||
data.referenceItem = referenceItem;
|
||||
|
||||
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_sceneitem_t *newItem = data.newItem;
|
||||
if (!newItem) {
|
||||
return req->SendErrorResponse("Error duplicating scene item");
|
||||
return request.failed("Error duplicating scene item");
|
||||
}
|
||||
|
||||
OBSDataAutoRelease itemData = obs_data_create();
|
||||
@ -600,7 +599,7 @@ HandlerResponse WSRequestHandler::HandleDuplicateSceneItem(WSRequestHandler* req
|
||||
|
||||
OBSDataAutoRelease responseData = obs_data_create();
|
||||
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
|
||||
* @since 0.3
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleSetCurrentScene(WSRequestHandler* req) {
|
||||
if (!req->hasField("scene-name")) {
|
||||
return req->SendErrorResponse("missing request parameters");
|
||||
RpcResponse WSRequestHandler::SetCurrentScene(const RpcRequest& request) {
|
||||
if (!request.hasField("scene-name")) {
|
||||
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);
|
||||
|
||||
if (source) {
|
||||
obs_frontend_set_current_scene(source);
|
||||
return req->SendOKResponse();
|
||||
return request.success();
|
||||
} 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
|
||||
* @since 0.3
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetCurrentScene(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::GetCurrentScene(const RpcRequest& request) {
|
||||
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
|
||||
OBSDataArrayAutoRelease sceneItems = Utils::GetSceneItems(currentScene);
|
||||
|
||||
@ -53,7 +53,7 @@ HandlerResponse WSRequestHandler::HandleGetCurrentScene(WSRequestHandler* req) {
|
||||
obs_data_set_string(data, "name", obs_source_get_name(currentScene));
|
||||
obs_data_set_array(data, "sources", sceneItems);
|
||||
|
||||
return req->SendOKResponse(data);
|
||||
return request.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,7 +67,7 @@ HandlerResponse WSRequestHandler::HandleGetCurrentScene(WSRequestHandler* req) {
|
||||
* @category scenes
|
||||
* @since 0.3
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetSceneList(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::GetSceneList(const RpcRequest& request) {
|
||||
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
|
||||
OBSDataArrayAutoRelease scenes = Utils::GetScenes();
|
||||
|
||||
@ -76,7 +76,7 @@ HandlerResponse WSRequestHandler::HandleGetSceneList(WSRequestHandler* req) {
|
||||
obs_source_get_name(currentScene));
|
||||
obs_data_set_array(data, "scenes", scenes);
|
||||
|
||||
return req->SendOKResponse(data);
|
||||
return request.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -92,50 +92,59 @@ HandlerResponse WSRequestHandler::HandleGetSceneList(WSRequestHandler* req) {
|
||||
* @category scenes
|
||||
* @since 4.5.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleReorderSceneItems(WSRequestHandler* req) {
|
||||
QString sceneName = obs_data_get_string(req->data, "scene");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
RpcResponse WSRequestHandler::ReorderSceneItems(const RpcRequest& request) {
|
||||
QString sceneName = obs_data_get_string(request.parameters(), "scene");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName);
|
||||
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) {
|
||||
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;
|
||||
newOrder.reserve(count);
|
||||
struct reorder_context ctx;
|
||||
ctx.success = false;
|
||||
ctx.items = items;
|
||||
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
OBSDataAutoRelease item = obs_data_array_item(items, i);
|
||||
obs_scene_atomic_update(scene, [](void* param, obs_scene_t* scene) {
|
||||
auto ctx = reinterpret_cast<struct reorder_context*>(param);
|
||||
|
||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item);
|
||||
obs_sceneitem_release(sceneItem); // ref dec
|
||||
QVector<struct obs_sceneitem_order_info> orderList;
|
||||
struct obs_sceneitem_order_info info;
|
||||
|
||||
if (!sceneItem) {
|
||||
return req->SendErrorResponse("Invalid sceneItem id or name specified");
|
||||
}
|
||||
|
||||
for (size_t j = 0; j <= i; ++j) {
|
||||
if (sceneItem == newOrder[j]) {
|
||||
return req->SendErrorResponse("Duplicate sceneItem in specified order");
|
||||
size_t itemCount = obs_data_array_count(ctx->items);
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
OBSDataAutoRelease item = obs_data_array_item(ctx->items, i);
|
||||
|
||||
OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item);
|
||||
if (!sceneItem) {
|
||||
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);
|
||||
if (!success) {
|
||||
return req->SendErrorResponse("Invalid sceneItem order");
|
||||
}
|
||||
|
||||
for (auto const& item: newOrder) {
|
||||
obs_sceneitem_release(item);
|
||||
}
|
||||
|
||||
return req->SendOKResponse();
|
||||
return request.success();
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -20,28 +20,26 @@
|
||||
* @category streaming
|
||||
* @since 0.3
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::GetStreamingStatus(const RpcRequest& request) {
|
||||
auto events = GetEventsSystem();
|
||||
|
||||
OBSDataAutoRelease data = obs_data_create();
|
||||
obs_data_set_bool(data, "streaming", obs_frontend_streaming_active());
|
||||
obs_data_set_bool(data, "recording", obs_frontend_recording_active());
|
||||
obs_data_set_bool(data, "recording-paused", obs_frontend_recording_paused());
|
||||
obs_data_set_bool(data, "preview-only", false);
|
||||
|
||||
const char* tc = nullptr;
|
||||
if (obs_frontend_streaming_active()) {
|
||||
tc = events->GetStreamingTimecode();
|
||||
obs_data_set_string(data, "stream-timecode", tc);
|
||||
bfree((void*)tc);
|
||||
QString streamingTimecode = events->getStreamingTimecode();
|
||||
obs_data_set_string(data, "stream-timecode", streamingTimecode.toUtf8().constData());
|
||||
}
|
||||
|
||||
if (obs_frontend_recording_active()) {
|
||||
tc = events->GetRecordingTimecode();
|
||||
obs_data_set_string(data, "rec-timecode", tc);
|
||||
bfree((void*)tc);
|
||||
QString recordingTimecode = events->getRecordingTimecode();
|
||||
obs_data_set_string(data, "rec-timecode", recordingTimecode.toUtf8().constData());
|
||||
}
|
||||
|
||||
return req->SendOKResponse(data);
|
||||
return request.success(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,11 +50,11 @@ HandlerResponse WSRequestHandler::HandleGetStreamingStatus(WSRequestHandler* req
|
||||
* @category streaming
|
||||
* @since 0.3
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleStartStopStreaming(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::StartStopStreaming(const RpcRequest& request) {
|
||||
if (obs_frontend_streaming_active())
|
||||
return HandleStopStreaming(req);
|
||||
return StopStreaming(request);
|
||||
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 {String (optional)} `stream.settings.server` The publish URL.
|
||||
* @param {String (optional)} `stream.settings.key` The publish key of the stream.
|
||||
* @param {boolean (optional)} `stream.settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.
|
||||
* @param {String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use-auth` is not set to `true`.
|
||||
* @param {String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use-auth` is not set to `true`.
|
||||
* @param {boolean (optional)} `stream.settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.
|
||||
* @param {String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use_auth` is not set to `true`.
|
||||
* @param {String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`.
|
||||
*
|
||||
* @api requests
|
||||
* @name StartStreaming
|
||||
* @category streaming
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleStartStreaming(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::StartStreaming(const RpcRequest& request) {
|
||||
if (obs_frontend_streaming_active() == false) {
|
||||
OBSService configuredService = obs_frontend_get_streaming_service();
|
||||
OBSService newService = nullptr;
|
||||
|
||||
// TODO: fix service memory leak
|
||||
|
||||
if (req->hasField("stream")) {
|
||||
OBSDataAutoRelease streamData = obs_data_get_obj(req->data, "stream");
|
||||
if (request.hasField("stream")) {
|
||||
OBSDataAutoRelease streamData = obs_data_get_obj(request.parameters(), "stream");
|
||||
OBSDataAutoRelease newSettings = obs_data_get_obj(streamData, "settings");
|
||||
OBSDataAutoRelease newMetadata = obs_data_get_obj(streamData, "metadata");
|
||||
|
||||
@ -159,9 +157,9 @@ HandlerResponse WSRequestHandler::HandleStartStreaming(WSRequestHandler* req) {
|
||||
obs_frontend_set_streaming_service(configuredService);
|
||||
}
|
||||
|
||||
return req->SendOKResponse();
|
||||
return request.success();
|
||||
} else {
|
||||
return req->SendErrorResponse("streaming already active");
|
||||
return request.failed("streaming already active");
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,12 +172,12 @@ HandlerResponse WSRequestHandler::HandleStartStreaming(WSRequestHandler* req) {
|
||||
* @category streaming
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleStopStreaming(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::StopStreaming(const RpcRequest& request) {
|
||||
if (obs_frontend_streaming_active() == true) {
|
||||
obs_frontend_streaming_stop();
|
||||
return req->SendOKResponse();
|
||||
return request.success();
|
||||
} 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 {String (optional)} `settings.server` The publish URL.
|
||||
* @param {String (optional)} `settings.key` The publish key.
|
||||
* @param {boolean (optional)} `settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.
|
||||
* @param {boolean (optional)} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.
|
||||
* @param {String (optional)} `settings.username` The username for the streaming service.
|
||||
* @param {String (optional)} `settings.password` The password for the streaming service.
|
||||
* @param {boolean} `save` Persist the settings to disk.
|
||||
@ -200,21 +198,22 @@ HandlerResponse WSRequestHandler::HandleStopStreaming(WSRequestHandler* req) {
|
||||
* @category streaming
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleSetStreamSettings(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::SetStreamSettings(const RpcRequest& request) {
|
||||
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) {
|
||||
return req->SendErrorResponse("'settings' are required'");
|
||||
return request.failed("'settings' are required'");
|
||||
}
|
||||
|
||||
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) {
|
||||
OBSDataAutoRelease hotkeys = obs_hotkeys_save_service(service);
|
||||
service = obs_service_create(
|
||||
requestedType.toUtf8(), STREAM_SERVICE_ID, requestSettings, hotkeys);
|
||||
obs_frontend_set_streaming_service(service);
|
||||
} else {
|
||||
// If type isn't changing, we should overlay the settings we got
|
||||
// to the existing settings. By doing so, you can send a request that
|
||||
@ -233,17 +232,19 @@ HandlerResponse WSRequestHandler::HandleSetStreamSettings(WSRequestHandler* req)
|
||||
}
|
||||
|
||||
//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();
|
||||
}
|
||||
|
||||
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();
|
||||
obs_data_set_string(response, "type", requestedType.toUtf8());
|
||||
obs_data_set_string(response, "type", responseType);
|
||||
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 {String} `settings.server` The publish URL.
|
||||
* @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 {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 {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.password` The password to use when accessing the streaming server. Only present if `use_auth` is `true`.
|
||||
*
|
||||
* @api requests
|
||||
* @name GetStreamSettings
|
||||
* @category streaming
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetStreamSettings(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::GetStreamSettings(const RpcRequest& request) {
|
||||
OBSService service = obs_frontend_get_streaming_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_obj(response, "settings", settings);
|
||||
|
||||
return req->SendOKResponse(response);
|
||||
return request.success(response);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -283,9 +284,9 @@ HandlerResponse WSRequestHandler::HandleGetStreamSettings(WSRequestHandler* req)
|
||||
* @category streaming
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleSaveStreamSettings(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::SaveStreamSettings(const RpcRequest& request) {
|
||||
obs_frontend_save_streaming_service();
|
||||
return req->SendOKResponse();
|
||||
return request.success();
|
||||
}
|
||||
|
||||
|
||||
@ -301,18 +302,19 @@ HandlerResponse WSRequestHandler::HandleSaveStreamSettings(WSRequestHandler* req
|
||||
* @since 4.6.0
|
||||
*/
|
||||
#if BUILD_CAPTIONS
|
||||
HandlerResponse WSRequestHandler::HandleSendCaptions(WSRequestHandler* req) {
|
||||
if (!req->hasField("text")) {
|
||||
return req->SendErrorResponse("missing request parameters");
|
||||
RpcResponse WSRequestHandler::SendCaptions(const RpcRequest& request) {
|
||||
if (!request.hasField("text")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
OBSOutputAutoRelease output = obs_frontend_get_streaming_output();
|
||||
if (output) {
|
||||
const char* caption = obs_data_get_string(req->data, "text");
|
||||
obs_output_output_caption_text1(output, caption);
|
||||
const char* caption = obs_data_get_string(request.parameters(), "text");
|
||||
// Send caption text with immediately (0 second delay)
|
||||
obs_output_output_caption_text2(output, caption, 0.0);
|
||||
}
|
||||
|
||||
return req->SendOKResponse();
|
||||
return request.success();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -12,13 +12,13 @@
|
||||
* @category studio mode
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetStudioModeStatus(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::GetStudioModeStatus(const RpcRequest& request) {
|
||||
bool previewActive = obs_frontend_preview_program_mode_active();
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
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
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetPreviewScene(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::GetPreviewScene(const RpcRequest& request) {
|
||||
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();
|
||||
@ -45,7 +45,7 @@ HandlerResponse WSRequestHandler::HandleGetPreviewScene(WSRequestHandler* req) {
|
||||
obs_data_set_string(data, "name", obs_source_get_name(scene));
|
||||
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
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleSetPreviewScene(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::SetPreviewScene(const RpcRequest& request) {
|
||||
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")) {
|
||||
return req->SendErrorResponse("missing request parameters");
|
||||
if (!request.hasField("scene-name")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
const char* scene_name = obs_data_get_string(req->data, "scene-name");
|
||||
OBSSourceAutoRelease scene = Utils::GetSceneFromNameOrCurrent(scene_name);
|
||||
const char* scene_name = obs_data_get_string(request.parameters(), "scene-name");
|
||||
OBSScene scene = Utils::GetSceneFromNameOrCurrent(scene_name);
|
||||
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);
|
||||
return req->SendOKResponse();
|
||||
obs_frontend_set_current_preview_scene(obs_scene_get_source(scene));
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,37 +91,37 @@ HandlerResponse WSRequestHandler::HandleSetPreviewScene(WSRequestHandler* req) {
|
||||
* @category studio mode
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleTransitionToProgram(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::TransitionToProgram(const RpcRequest& request) {
|
||||
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 =
|
||||
obs_data_get_obj(req->data, "with-transition");
|
||||
obs_data_get_obj(request.parameters(), "with-transition");
|
||||
|
||||
if (obs_data_has_user_value(transitionInfo, "name")) {
|
||||
QString transitionName =
|
||||
obs_data_get_string(transitionInfo, "name");
|
||||
if (transitionName.isEmpty()) {
|
||||
return req->SendErrorResponse("invalid request parameters");
|
||||
return request.failed("invalid request parameters");
|
||||
}
|
||||
|
||||
bool success = Utils::SetTransitionByName(transitionName);
|
||||
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")) {
|
||||
int transitionDuration =
|
||||
obs_data_get_int(transitionInfo, "duration");
|
||||
Utils::SetTransitionDuration(transitionDuration);
|
||||
obs_frontend_set_transition_duration(transitionDuration);
|
||||
}
|
||||
}
|
||||
|
||||
Utils::TransitionToProgram();
|
||||
return req->SendOKResponse();
|
||||
obs_frontend_preview_program_trigger_transition();
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -132,9 +132,13 @@ HandlerResponse WSRequestHandler::HandleTransitionToProgram(WSRequestHandler* re
|
||||
* @category studio mode
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleEnableStudioMode(WSRequestHandler* req) {
|
||||
obs_frontend_set_preview_program_mode(true);
|
||||
return req->SendOKResponse();
|
||||
RpcResponse WSRequestHandler::EnableStudioMode(const RpcRequest& request) {
|
||||
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 +149,14 @@ HandlerResponse WSRequestHandler::HandleEnableStudioMode(WSRequestHandler* req)
|
||||
* @category studio mode
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleDisableStudioMode(WSRequestHandler* req) {
|
||||
obs_frontend_set_preview_program_mode(false);
|
||||
return req->SendOKResponse();
|
||||
RpcResponse WSRequestHandler::DisableStudioMode(const RpcRequest& request) {
|
||||
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 +167,13 @@ HandlerResponse WSRequestHandler::HandleDisableStudioMode(WSRequestHandler* req)
|
||||
* @category studio mode
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleToggleStudioMode(WSRequestHandler* req) {
|
||||
bool previewProgramMode = obs_frontend_preview_program_mode_active();
|
||||
obs_frontend_set_preview_program_mode(!previewProgramMode);
|
||||
return req->SendOKResponse();
|
||||
RpcResponse WSRequestHandler::ToggleStudioMode(const RpcRequest& request) {
|
||||
obs_queue_task(OBS_TASK_UI, [](void* param) {
|
||||
bool previewProgramMode = obs_frontend_preview_program_mode_active();
|
||||
obs_frontend_set_preview_program_mode(!previewProgramMode);
|
||||
|
||||
UNUSED_PARAMETER(param);
|
||||
}, nullptr, true);
|
||||
|
||||
return request.success();
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
* @category transitions
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetTransitionList(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::GetTransitionList(const RpcRequest& request) {
|
||||
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
|
||||
obs_frontend_source_list transitionList = {};
|
||||
obs_frontend_get_transitions(&transitionList);
|
||||
@ -34,7 +34,7 @@ HandlerResponse WSRequestHandler::HandleGetTransitionList(WSRequestHandler* req)
|
||||
obs_source_get_name(currentTransition));
|
||||
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
|
||||
* @since 0.3
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::GetCurrentTransition(const RpcRequest& request) {
|
||||
OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition();
|
||||
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
@ -56,9 +56,9 @@ HandlerResponse WSRequestHandler::HandleGetCurrentTransition(WSRequestHandler* r
|
||||
obs_source_get_name(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
|
||||
* @since 0.3
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleSetCurrentTransition(WSRequestHandler* req) {
|
||||
if (!req->hasField("transition-name")) {
|
||||
return req->SendErrorResponse("missing request parameters");
|
||||
RpcResponse WSRequestHandler::SetCurrentTransition(const RpcRequest& request) {
|
||||
if (!request.hasField("transition-name")) {
|
||||
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);
|
||||
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
|
||||
* @since 4.0.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleSetTransitionDuration(WSRequestHandler* req) {
|
||||
if (!req->hasField("duration")) {
|
||||
return req->SendErrorResponse("missing request parameters");
|
||||
RpcResponse WSRequestHandler::SetTransitionDuration(const RpcRequest& request) {
|
||||
if (!request.hasField("duration")) {
|
||||
return request.failed("missing request parameters");
|
||||
}
|
||||
|
||||
int ms = obs_data_get_int(req->data, "duration");
|
||||
Utils::SetTransitionDuration(ms);
|
||||
return req->SendOKResponse();
|
||||
int ms = obs_data_get_int(request.parameters(), "duration");
|
||||
obs_frontend_set_transition_duration(ms);
|
||||
return request.success();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -115,8 +115,8 @@ HandlerResponse WSRequestHandler::HandleSetTransitionDuration(WSRequestHandler*
|
||||
* @category transitions
|
||||
* @since 4.1.0
|
||||
*/
|
||||
HandlerResponse WSRequestHandler::HandleGetTransitionDuration(WSRequestHandler* req) {
|
||||
RpcResponse WSRequestHandler::GetTransitionDuration(const RpcRequest& request) {
|
||||
OBSDataAutoRelease response = obs_data_create();
|
||||
obs_data_set_int(response, "transition-duration", Utils::GetTransitionDuration());
|
||||
return req->SendOKResponse(response);
|
||||
obs_data_set_int(response, "transition-duration", obs_frontend_get_transition_duration());
|
||||
return request.success(response);
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
#include "obs-websocket.h"
|
||||
#include "Config.h"
|
||||
#include "Utils.h"
|
||||
#include "protocol/OBSRemoteProtocol.h"
|
||||
|
||||
QT_USE_NAMESPACE
|
||||
|
||||
@ -124,8 +125,15 @@ void WSServer::stop()
|
||||
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);
|
||||
for (connection_hdl hdl : _connections) {
|
||||
if (GetConfig()->AuthRequired) {
|
||||
@ -134,7 +142,15 @@ void WSServer::broadcast(std::string message)
|
||||
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 +179,26 @@ void WSServer::onMessage(connection_hdl hdl, server::message_ptr message)
|
||||
ConnectionProperties& connProperties = _connectionProperties[hdl];
|
||||
locker.unlock();
|
||||
|
||||
WSRequestHandler handler(connProperties);
|
||||
std::string response = handler.processIncomingMessage(payload);
|
||||
if (GetConfig()->DebugEnabled) {
|
||||
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 "ConnectionProperties.h"
|
||||
|
||||
#include "WSRequestHandler.h"
|
||||
|
||||
QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
|
||||
QT_FORWARD_DECLARE_CLASS(QWebSocket)
|
||||
#include "rpc/RpcEvent.h"
|
||||
|
||||
using websocketpp::connection_hdl;
|
||||
|
||||
@ -49,7 +46,7 @@ public:
|
||||
virtual ~WSServer();
|
||||
void start(quint16 port);
|
||||
void stop();
|
||||
void broadcast(std::string message);
|
||||
void broadcast(const RpcEvent& event);
|
||||
QThreadPool* threadPool() {
|
||||
return &_threadPool;
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
|
||||
|
||||
#include <obs-module.h>
|
||||
#include <obs-frontend-api.h>
|
||||
#include <obs-data.h>
|
||||
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtWidgets/QAction>
|
||||
@ -35,6 +36,11 @@ void ___data_dummy_addref(obs_data_t*) {}
|
||||
void ___data_array_dummy_addref(obs_data_array_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_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US")
|
||||
|
||||
@ -55,10 +61,6 @@ bool obs_module_load(void) {
|
||||
_server = WSServerPtr(new WSServer());
|
||||
_eventsSystem = WSEventsPtr(new WSEvents(_server));
|
||||
|
||||
if (_config->ServerEnabled) {
|
||||
_server->start(_config->ServerPort);
|
||||
}
|
||||
|
||||
// UI setup
|
||||
obs_frontend_push_ui_translation(obs_module_get_string);
|
||||
QMainWindow* mainWindow = (QMainWindow*)obs_frontend_get_main_window();
|
||||
@ -75,6 +77,17 @@ bool obs_module_load(void) {
|
||||
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
|
||||
blog(LOG_INFO, "module loaded!");
|
||||
|
||||
|
@ -38,6 +38,11 @@ using OBSDataArrayAutoRelease =
|
||||
using OBSOutputAutoRelease =
|
||||
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;
|
||||
typedef std::shared_ptr<Config> ConfigPtr;
|
||||
|
||||
@ -51,6 +56,6 @@ ConfigPtr GetConfig();
|
||||
WSServerPtr GetServer();
|
||||
WSEventsPtr GetEventsSystem();
|
||||
|
||||
#define OBS_WEBSOCKET_VERSION "4.7.0"
|
||||
#define OBS_WEBSOCKET_VERSION "4.8.0"
|
||||
|
||||
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)
|
||||
|
117
src/protocol/OBSRemoteProtocol.cpp
Normal file
117
src/protocol/OBSRemoteProtocol.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
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());
|
||||
|
||||
if (obs_frontend_streaming_active()) {
|
||||
QString streamingTimecode = Utils::nsToTimestamp(event.streamTime());
|
||||
obs_data_set_string(eventData, "stream-timecode", streamingTimecode.toUtf8().constData());
|
||||
}
|
||||
|
||||
if (obs_frontend_recording_active()) {
|
||||
QString recordingTimecode = Utils::nsToTimestamp(event.recordingTime());
|
||||
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,
|
||||
uint64_t streamTime, 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);
|
||||
}
|
||||
}
|
60
src/rpc/RpcEvent.h
Normal file
60
src/rpc/RpcEvent.h
Normal file
@ -0,0 +1,60 @@
|
||||
/*
|
||||
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 <obs-data.h>
|
||||
#include <QtCore/QString>
|
||||
|
||||
#include "../obs-websocket.h"
|
||||
|
||||
class RpcEvent
|
||||
{
|
||||
public:
|
||||
explicit RpcEvent(
|
||||
const QString& updateType,
|
||||
uint64_t streamTime, uint64_t recordingTime,
|
||||
obs_data_t* additionalFields = nullptr
|
||||
);
|
||||
|
||||
const QString& updateType() const
|
||||
{
|
||||
return _updateType;
|
||||
}
|
||||
|
||||
const uint64_t streamTime() const
|
||||
{
|
||||
return _streamTime;
|
||||
}
|
||||
|
||||
const uint64_t recordingTime() const
|
||||
{
|
||||
return _recordingTime;
|
||||
}
|
||||
|
||||
const OBSData additionalFields() const
|
||||
{
|
||||
return OBSData(_additionalFields);
|
||||
}
|
||||
|
||||
private:
|
||||
QString _updateType;
|
||||
uint64_t _streamTime;
|
||||
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;
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user