Merge branch 'master' into docs-formatting

This commit is contained in:
Dominik Nakamura 2022-02-19 15:53:39 +09:00
commit d7de347b37
No known key found for this signature in database
GPG Key ID: E4C6A749B2491910
74 changed files with 10157 additions and 1791 deletions

View File

@ -69,8 +69,8 @@ body:
label: obs-websocket Version
description: What version of obs-websocket are you using?
options:
- 5.0.0-alpha3
- 5.0.0-alpha2
- 5.0.0-alpha1
- 4.9.1
- 4.9.0
- Git

View File

@ -1,4 +1,4 @@
name: "CI Multiplatform Build"
name: 'CI Multiplatform Build'
on:
push:
@ -18,15 +18,15 @@ on:
jobs:
windows:
name: 'Windows 32/64-bit'
runs-on: [windows-latest]
runs-on: [windows-2019]
if: contains(github.event.head_commit.message, '[skip ci]') != true
env:
QT_CACHE_VERSION: '2' # Change whenever updating OBS dependencies URL, in order to force a cache reset
QT_VERSION: '5.15.2'
WINDOWS_DEPS_CACHE_VERSION: '1' # Change whenever updating Qt dependency URL, in order to force a cache reset
WINDOWS_DEPS_VERSION: '2019'
CMAKE_GENERATOR: "Visual Studio 16 2019"
CMAKE_SYSTEM_VERSION: "10.0"
CMAKE_GENERATOR: 'Visual Studio 16 2019'
CMAKE_SYSTEM_VERSION: '10.0'
steps:
- name: 'Add msbuild to PATH'
uses: microsoft/setup-msbuild@v1.0.2
@ -35,20 +35,20 @@ jobs:
with:
path: ${{ github.workspace }}/obs-websocket
submodules: 'recursive'
- name: 'Checkout OBS-Studio'
- name: 'Checkout OBS Studio'
uses: actions/checkout@v2
with:
repository: obsproject/obs-studio
path: ${{ github.workspace }}/obs-studio
submodules: 'recursive'
- name: 'Get OBS-Studio Git Info'
- name: 'Get OBS Studio Git Info'
shell: bash
working-directory: ${{ github.workspace }}/obs-studio
run: |
git fetch --prune --unshallow
echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
- name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})'
- name: 'Checkout last OBS Studio release (${{ env.OBS_GIT_TAG }})'
shell: bash
working-directory: ${{ github.workspace }}/obs-studio
run: |
@ -78,8 +78,6 @@ jobs:
with:
path: Qt_${{ env.QT_VERSION }}.7z
key: 'qtdep-${{ env.QT_CACHE_VERSION }} | ${{ runner.os }}'
restore-keys: |
qtdep-${{ env.QT_CACHE_VERSION }} | ${{ runner.os }}
- name: 'Download Prerequisite: Qt'
if: steps.qtcache.outputs.cache-hit != 'true'
run: |
@ -87,20 +85,18 @@ jobs:
- name: 'Extract Prerequisite: Qt'
run: |
7z x Qt_${{ env.QT_VERSION }}.7z -o"${{ github.workspace }}\cmbuild\QT"
- name: 'Restore Cached OBS-Studio Dependencies'
- name: 'Restore Cached OBS Studio Dependencies'
id: obscache
uses: actions/cache@v2
with:
path: ${{ github.workspace }}\cmbuild\deps\**
key: 'obsdep-${{ env.WINDOWS_DEPS_CACHE_VERSION }} | ${{ runner.os }}'
restore-keys: |
obsdep-${{ env.WINDOWS_DEPS_CACHE_VERSION }} | ${{ runner.os }}
- name: 'Install Prerequisite: Pre-built OBS-Studio dependencies'
- name: 'Install Prerequisite: Pre-built OBS Studio dependencies'
if: steps.obscache.outputs.cache-hit != 'true'
run: |
curl -kLO https://cdn-fastly.obsproject.com/downloads/dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -f --retry 5 -C -
7z x dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -o"${{ github.workspace }}\cmbuild\deps"
- name: 'Restore OBS-Studio 32-bit Build v${{ env.OBS_GIT_TAG }} from Cache'
- name: 'Restore OBS Studio 32-bit Build v${{ env.OBS_GIT_TAG }} from Cache'
id: build-cache-obs-32
uses: actions/cache@v2
env:
@ -108,22 +104,20 @@ jobs:
with:
path: ${{ github.workspace }}/obs-studio/build32
key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }}
restore-keys: |
${{ runner.os }}-${{ env.CACHE_NAME }}-
- name: 'Configure OBS-Studio 32-bit'
- name: 'Configure OBS Studio 32-bit'
if: steps.build-cache-obs-32.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio
run: |
if(!(Test-Path -Path ".\build32")){New-Item -ItemType directory -Path .\build32}
cd .\build32
cmake -G "${{ env.CMAKE_GENERATOR }}" -A Win32 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win32" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES -DBUILD_BROWSER=OFF ..
- name: 'Build OBS-Studio 32-bit'
- name: 'Build OBS Studio 32-bit'
if: steps.build-cache-obs-32.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio
run: |
msbuild /m /p:Configuration=RelWithDebInfo .\build32\libobs\libobs.vcxproj
msbuild /m /p:Configuration=RelWithDebInfo .\build32\UI\obs-frontend-api\obs-frontend-api.vcxproj
- name: 'Restore OBS-Studio 64-bit Build v${{ env.OBS_GIT_TAG }} from Cache'
- name: 'Restore OBS Studio 64-bit Build v${{ env.OBS_GIT_TAG }} from Cache'
id: build-cache-obs-64
uses: actions/cache@v1
env:
@ -131,16 +125,14 @@ jobs:
with:
path: ${{ github.workspace }}/obs-studio/build64
key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }}
restore-keys: |
${{ runner.os }}-${{ env.CACHE_NAME }}-
- name: 'Configure OBS-Studio 64-bit'
- name: 'Configure OBS Studio 64-bit'
if: steps.build-cache-obs-64.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio
run: |
if(!(Test-Path -Path ".\build64")){New-Item -ItemType directory -Path .\build64}
cd .\build64
cmake -G "${{ env.CMAKE_GENERATOR }}" -A x64 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019_64" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win64" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES -DBUILD_BROWSER=OFF ..
- name: 'Build OBS-Studio 64-bit'
- name: 'Build OBS Studio 64-bit'
if: steps.build-cache-obs-64.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio
run: |
@ -188,7 +180,7 @@ jobs:
name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-Windows-Installer'
path: ${{ github.workspace }}/obs-websocket/package/*.exe
ubuntu64:
name: "Linux/Ubuntu 64-bit"
name: 'Linux/Ubuntu 64-bit'
runs-on: [ubuntu-latest]
if: contains(github.event.head_commit.message, '[skip ci]') != true
steps:
@ -197,20 +189,20 @@ jobs:
with:
path: ${{ github.workspace }}/obs-websocket
submodules: 'recursive'
- name: 'Checkout OBS-Studio'
- name: 'Checkout OBS Studio'
uses: actions/checkout@v2
with:
repository: obsproject/obs-studio
path: ${{ github.workspace }}/obs-studio
submodules: 'recursive'
- name: 'Get OBS-Studio Git Info'
- name: 'Get OBS Studio Git Info'
shell: bash
working-directory: ${{ github.workspace }}/obs-studio
run: |
git fetch --prune --unshallow
echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
- name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})'
- name: 'Checkout last OBS Studio release (${{ env.OBS_GIT_TAG }})'
shell: bash
working-directory: ${{ github.workspace }}/obs-studio
run: |
@ -284,21 +276,21 @@ jobs:
libx11-xcb-dev \
libxcb1-dev \
libxss-dev \
- name: 'Configure OBS-Studio'
- name: 'Configure OBS Studio'
working-directory: ${{ github.workspace }}/obs-studio
shell: bash
run: |
mkdir ./build
cd ./build
cmake -DDISABLE_PLUGINS=YES -DENABLE_SCRIPTING=NO -DUNIX_STRUCTURE=YES -DCMAKE_INSTALL_PREFIX=/usr ..
- name: 'Build OBS-Studio'
- name: 'Build OBS Studio'
working-directory: ${{ github.workspace }}/obs-studio
shell: bash
run: |
set -e
cd ./build
make -j4 libobs obs-frontend-api
- name: 'Install OBS-Studio'
- name: 'Install OBS Studio'
working-directory: ${{ github.workspace }}/obs-studio
shell: bash
run: |
@ -316,7 +308,7 @@ jobs:
if [ "${{ env.GIT_TAG }}" ] ; then \
cmake -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=TRUE -DOBS_WEBSOCKET_VERSION_SUFFIX="${{ env.CMAKE_VERSION_SUFFIX }}" -DCMAKE_BUILD_TYPE=Release .. ; \
else \
cmake -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=TRUE -DOBS_WEBSOCKET_VERSION_SUFFIX="${{ env.CMAKE_VERSION_SUFFIX }}" .. ; \
cmake -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=TRUE -DOBS_WEBSOCKET_VERSION_SUFFIX="${{ env.CMAKE_VERSION_SUFFIX }}" -DPLUGIN_TESTS=TRUE .. ; \
fi
- name: 'Build obs-websocket'
working-directory: ${{ github.workspace }}/obs-websocket
@ -355,3 +347,213 @@ jobs:
with:
name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-Ubuntu64'
path: '${{ github.workspace }}/obs-websocket/package/*.deb'
macOS:
name: 'macOS 64-bit'
runs-on: [macos-latest]
if: contains(github.event.head_commit.message, '[skip ci]') != true
env:
MACOS_DEPS_VERSION: '2022-01-01'
MACOS_DEPS_CACHE_VERSION: '2' # Change whenever updating dependencies version, in order to force a cache reset
steps:
- name: 'Checkout obs-websocket'
uses: actions/checkout@v2
with:
path: ${{ github.workspace }}/obs-websocket
submodules: 'recursive'
- name: 'Checkout OBS Studio'
uses: actions/checkout@v2
with:
repository: obsproject/obs-studio
path: ${{ github.workspace }}/obs-studio
submodules: 'recursive'
- name: 'Install Prerequisite: Binary Signing Certificate'
if: github.event_name != 'pull_request'
uses: apple-actions/import-codesign-certs@v1
with:
p12-file-base64: ${{ secrets.MACOS_SIGNING_CERT }}
p12-password: ${{ secrets.MACOS_SIGNING_CERT_PASSWORD }}
create-keychain: true
keychain-password: ${{ secrets.MACOS_TEMP_CI_KEYCHAIN_PASSWORD }}
- name: 'Install Prerequisite: Installer Signing Certificate'
if: github.event_name != 'pull_request'
uses: apple-actions/import-codesign-certs@v1
with:
p12-file-base64: ${{ secrets.MACOS_INSTALLER_CERT }}
p12-password: ${{ secrets.MACOS_INSTALLER_CERT_PASSWORD }}
create-keychain: false
keychain-password: ${{ secrets.MACOS_TEMP_CI_KEYCHAIN_PASSWORD }}
- name: 'Get OBS Studio Git Info'
shell: bash
working-directory: ${{ github.workspace }}/obs-studio
run: |
git fetch --prune --unshallow
echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
- name: 'Checkout last OBS Studio release (${{ env.OBS_GIT_TAG }})'
shell: bash
working-directory: ${{ github.workspace }}/obs-studio
run: |
git checkout ${{ env.OBS_GIT_TAG }}
git submodule update
- name: 'Get obs-websocket git info'
shell: bash
working-directory: ${{ github.workspace }}/obs-websocket
run: |
git fetch --prune --unshallow
GIT_HASH=$(git rev-parse --short HEAD)
echo "GIT_HASH=$GIT_HASH" >> $GITHUB_ENV
GIT_TAG=$(git describe --exact-match --tags --abbrev=0) || GIT_TAG=""
echo "GIT_TAG=$GIT_TAG" >> $GITHUB_ENV
if [ "$GIT_TAG" ] ; then \
VERSION="$GIT_TAG" \
VERSION_SUFFIX=$(echo "$GIT_TAG" | cut -c6-20) ; \
else \
VERSION="$GIT_HASH-git" \
VERSION_SUFFIX="-$GIT_HASH-git" ; \
fi
echo "PACKAGE_VERSION=$VERSION" >> $GITHUB_ENV
echo "CMAKE_VERSION_SUFFIX=$VERSION_SUFFIX" >> $GITHUB_ENV
- name: 'Install Packages'
shell: bash
run: |
curl -L -O http://s.sudre.free.fr/Software/files/Packages.dmg
sudo hdiutil attach ./Packages.dmg
sudo installer -pkg /Volumes/Packages\ 1.2.10/Install\ Packages.pkg -target /
- name: 'Restore Cached Qt & OBS Studio dependencies'
id: deps-cache
uses: actions/cache@v2
with:
path: ${{ github.workspace }}/obsdeps/**
key: 'deps-cache-${{ env.MACOS_DEPS_CACHE_VERSION }} | ${{ runner.os }}'
- name: 'Install Prerequisite: Qt + OBS Studio dependencies'
if: steps.deps-cache.outputs.cache-hit != 'true'
shell: bash
run: |
mkdir -p obsdeps
curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.MACOS_DEPS_VERSION }}/macos-deps-qt-${{ env.MACOS_DEPS_VERSION }}-universal.tar.xz
tar -xf macos-deps-qt-${{ env.MACOS_DEPS_VERSION }}-universal.tar.xz -C "./obsdeps"
curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.MACOS_DEPS_VERSION }}/macos-deps-${{ env.MACOS_DEPS_VERSION }}-universal.tar.xz
tar -xf macos-deps-${{ env.MACOS_DEPS_VERSION }}-universal.tar.xz -C "./obsdeps"
- run: xattr -r -d com.apple.quarantine ./obsdeps
shell: bash
- name: 'Configue OBS Studio'
if: steps.cache-obs-build.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio
shell: bash
run: |
mkdir -p ./build
cd ./build
cmake .. \
-DQTDIR=${{ github.workspace }}/obsdeps \
-DDepsPath=${{ github.workspace }}/obsdeps \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 \
-DDISABLE_PLUGINS=true \
-DENABLE_SCRIPTING=0 \
-DCMAKE_PREFIX_PATH=${{ github.workspace }}/obsdeps/lib/cmake
- name: 'Build OBS Studio'
if: steps.cache-obs-build.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio/build
shell: bash
run: |
set -e
make -j4 libobs obs-frontend-api
- name: 'Configure obs-websocket'
working-directory: ${{ github.workspace }}/obs-websocket
shell: bash
run: |
mkdir -p build
cd build
cmake .. \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 \
-DQTDIR=${{ github.workspace }}/obsdeps \
-DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs \
-DLIBOBS_LIB=${{ github.workspace }}/obs-studio/libobs \
-DOBS_FRONTEND_LIB="${{ github.workspace }}/obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=/usr
- name: 'Build obs-websocket'
working-directory: ${{ github.workspace }}/obs-websocket/build
shell: bash
run: |
set -e
make -j4
- name: 'Relink Qt'
shell: bash
working-directory: ${{ github.workspace }}/obs-websocket/build
run: |
install_name_tool \
-change /tmp/obsdeps/lib/QtWidgets.framework/Versions/5/QtWidgets \
@executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets \
-change /tmp/obsdeps/lib/QtGui.framework/Versions/5/QtGui \
@executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui \
-change /tmp/obsdeps/lib/QtCore.framework/Versions/5/QtCore \
@executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore \
-change /tmp/obsdeps/lib/QtNetwork.framework/Versions/5/QtNetwork \
@executable_path/../Frameworks/QtNetwork.framework/Versions/5/QtNetwork \
-change /tmp/obsdeps/lib/QtSvg.framework/Versions/5/QtSvg \
@executable_path/../Frameworks/QtSvg.framework/Versions/5/QtSvg \
./obs-websocket.so
- name: 'Sign plugin binary'
if: github.event_name != 'pull_request'
shell: bash
working-directory: ${{ github.workspace }}/obs-websocket/build
run: |
codesign --sign "${{ secrets.MACOS_SIGNING_IDENTITY }}" ./obs-websocket.so
- name: 'Set PR Artifact Filename'
shell: bash
run: |
echo "MACOS_FILENAME=obs-websocket-${{ env.PACKAGE_VERSION }}-macOS.pkg" >> $GITHUB_ENV
echo "MACOS_FILENAME_UNSIGNED=obs-websocket-${{ env.PACKAGE_VERSION }}-macOS-Unsigned.pkg" >> $GITHUB_ENV
- name: 'Package ${{ env.MACOS_FILENAME_UNSIGNED }}'
if: success()
working-directory: ${{ github.workspace }}/obs-websocket
shell: bash
run: |
packagesbuild ./CI/macos/obs-websocket.pkgproj
mv ./release/obs-websocket.pkg ./release/${{ env.MACOS_FILENAME_UNSIGNED }}
- name: 'Sign plugin package'
if: ${{ env.GIT_TAG != '' }}
shell: bash
working-directory: ${{ github.workspace }}/obs-websocket
run: |
productsign \
--sign "${{ secrets.MACOS_INSTALLER_IDENTITY }}" \
./release/${{ env.MACOS_FILENAME_UNSIGNED }} \
./release/${{ env.MACOS_FILENAME }}
rm ./release/${{ env.MACOS_FILENAME_UNSIGNED }}
- name: 'Notarize package'
if: ${{ env.GIT_TAG != '' }}
shell: bash
working-directory: ${{ github.workspace }}/obs-websocket
run: |
zip -r ./release/${{ env.MACOS_FILENAME }}.zip ./release/${{ env.MACOS_FILENAME }}
UPLOAD_RESULT=$(xcrun altool --notarize-app \
--primary-bundle-id "com.obsproject.obs-websocket" \
--username "${{ secrets.MACOS_NOTARIZATION_USERNAME }}" \
--password "${{ secrets.MACOS_NOTARIZATION_PASSWORD }}" \
--asc-provider "${{ secrets.ASC_PROVIDER_SHORTNAME }}" \
--file "./release/${{ env.MACOS_FILENAME }}.zip")
rm ./release/${{ env.MACOS_FILENAME }}.zip
REQUEST_UUID=$(echo $UPLOAD_RESULT | awk -F ' = ' '/RequestUUID/ {print $2}')
# Pieces of code borrowed from rednoah/notarized-app
while sleep 30 && date; do
CHECK_RESULT=$(xcrun altool \
--notarization-info "$REQUEST_UUID" \
--username "${{ secrets.MACOS_NOTARIZATION_USERNAME }}" \
--password "${{ secrets.MACOS_NOTARIZATION_PASSWORD }}" \
--asc-provider "${{ secrets.ASC_PROVIDER_SHORTNAME }}")
if ! grep -q "Status: in progress" <<< "$CHECK_RESULT"; then
xcrun stapler staple ./release/${{ env.MACOS_FILENAME }}
break
fi
done
- name: 'Publish Packages'
if: success()
uses: actions/upload-artifact@v2-preview
with:
name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-macOS'
path: '${{ github.workspace }}/obs-websocket/release/*.pkg'

1
.gitignore vendored
View File

@ -11,3 +11,4 @@
/docs/node_modules/
/src/plugin-macros.generated.h
/installer/installer-windows.generated.iss
/cmake-build-debug/

View File

@ -1,5 +0,0 @@
brew "jack"
brew "speexdsp"
brew "cmake"
brew "freetype"
brew "fdk-aac"

View File

@ -1,26 +0,0 @@
#!/bin/sh
OSTYPE=$(uname)
if [ "${OSTYPE}" != "Darwin" ]; then
echo "[obs-websocket - Error] macOS build script can be run on Darwin-type OS only."
exit 1
fi
HAS_CMAKE=$(type cmake 2>/dev/null)
if [ "${HAS_CMAKE}" = "" ]; then
echo "[obs-websocket - Error] CMake not installed - please run 'install-dependencies-macos.sh' first."
exit 1
fi
echo "[obs-websocket] Building 'obs-websocket' for macOS."
mkdir -p build && cd build
cmake .. \
-DQTDIR=/tmp/obsdeps \
-DLIBOBS_INCLUDE_DIR=../../obs-studio/libobs \
-DLIBOBS_LIB=../../obs-studio/libobs \
-DOBS_FRONTEND_LIB="$(pwd)/../../obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=/usr \
&& make -j4

View File

@ -1,39 +0,0 @@
#!/bin/sh
OSTYPE=$(uname)
if [ "${OSTYPE}" != "Darwin" ]; then
echo "[obs-websocket - Error] macOS obs-studio build script can be run on Darwin-type OS only."
exit 1
fi
HAS_CMAKE=$(type cmake 2>/dev/null)
HAS_GIT=$(type git 2>/dev/null)
if [ "${HAS_CMAKE}" = "" ]; then
echo "[obs-websocket - Error] CMake not installed - please run 'install-dependencies-macos.sh' first."
exit 1
fi
if [ "${HAS_GIT}" = "" ]; then
echo "[obs-websocket - Error] Git not installed - please install Xcode developer tools or via Homebrew."
exit 1
fi
# Build obs-studio
cd ..
echo "[obs-websocket] Cloning obs-studio from GitHub.."
git clone https://github.com/obsproject/obs-studio
cd obs-studio
OBSLatestTag=$(git describe --tags --abbrev=0)
git checkout $OBSLatestTag
mkdir build && cd build
echo "[obs-websocket] Building obs-studio.."
cmake .. \
-DQTDIR=/tmp/obsdeps \
-DDepsPath=/tmp/obsdeps \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 \
-DDISABLE_PLUGINS=true \
-DENABLE_SCRIPTING=0 \
-DCMAKE_PREFIX_PATH=/tmp/obsdeps/lib/cmake \
&& make -j4

View File

@ -1,57 +0,0 @@
#!/bin/sh
OSTYPE=$(uname)
if [ "${OSTYPE}" != "Darwin" ]; then
echo "[obs-websocket - Error] macOS install dependencies script can be run on Darwin-type OS only."
exit 1
fi
HAS_BREW=$(type brew 2>/dev/null)
if [ "${HAS_BREW}" = "" ]; then
echo "[obs-websocket - Error] Please install Homebrew (https://www.brew.sh/) to build obs-websocket on macOS."
exit 1
fi
# OBS Studio Brew Deps
echo "[obs-websocket] Updating Homebrew.."
brew update >/dev/null
echo "[obs-websocket] Checking installed Homebrew formulas.."
if [ -d /usr/local/opt/openssl@1.0.2t ]; then
brew uninstall openssl@1.0.2t
brew untap local/openssl
fi
if [ -d /usr/local/opt/python@2.7.17 ]; then
brew uninstall python@2.7.17
brew untap local/python2
fi
brew bundle --file ./CI/macos/Brewfile
# Fetch and install Packages app
# =!= NOTICE =!=
# Installs a LaunchDaemon under /Library/LaunchDaemons/fr.whitebox.packages.build.dispatcher.plist
# =!= NOTICE =!=
HAS_PACKAGES=$(type packagesbuild 2>/dev/null)
if [ "${HAS_PACKAGES}" = "" ]; then
echo "[obs-websocket] Installing Packaging app (might require password due to 'sudo').."
curl -L -O http://s.sudre.free.fr/Software/files/Packages.dmg
sudo hdiutil attach ./Packages.dmg
sudo installer -pkg /Volumes/Packages\ 1.2.9/Install\ Packages.pkg -target /
fi
# OBS Deps
echo "[obs-websocket] Installing obs-websocket dependency 'OBS Deps ${OBS_DEPS_VERSION}'.."
wget --quiet --retry-connrefused --waitretry=1 https://github.com/obsproject/obs-deps/releases/download/${OBS_DEPS_VERSION}/macos-deps-${OBS_DEPS_VERSION}.tar.gz
tar -xf ./macos-deps-${OBS_DEPS_VERSION}.tar.gz -C /tmp
# Qt deps
echo "[obs-websocket] Installing obs-websocket dependency 'Qt ${QT_VERSION}'.."
curl -L -O https://github.com/obsproject/obs-deps/releases/download/${OBS_DEPS_VERSION}/macos-qt-${QT_VERSION}-${OBS_DEPS_VERSION}.tar.gz
tar -xf ./macos-qt-${QT_VERSION}-${OBS_DEPS_VERSION}.tar.gz -C "/tmp"
xattr -r -d com.apple.quarantine /tmp/obsdeps

View File

@ -514,7 +514,7 @@
<key>CONCLUSION_ACTION</key>
<integer>0</integer>
<key>IDENTIFIER</key>
<string>fr.palakis.obs-websocket</string>
<string>com.obsproject.obs-websocket</string>
<key>OVERWRITE_PERMISSIONS</key>
<false/>
<key>VERSION</key>

View File

@ -1,93 +0,0 @@
#!/bin/bash
set -e
OSTYPE=$(uname)
if [ "${OSTYPE}" != "Darwin" ]; then
echo "[obs-websocket - Error] macOS package script can be run on Darwin-type OS only."
exit 1
fi
echo "[obs-websocket] Preparing package build"
GIT_HASH=$(git rev-parse --short HEAD)
GIT_BRANCH_OR_TAG=$(git name-rev --name-only HEAD | awk -F/ '{print $NF}')
VERSION="$GIT_HASH-$GIT_BRANCH_OR_TAG"
FILENAME_UNSIGNED="obs-websocket-$VERSION-Unsigned.pkg"
FILENAME="obs-websocket-$VERSION.pkg"
echo "[obs-websocket] Modifying obs-websocket.so linking"
install_name_tool \
-change /tmp/obsdeps/lib/QtWidgets.framework/Versions/5/QtWidgets \
@executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets \
-change /tmp/obsdeps/lib/QtGui.framework/Versions/5/QtGui \
@executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui \
-change /tmp/obsdeps/lib/QtCore.framework/Versions/5/QtCore \
@executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore \
-change /tmp/obsdeps/lib/QtNetwork.framework/Versions/5/QtNetwork \
@executable_path/../Frameworks/QtNetwork.framework/Versions/5/QtNetwork \
-change /tmp/obsdeps/lib/QtSvg.framework/Versions/5/QtSvg \
@executable_path/../Frameworks/QtSvg.framework/Versions/5/QtSvg \
./build/obs-websocket.so
# Check if replacement worked
echo "[obs-websocket] Dependencies for obs-websocket"
otool -L ./build/obs-websocket.so
if [[ "$RELEASE_MODE" == "True" ]]; then
echo "[obs-websocket] Signing plugin binary: obs-websocket.so"
codesign --sign "$CODE_SIGNING_IDENTITY" ./build/obs-websocket.so
else
echo "[obs-websocket] Skipped plugin codesigning"
fi
echo "[obs-websocket] Actual package build"
packagesbuild ./CI/macos/obs-websocket.pkgproj
echo "[obs-websocket] Renaming obs-websocket.pkg to $FILENAME"
mv ./release/obs-websocket.pkg ./release/$FILENAME_UNSIGNED
if [[ "$RELEASE_MODE" == "True" ]]; then
echo "[obs-websocket] Signing installer: $FILENAME"
productsign \
--sign "$INSTALLER_SIGNING_IDENTITY" \
./release/$FILENAME_UNSIGNED \
./release/$FILENAME
rm ./release/$FILENAME_UNSIGNED
echo "[obs-websocket] Submitting installer $FILENAME for notarization"
zip -r ./release/$FILENAME.zip ./release/$FILENAME
UPLOAD_RESULT=$(xcrun altool \
--notarize-app \
--primary-bundle-id "fr.palakis.obs-websocket" \
--username "$AC_USERNAME" \
--password "$AC_PASSWORD" \
--asc-provider "$AC_PROVIDER_SHORTNAME" \
--file "./release/$FILENAME.zip")
rm ./release/$FILENAME.zip
REQUEST_UUID=$(echo $UPLOAD_RESULT | awk -F ' = ' '/RequestUUID/ {print $2}')
echo "Request UUID: $REQUEST_UUID"
echo "[obs-websocket] Wait for notarization result"
# Pieces of code borrowed from rednoah/notarized-app
while sleep 30 && date; do
CHECK_RESULT=$(xcrun altool \
--notarization-info "$REQUEST_UUID" \
--username "$AC_USERNAME" \
--password "$AC_PASSWORD" \
--asc-provider "$AC_PROVIDER_SHORTNAME")
echo $CHECK_RESULT
if ! grep -q "Status: in progress" <<< "$CHECK_RESULT"; then
echo "[obs-websocket] Staple ticket to installer: $FILENAME"
xcrun stapler staple ./release/$FILENAME
break
fi
done
else
echo "[obs-websocket] Skipped installer codesigning and notarization"
fi

View File

@ -103,6 +103,7 @@ set(obs-websocket_SOURCES
src/eventhandler/EventHandler_Outputs.cpp
src/eventhandler/EventHandler_SceneItems.cpp
src/eventhandler/EventHandler_MediaInputs.cpp
src/eventhandler/EventHandler_Ui.cpp
src/requesthandler/RequestHandler.cpp
src/requesthandler/RequestBatchHandler.cpp
src/requesthandler/RequestHandler_General.cpp
@ -110,10 +111,14 @@ set(obs-websocket_SOURCES
src/requesthandler/RequestHandler_Sources.cpp
src/requesthandler/RequestHandler_Scenes.cpp
src/requesthandler/RequestHandler_Inputs.cpp
src/requesthandler/RequestHandler_Transitions.cpp
src/requesthandler/RequestHandler_Filters.cpp
src/requesthandler/RequestHandler_SceneItems.cpp
src/requesthandler/RequestHandler_Outputs.cpp
src/requesthandler/RequestHandler_Stream.cpp
src/requesthandler/RequestHandler_Record.cpp
src/requesthandler/RequestHandler_MediaInputs.cpp
src/requesthandler/RequestHandler_Ui.cpp
src/requesthandler/rpc/Request.cpp
src/requesthandler/rpc/RequestBatchRequest.cpp
src/requesthandler/rpc/RequestResult.cpp
@ -123,7 +128,14 @@ set(obs-websocket_SOURCES
src/utils/Crypto.cpp
src/utils/Json.cpp
src/utils/Obs.cpp
src/utils/ObsVolumeMeter.cpp
src/utils/Obs_StringHelper.cpp
src/utils/Obs_EnumHelper.cpp
src/utils/Obs_NumberHelper.cpp
src/utils/Obs_ArrayHelper.cpp
src/utils/Obs_ObjectHelper.cpp
src/utils/Obs_SearchHelper.cpp
src/utils/Obs_ActionHelper.cpp
src/utils/Obs_VolumeMeter.cpp
src/utils/Platform.cpp
src/utils/Compat.cpp
deps/qr/cpp/QrCode.cpp)
@ -150,8 +162,8 @@ set(obs-websocket_HEADERS
src/utils/Crypto.h
src/utils/Json.h
src/utils/Obs.h
src/utils/ObsVolumeMeter.h
src/utils/ObsVolumeMeter_Helpers.h
src/utils/Obs_VolumeMeter.h
src/utils/Obs_VolumeMeter_Helpers.h
src/utils/Platform.h
src/utils/Compat.h
src/utils/Utils.h

View File

@ -8,7 +8,7 @@
WebSocket API for OBS Studio.
[![CI Multiplatform Build](https://github.com/obsproject/obs-websocket/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/obs-websocket/obs-websocket/actions/workflows/main.yml)
[![CI Multiplatform Build](https://github.com/obsproject/obs-websocket/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/obsproject/obs-websocket/actions/workflows/main.yml)
[![Discord](https://img.shields.io/discord/715691013825364120.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/WBaSQ3A)
[![Financial Contributors on Open Collective](https://opencollective.com/obs-websocket-dev/all/badge.svg?label=financial+contributors)](https://opencollective.com/obs-websocket-dev)
@ -30,7 +30,7 @@ It is **highly recommended** to protect obs-websocket with a password against un
### Client software
- (No known clients supporting 5.0.0 at the moment. Send a message in Discord if you have one!)
- (No known clients supporting 5.0.0 at the moment. Ping us in the Discord if you have one!)
### Client libraries (for developers)
@ -39,10 +39,10 @@ Here's a list of available language APIs for obs-websocket:
- Python 3.7+ (Asyncio): [simpleobsws](https://github.com/IRLToolkit/simpleobsws/tree/master) by IRLToolkit
- Rust: [obws](https://github.com/dnaka91/obws/tree/v5-api) by dnaka91
The server is a typical Websockets server running by default on port 4444 (the port number can be changed in the Settings dialog under `Tools`).
The 5.x server is a typical WebSocket server running by default on port 4455 (the port number can be changed in the Settings dialog under `Tools`).
The protocol we use is documented in [PROTOCOL.md](docs/generated/protocol.md).
We'd like to know what you're building with or for obs-websocket. If you do something in this fashion, feel free to drop a message in `#project-showoff` in the [discord server!](https://discord.gg/WBaSQ3A)
We'd like to know what you're building with obs-websocket! If you do something in this fashion, feel free to drop a message in `#project-showoff` in the [discord server!](https://discord.gg/WBaSQ3A)
## Contributors

2
deps/asio vendored

@ -1 +1 @@
Subproject commit b84e6c16b2ea907dbad94206b7510d85aafc0b42
Subproject commit b73dc1d2c0ecb9452a87c26544d7f71e24342df6

View File

@ -25,6 +25,7 @@ categoryOrder = [
'Stream',
'Record',
'Media Inputs',
'Ui',
'High-Volume'
]

View File

@ -122,7 +122,7 @@ for comment in comments_raw:
enumValue = field_to_string(comment['enumValue'])
enum['enumValue'] = int(enumValue) if enumValue.isdigit() else enumValue
else:
enum['enumValue'] = None
enum['enumValue'] = enum['enumIdentifier']
if enumType not in enums_raw:
enums_raw[enumType] = {'enumIdentifiers': [enum]}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <obs.h>
#define OBS_WEBSOCKET_API_VERSION 1
#ifdef __cplusplus
extern "C" {
#endif

View File

@ -41,7 +41,7 @@ Config::Config() :
PasswordOverridden(false),
FirstLoad(true),
ServerEnabled(true),
ServerPort(4444),
ServerPort(4455),
DebugEnabled(false),
AlertsEnabled(false),
AuthRequired(true),
@ -70,7 +70,7 @@ void Config::Load()
// future loads use the override flag.
if (FirstLoad) {
FirstLoad = false;
if (!ServerPassword.isEmpty()) {
if (ServerPassword.isEmpty()) {
blog(LOG_INFO, "[Config::Load] (FirstLoad) Generating new server password.");
ServerPassword = QString::fromStdString(Utils::Crypto::GeneratePassword());
} else {

View File

@ -16,8 +16,7 @@ WebSocketApi::Vendor *get_vendor(calldata_t *cd)
return static_cast<WebSocketApi::Vendor*>(voidVendor);
}
WebSocketApi::WebSocketApi(EventCallback cb) :
_eventCallback(cb)
WebSocketApi::WebSocketApi()
{
blog_debug("[WebSocketApi::WebSocketApi] Setting up...");
@ -50,6 +49,11 @@ WebSocketApi::~WebSocketApi()
blog_debug("[WebSocketApi::~WebSocketApi] Finished.");
}
void WebSocketApi::SetEventCallback(EventCallback cb)
{
_eventCallback = cb;
}
enum WebSocketApi::RequestReturnCode WebSocketApi::PerformVendorRequest(std::string vendorName, std::string requestType, obs_data_t *requestData, obs_data_t *responseData)
{
std::shared_lock l(_mutex);
@ -196,6 +200,9 @@ void WebSocketApi::vendor_event_emit_cb(void *priv_data, calldata_t *cd)
auto eventData = static_cast<obs_data_t*>(voidEventData);
if (!c->_eventCallback)
RETURN_FAILURE();
c->_eventCallback(v->_name, eventType, eventData);
RETURN_SUCCESS();

View File

@ -25,9 +25,11 @@ class WebSocketApi {
std::map<std::string, obs_websocket_request_callback> _requests;
};
WebSocketApi(EventCallback cb);
WebSocketApi();
~WebSocketApi();
void SetEventCallback(EventCallback cb);
enum RequestReturnCode PerformVendorRequest(std::string vendorName, std::string requestName, obs_data_t *requestData, obs_data_t *responseData);
static void get_ph_cb(void *priv_data, calldata_t *cd);

View File

@ -134,9 +134,13 @@ void EventHandler::ConnectSourceSignals(obs_source_t *source) // Applies to inpu
signal_handler_connect(sh, "hide", HandleInputShowStateChanged, this);
signal_handler_connect(sh, "mute", HandleInputMuteStateChanged, this);
signal_handler_connect(sh, "volume", HandleInputVolumeChanged, this);
signal_handler_connect(sh, "audio_balance", HandleInputAudioBalanceChanged, this);
signal_handler_connect(sh, "audio_sync", HandleInputAudioSyncOffsetChanged, this);
signal_handler_connect(sh, "audio_mixers", HandleInputAudioTracksChanged, this);
//signal_handler_connect(sh, "audio_monitoring", HandleInputAudioMonitorTypeChanged, this);
signal_handler_connect(sh, "audio_monitoring", HandleInputAudioMonitorTypeChanged, this);
signal_handler_connect(sh, "filter_add", HandleSourceFilterCreated, this);
signal_handler_connect(sh, "filter_remove", HandleSourceFilterRemoved, this);
signal_handler_connect(sh, "reorder_filters", HandleSourceFilterListReindexed, this);
if (sourceType == OBS_SOURCE_TYPE_INPUT) {
signal_handler_connect(sh, "media_started", HandleMediaInputPlaybackStarted, this);
@ -156,6 +160,7 @@ void EventHandler::ConnectSourceSignals(obs_source_t *source) // Applies to inpu
signal_handler_connect(sh, "reorder", HandleSceneItemListReindexed, this);
signal_handler_connect(sh, "item_visible", HandleSceneItemEnableStateChanged, this);
signal_handler_connect(sh, "item_locked", HandleSceneItemLockStateChanged, this);
signal_handler_connect(sh, "item_select", HandleSceneItemSelected, this);
signal_handler_connect(sh, "item_transform", HandleSceneItemTransformChanged, this);
}
}
@ -174,9 +179,10 @@ void EventHandler::DisconnectSourceSignals(obs_source_t *source)
signal_handler_disconnect(sh, "hide", HandleInputShowStateChanged, this);
signal_handler_disconnect(sh, "mute", HandleInputMuteStateChanged, this);
signal_handler_disconnect(sh, "volume", HandleInputVolumeChanged, this);
signal_handler_disconnect(sh, "audio_balance", HandleInputAudioBalanceChanged, this);
signal_handler_disconnect(sh, "audio_sync", HandleInputAudioSyncOffsetChanged, this);
signal_handler_disconnect(sh, "audio_mixers", HandleInputAudioTracksChanged, this);
//signal_handler_disconnect(sh, "audio_monitoring", HandleInputAudioMonitorTypeChanged, this);
signal_handler_disconnect(sh, "audio_monitoring", HandleInputAudioMonitorTypeChanged, this);
signal_handler_disconnect(sh, "media_started", HandleMediaInputPlaybackStarted, this);
signal_handler_disconnect(sh, "media_ended", HandleMediaInputPlaybackEnded, this);
signal_handler_disconnect(sh, "media_pause", SourceMediaPauseMultiHandler, this);
@ -185,6 +191,9 @@ void EventHandler::DisconnectSourceSignals(obs_source_t *source)
signal_handler_disconnect(sh, "media_stopped", SourceMediaStopMultiHandler, this);
signal_handler_disconnect(sh, "media_next", SourceMediaNextMultiHandler, this);
signal_handler_disconnect(sh, "media_previous", SourceMediaPreviousMultiHandler, this);
signal_handler_disconnect(sh, "filter_add", HandleSourceFilterCreated, this);
signal_handler_disconnect(sh, "filter_remove", HandleSourceFilterRemoved, this);
signal_handler_disconnect(sh, "reorder_filters", HandleSourceFilterListReindexed, this);
// Scenes
signal_handler_disconnect(sh, "item_add", HandleSceneItemCreated, this);
@ -192,12 +201,37 @@ void EventHandler::DisconnectSourceSignals(obs_source_t *source)
signal_handler_disconnect(sh, "reorder", HandleSceneItemListReindexed, this);
signal_handler_disconnect(sh, "item_visible", HandleSceneItemEnableStateChanged, this);
signal_handler_disconnect(sh, "item_locked", HandleSceneItemLockStateChanged, this);
signal_handler_disconnect(sh, "item_select", HandleSceneItemSelected, this);
signal_handler_disconnect(sh, "item_transform", HandleSceneItemTransformChanged, this);
}
void EventHandler::ConnectFilterSignals(obs_source_t *filter)
{
if (!filter || obs_source_removed(filter))
return;
DisconnectFilterSignals(filter);
signal_handler_t* sh = obs_source_get_signal_handler(filter);
signal_handler_connect(sh, "enable", HandleSourceFilterEnableStateChanged, this);
signal_handler_connect(sh, "rename", HandleSourceFilterNameChanged, this);
}
void EventHandler::DisconnectFilterSignals(obs_source_t *filter)
{
if (!filter)
return;
signal_handler_t* sh = obs_source_get_signal_handler(filter);
signal_handler_disconnect(sh, "enable", HandleSourceFilterEnableStateChanged, this);
signal_handler_disconnect(sh, "rename", HandleSourceFilterNameChanged, this);
}
void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(private_data);
auto eventHandler = static_cast<EventHandler*>(private_data);
if (!eventHandler->_obsLoaded.load()) {
if (event == OBS_FRONTEND_EVENT_FINISHED_LOADING) {
@ -207,18 +241,38 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
// In the case that plugins become hotloadable, this will have to go back into `EventHandler::EventHandler()`
// Enumerate inputs and connect each one
obs_enum_sources([](void* param, obs_source_t* source) {
auto eventHandler = reinterpret_cast<EventHandler*>(param);
eventHandler->ConnectSourceSignals(source);
return true;
}, private_data);
{
auto enumInputs = [](void *param, obs_source_t *source) {
auto eventHandler = static_cast<EventHandler*>(param);
eventHandler->ConnectSourceSignals(source);
auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param){
auto eventHandler = static_cast<EventHandler*>(param);
eventHandler->ConnectFilterSignals(filter);
};
obs_source_enum_filters(source, enumFilters, param);
return true;
};
obs_enum_sources(enumInputs, private_data);
}
// Enumerate scenes and connect each one
obs_enum_scenes([](void* param, obs_source_t* source) {
auto eventHandler = reinterpret_cast<EventHandler*>(param);
eventHandler->ConnectSourceSignals(source);
return true;
}, private_data);
{
auto enumScenes = [](void *param, obs_source_t *source) {
auto eventHandler = static_cast<EventHandler*>(param);
eventHandler->ConnectSourceSignals(source);
auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param){
auto eventHandler = static_cast<EventHandler*>(param);
eventHandler->ConnectFilterSignals(filter);
};
obs_source_enum_filters(source, enumFilters, param);
return true;
};
obs_enum_scenes(enumScenes, private_data);
}
blog_debug("[EventHandler::OnFrontendEvent] Finished.");
@ -240,18 +294,38 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
// In the case that plugins become hotloadable, this will have to go back into `EventHandler::~EventHandler()`
// Enumerate inputs and disconnect each one
obs_enum_sources([](void* param, obs_source_t* source) {
auto eventHandler = reinterpret_cast<EventHandler*>(param);
eventHandler->DisconnectSourceSignals(source);
return true;
}, private_data);
{
auto enumInputs = [](void *param, obs_source_t *source) {
auto eventHandler = static_cast<EventHandler*>(param);
eventHandler->DisconnectSourceSignals(source);
auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param){
auto eventHandler = static_cast<EventHandler*>(param);
eventHandler->ConnectFilterSignals(filter);
};
obs_source_enum_filters(source, enumFilters, param);
return true;
};
obs_enum_sources(enumInputs, private_data);
}
// Enumerate scenes and disconnect each one
obs_enum_scenes([](void* param, obs_source_t* source) {
auto eventHandler = reinterpret_cast<EventHandler*>(param);
eventHandler->DisconnectSourceSignals(source);
return true;
}, private_data);
{
auto enumScenes = [](void *param, obs_source_t *source) {
auto eventHandler = static_cast<EventHandler*>(param);
eventHandler->DisconnectSourceSignals(source);
auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param){
auto eventHandler = static_cast<EventHandler*>(param);
eventHandler->ConnectFilterSignals(filter);
};
obs_source_enum_filters(source, enumFilters, param);
return true;
};
obs_enum_scenes(enumScenes, private_data);
}
blog_debug("[EventHandler::OnFrontendEvent] Finished.");
@ -264,18 +338,18 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
break;
// Config
//case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING:
// eventHandler->HandleCurrentSceneCollectionChanging();
// break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING:
eventHandler->HandleCurrentSceneCollectionChanging();
break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED:
eventHandler->HandleCurrentSceneCollectionChanged();
break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED:
eventHandler->HandleSceneCollectionListChanged();
break;
//case OBS_FRONTEND_EVENT_PROFILE_CHANGING:
// eventHandler->HandleCurrentProfileChanging();
// break;
case OBS_FRONTEND_EVENT_PROFILE_CHANGING:
eventHandler->HandleCurrentProfileChanging();
break;
case OBS_FRONTEND_EVENT_PROFILE_CHANGED:
eventHandler->HandleCurrentProfileChanged();
break;
@ -285,7 +359,7 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
// Scenes
case OBS_FRONTEND_EVENT_SCENE_CHANGED:
eventHandler->HandleCurrentSceneChanged();
eventHandler->HandleCurrentProgramSceneChanged();
break;
case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED:
eventHandler->HandleCurrentPreviewSceneChanged();
@ -296,10 +370,12 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
// Transitions
case OBS_FRONTEND_EVENT_TRANSITION_CHANGED:
eventHandler->HandleCurrentSceneTransitionChanged();
break;
case OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED:
break;
case OBS_FRONTEND_EVENT_TRANSITION_DURATION_CHANGED:
eventHandler->HandleCurrentSceneTransitionDurationChanged();
break;
// Outputs
@ -363,7 +439,7 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
// Only called for creation of a public source
void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
// Don't react to signals until OBS has finished loading
if (!eventHandler->_obsLoaded.load())
@ -379,10 +455,6 @@ void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data)
case OBS_SOURCE_TYPE_INPUT:
eventHandler->HandleInputCreated(source);
break;
case OBS_SOURCE_TYPE_FILTER:
break;
case OBS_SOURCE_TYPE_TRANSITION:
break;
case OBS_SOURCE_TYPE_SCENE:
eventHandler->HandleSceneCreated(source);
break;
@ -394,7 +466,7 @@ void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data)
// Only called for destruction of a public source
void EventHandler::SourceDestroyedMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
// We can't use any smart types here because releasing the source will cause infinite recursion
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
@ -413,10 +485,6 @@ void EventHandler::SourceDestroyedMultiHandler(void *param, calldata_t *data)
// We have to call `InputRemoved` with source_destroy because source_removed is not called when an input's last scene item is removed
eventHandler->HandleInputRemoved(source);
break;
case OBS_SOURCE_TYPE_FILTER:
break;
case OBS_SOURCE_TYPE_TRANSITION:
break;
case OBS_SOURCE_TYPE_SCENE:
break;
default:
@ -426,7 +494,7 @@ void EventHandler::SourceDestroyedMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceRemovedMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
if (!eventHandler->_obsLoaded.load())
return;
@ -438,10 +506,6 @@ void EventHandler::SourceRemovedMultiHandler(void *param, calldata_t *data)
switch (obs_source_get_type(source)) {
case OBS_SOURCE_TYPE_INPUT:
break;
case OBS_SOURCE_TYPE_FILTER:
break;
case OBS_SOURCE_TYPE_TRANSITION:
break;
case OBS_SOURCE_TYPE_SCENE:
// Scenes emit the `removed` signal when they are removed from OBS, as expected
eventHandler->HandleSceneRemoved(source);
@ -453,7 +517,7 @@ void EventHandler::SourceRemovedMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceRenamedMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
if (!eventHandler->_obsLoaded.load())
return;
@ -471,8 +535,6 @@ void EventHandler::SourceRenamedMultiHandler(void *param, calldata_t *data)
case OBS_SOURCE_TYPE_INPUT:
eventHandler->HandleInputNameChanged(source, oldSourceName, sourceName);
break;
case OBS_SOURCE_TYPE_FILTER:
break;
case OBS_SOURCE_TYPE_TRANSITION:
break;
case OBS_SOURCE_TYPE_SCENE:

View File

@ -22,12 +22,11 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <atomic>
#include <obs.hpp>
#include <obs-frontend-api.h>
#include <util/platform.h>
#include "types/EventSubscription.h"
#include "../obs-websocket.h"
#include "../utils/Obs.h"
#include "../utils/ObsVolumeMeter.h"
#include "../utils/Obs_VolumeMeter.h"
#include "../plugin-macros.generated.h"
class EventHandler
@ -59,6 +58,9 @@ class EventHandler
void ConnectSourceSignals(obs_source_t *source);
void DisconnectSourceSignals(obs_source_t *source);
void ConnectFilterSignals(obs_source_t *filter);
void DisconnectFilterSignals(obs_source_t *filter);
void BroadcastEvent(uint64_t requiredIntent, std::string eventType, json eventData = nullptr, uint8_t rpcVersion = 0);
// Signal handler: frontend
@ -95,7 +97,7 @@ class EventHandler
void HandleSceneCreated(obs_source_t *source);
void HandleSceneRemoved(obs_source_t *source);
void HandleSceneNameChanged(obs_source_t *source, std::string oldSceneName, std::string sceneName);
void HandleCurrentSceneChanged();
void HandleCurrentProgramSceneChanged();
void HandleCurrentPreviewSceneChanged();
void HandleSceneListChanged();
@ -108,14 +110,14 @@ class EventHandler
static void HandleInputShowStateChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputMuteStateChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputVolumeChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputAudioBalanceChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputAudioSyncOffsetChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputAudioTracksChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputAudioMonitorTypeChanged(void *param, calldata_t *data); // Direct callback
// Transitions
void HandleTransitionCreated(obs_source_t *source);
void HandleTransitionRemoved(obs_source_t *source);
void HandleTransitionNameChanged(obs_source_t *source, std::string oldTransitionName, std::string transitionName);
void HandleCurrentSceneTransitionChanged();
void HandleCurrentSceneTransitionDurationChanged();
// Outputs
void HandleStreamStateChanged(ObsOutputState state);
@ -130,10 +132,18 @@ class EventHandler
static void HandleSceneItemListReindexed(void *param, calldata_t *data); // Direct callback
static void HandleSceneItemEnableStateChanged(void *param, calldata_t *data); // Direct callback
static void HandleSceneItemLockStateChanged(void *param, calldata_t *data); // Direct callback
static void HandleSceneItemSelected(void *param, calldata_t *data); // Direct callback
static void HandleSceneItemTransformChanged(void *param, calldata_t *data); // Direct callback
// Media Inputs
static void HandleMediaInputPlaybackStarted(void *param, calldata_t *data); // Direct callback
static void HandleMediaInputPlaybackEnded(void *param, calldata_t *data); // Direct callback
void HandleMediaInputActionTriggered(obs_source_t *source, ObsMediaInputAction action);
// Filters
static void HandleSourceFilterNameChanged(void *param, calldata_t *data); // Direct callback
static void HandleSourceFilterCreated(void *param, calldata_t *data); // Direct callback
static void HandleSourceFilterRemoved(void *param, calldata_t *data); // Direct callback
static void HandleSourceFilterListReindexed(void *param, calldata_t *data); // Direct callback
static void HandleSourceFilterEnableStateChanged(void *param, calldata_t *data); // Direct callback
};

View File

@ -80,7 +80,7 @@ void EventHandler::HandleCurrentSceneCollectionChanged()
void EventHandler::HandleSceneCollectionListChanged()
{
json eventData;
eventData["sceneCollections"] = Utils::Obs::ListHelper::GetSceneCollectionList();
eventData["sceneCollections"] = Utils::Obs::ArrayHelper::GetSceneCollectionList();
BroadcastEvent(EventSubscription::Config, "SceneCollectionListChanged", eventData);
}
@ -140,6 +140,6 @@ void EventHandler::HandleCurrentProfileChanged()
void EventHandler::HandleProfileListChanged()
{
json eventData;
eventData["profiles"] = Utils::Obs::ListHelper::GetProfileList();
eventData["profiles"] = Utils::Obs::ArrayHelper::GetProfileList();
BroadcastEvent(EventSubscription::Config, "ProfileListChanged", eventData);
}

View File

@ -18,3 +18,174 @@ with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include "EventHandler.h"
/**
* A filter has been added to a source.
*
* @dataField sourceName | String | Name of the source the filter was added to
* @dataField filterName | String | Name of the filter
* @dataField filterKind | String | The kind of the filter
* @dataField filterIndex | Number | Index position of the filter
* @dataField filterSettings | Object | The settings configured to the filter when it was created
* @dataField defaultFilterSettings | Object | The default settings for the filter
*
* @eventType SourceFilterCreated
* @eventSubscription Filters
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category filters
*/
void EventHandler::HandleSourceFilterCreated(void *param, calldata_t *data)
{
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "filter");
if (!(source && filter))
return;
eventHandler->ConnectFilterSignals(filter);
std::string filterKind = obs_source_get_id(filter);
OBSDataAutoRelease filterSettings = obs_source_get_settings(filter);
OBSDataAutoRelease defaultFilterSettings = obs_get_source_defaults(filterKind.c_str());
json eventData;
eventData["sourceName"] = obs_source_get_name(source);
eventData["filterName"] = obs_source_get_name(filter);
eventData["filterKind"] = filterKind;
eventData["filterIndex"] = Utils::Obs::NumberHelper::GetSourceFilterIndex(source, filter);
eventData["filterSettings"] = Utils::Json::ObsDataToJson(filterSettings);
eventData["defaultFilterSettings"] = Utils::Json::ObsDataToJson(defaultFilterSettings, true);
eventHandler->BroadcastEvent(EventSubscription::Filters, "SourceFilterCreated", eventData);
}
/**
* A filter has been removed from a source.
*
* @dataField sourceName | String | Name of the source the filter was on
* @dataField filterName | String | Name of the filter
*
* @eventType SourceFilterRemoved
* @eventSubscription Filters
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category filters
*/
void EventHandler::HandleSourceFilterRemoved(void *param, calldata_t *data)
{
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "filter");
if (!(source && filter))
return;
eventHandler->DisconnectFilterSignals(filter);
json eventData;
eventData["sourceName"] = obs_source_get_name(source);
eventData["filterName"] = obs_source_get_name(filter);
eventHandler->BroadcastEvent(EventSubscription::Filters, "SourceFilterRemoved", eventData);
}
/**
* A source's filter list has been reindexed.
*
* @dataField sourceName | String | Name of the source
* @dataField filters | Array<Object> | Array of filter objects
*
* @eventType SourceFilterListReindexed
* @eventSubscription Filters
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category filters
*/
void EventHandler::HandleSourceFilterListReindexed(void *param, calldata_t *data)
{
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
return;
json eventData;
eventData["sourceName"] = obs_source_get_name(source);
eventData["filters"] = Utils::Obs::ArrayHelper::GetSourceFilterList(source);
eventHandler->BroadcastEvent(EventSubscription::Filters, "SourceFilterListReindexed", eventData);
}
/**
* A source filter's enable state has changed.
*
* @dataField sourceName | String | Name of the source the filter is on
* @dataField filterName | String | Name of the filter
* @dataField filterEnabled | Boolean | Whether the filter is enabled
*
* @eventType SourceFilterEnableStateChanged
* @eventSubscription Filters
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category filters
*/
void EventHandler::HandleSourceFilterEnableStateChanged(void *param, calldata_t *data)
{
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "source");
if (!filter)
return;
// Not OBSSourceAutoRelease as get_parent doesn't increment refcount
obs_source_t *source = obs_filter_get_parent(filter);
if (!source)
return;
bool filterEnabled = calldata_bool(data, "enabled");
json eventData;
eventData["sourceName"] = obs_source_get_name(source);
eventData["filterName"] = obs_source_get_name(filter);
eventData["filterEnabled"] = filterEnabled;
eventHandler->BroadcastEvent(EventSubscription::Filters, "SourceFilterEnableStateChanged", eventData);
}
/**
* The name of a source filter has changed.
*
* @dataField sourceName | String | The source the filter is on
* @dataField oldFilterName | String | Old name of the filter
* @dataField filterName | String | New name of the filter
*
* @eventType SourceFilterNameChanged
* @eventSubscription Filters
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category filters
*/
void EventHandler::HandleSourceFilterNameChanged(void *param, calldata_t *data)
{
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *filter = GetCalldataPointer<obs_source_t>(data, "source");
if (!filter)
return;
json eventData;
eventData["sourceName"] = obs_source_get_name(obs_filter_get_parent(filter));
eventData["oldFilterName"] = calldata_string(data, "prev_name");
eventData["filterName"] = calldata_string(data, "new_name");
eventHandler->BroadcastEvent(EventSubscription::Filters, "SourceFilterNameChanged", eventData);
}

View File

@ -34,23 +34,3 @@ void EventHandler::HandleExitStarted()
{
BroadcastEvent(EventSubscription::General, "ExitStarted");
}
/**
* Studio mode has been enabled or disabled.
*
* @dataField studioModeEnabled | Boolean | True == Enabled, False == Disabled
*
* @eventType StudioModeStateChanged
* @eventSubscription General
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category general
* @api events
*/
void EventHandler::HandleStudioModeStateChanged(bool enabled)
{
json eventData;
eventData["studioModeEnabled"] = enabled;
BroadcastEvent(EventSubscription::General, "StudioModeStateChanged", eventData);
}

View File

@ -19,6 +19,23 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "EventHandler.h"
/**
* An input has been created.
*
* @dataField inputName | String | Name of the input
* @dataField inputKind | String | The kind of the input
* @dataField unversionedInputKind | String | The unversioned kind of input (aka no `_v2` stuff)
* @dataField inputSettings | Object | The settings configured to the input when it was created
* @dataField defaultInputSettings | Object | The default settings for the input
*
* @eventType InputCreated
* @eventSubscription Inputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category inputs
*/
void EventHandler::HandleInputCreated(obs_source_t *source)
{
std::string inputKind = obs_source_get_id(source);
@ -34,6 +51,19 @@ void EventHandler::HandleInputCreated(obs_source_t *source)
BroadcastEvent(EventSubscription::Inputs, "InputCreated", eventData);
}
/**
* An input has been removed.
*
* @dataField inputName | String | Name of the input
*
* @eventType InputRemoved
* @eventSubscription Inputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category inputs
*/
void EventHandler::HandleInputRemoved(obs_source_t *source)
{
json eventData;
@ -41,6 +71,20 @@ void EventHandler::HandleInputRemoved(obs_source_t *source)
BroadcastEvent(EventSubscription::Inputs, "InputRemoved", eventData);
}
/**
* The name of an input has changed.
*
* @dataField oldInputName | String | Old name of the input
* @dataField inputName | String | New name of the input
*
* @eventType InputNameChanged
* @eventSubscription Inputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category inputs
*/
void EventHandler::HandleInputNameChanged(obs_source_t *, std::string oldInputName, std::string inputName)
{
json eventData;
@ -49,16 +93,25 @@ void EventHandler::HandleInputNameChanged(obs_source_t *, std::string oldInputNa
BroadcastEvent(EventSubscription::Inputs, "InputNameChanged", eventData);
}
void EventHandler::HandleInputVolumeMeters(std::vector<json> inputs)
{
json eventData;
eventData["inputs"] = inputs;
BroadcastEvent(EventSubscription::InputVolumeMeters, "InputVolumeMeters", eventData);
}
/**
* An input's active state has changed.
*
* When an input is active, it means it's being shown by the program feed.
*
* @dataField inputName | String | Name of the input
* @dataField videoActive | Boolean | Whether the input is active
*
* @eventType InputActiveStateChanged
* @eventSubscription InputActiveStateChanged
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category inputs
*/
void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
if (!eventHandler->_inputActiveStateChangedRef.load())
return;
@ -76,9 +129,25 @@ void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data)
eventHandler->BroadcastEvent(EventSubscription::InputActiveStateChanged, "InputActiveStateChanged", eventData);
}
/**
* An input's show state has changed.
*
* When an input is showing, it means it's being shown by the preview or a dialog.
*
* @dataField inputName | String | Name of the input
* @dataField videoShowing | Boolean | Whether the input is showing
*
* @eventType InputShowStateChanged
* @eventSubscription InputShowStateChanged
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category inputs
*/
void EventHandler::HandleInputShowStateChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
if (!eventHandler->_inputShowStateChangedRef.load())
return;
@ -96,9 +165,23 @@ void EventHandler::HandleInputShowStateChanged(void *param, calldata_t *data)
eventHandler->BroadcastEvent(EventSubscription::InputShowStateChanged, "InputShowStateChanged", eventData);
}
/**
* An input's mute state has changed.
*
* @dataField inputName | String | Name of the input
* @dataField inputMuted | Boolean | Whether the input is muted
*
* @eventType InputMuteStateChanged
* @eventSubscription Inputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category inputs
*/
void EventHandler::HandleInputMuteStateChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -113,9 +196,24 @@ void EventHandler::HandleInputMuteStateChanged(void *param, calldata_t *data)
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputMuteStateChanged", eventData);
}
/**
* An input's volume level has changed.
*
* @dataField inputName | String | Name of the input
* @dataField inputVolumeMul | Number | New volume level in multimap
* @dataField inputVolumeDb | Number | New volume level in dB
*
* @eventType InputVolumeChanged
* @eventSubscription Inputs
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category inputs
*/
void EventHandler::HandleInputVolumeChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -138,9 +236,56 @@ void EventHandler::HandleInputVolumeChanged(void *param, calldata_t *data)
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputVolumeChanged", eventData);
}
/**
* The audio balance value of an input has changed.
*
* @dataField inputName | String | Name of the affected input
* @dataField inputAudioBalance | Number | New audio balance value of the input
*
* @eventType InputAudioBalanceChanged
* @eventSubscription Inputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @category inputs
* @api events
*/
void EventHandler::HandleInputAudioBalanceChanged(void *param, calldata_t *data)
{
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
return;
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT)
return;
float inputAudioBalance = (float)calldata_float(data, "balance");
json eventData;
eventData["inputName"] = obs_source_get_name(source);
eventData["inputAudioBalance"] = inputAudioBalance;
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioBalanceChanged", eventData);
}
/**
* The sync offset of an input has changed.
*
* @dataField inputName | String | Name of the input
* @dataField inputAudioSyncOffset | Number | New sync offset in milliseconds
*
* @eventType InputAudioSyncOffsetChanged
* @eventSubscription Inputs
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category inputs
*/
void EventHandler::HandleInputAudioSyncOffsetChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -157,9 +302,23 @@ void EventHandler::HandleInputAudioSyncOffsetChanged(void *param, calldata_t *da
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioSyncOffsetChanged", eventData);
}
/**
* The audio tracks of an input have changed.
*
* @dataField inputName | String | Name of the input
* @dataField inputAudioTracks | Object | Object of audio tracks along with their associated enable states
*
* @eventType InputAudioTracksChanged
* @eventSubscription Inputs
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category inputs
*/
void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -181,9 +340,28 @@ void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data)
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioTracksChanged", eventData);
}
/**
* The monitor type of an input has changed.
*
* Available types are:
* - `OBS_MONITORING_TYPE_NONE`
* - `OBS_MONITORING_TYPE_MONITOR_ONLY`
* - `OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT`
*
* @dataField inputName | String | Name of the input
* @dataField monitorType | String | New monitor type of the input
*
* @eventType InputAudioMonitorTypeChanged
* @eventSubscription Inputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category inputs
*/
void EventHandler::HandleInputAudioMonitorTypeChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -194,22 +372,30 @@ void EventHandler::HandleInputAudioMonitorTypeChanged(void *param, calldata_t *d
enum obs_monitoring_type monitorType = (obs_monitoring_type)calldata_int(data, "type");
std::string monitorTypeString;
switch (monitorType) {
default:
case OBS_MONITORING_TYPE_NONE:
monitorTypeString = "OBS_WEBSOCKET_MONITOR_TYPE_NONE";
break;
case OBS_MONITORING_TYPE_MONITOR_ONLY:
monitorTypeString = "OBS_WEBSOCKET_MONITOR_TYPE_MONITOR_ONLY";
break;
case OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT:
monitorTypeString = "OBS_WEBSOCKET_MONITOR_TYPE_MONITOR_AND_OUTPUT";
break;
}
std::string monitorTypeString = Utils::Obs::StringHelper::GetInputMonitorType(monitorType);
json eventData;
eventData["inputName"] = obs_source_get_name(source);
eventData["monitorType"] = monitorTypeString;
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioMonitorTypeChanged", eventData);
}
/**
* A high-volume event providing volume levels of all active inputs every 50 milliseconds.
*
* @dataField inputs | Array<Object> | Array of active inputs with their associated volume levels
*
* @eventType InputVolumeMeters
* @eventSubscription InputVolumeMeters
* @complexity 4
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category inputs
*/
void EventHandler::HandleInputVolumeMeters(std::vector<json> inputs)
{
json eventData;
eventData["inputs"] = inputs;
BroadcastEvent(EventSubscription::InputVolumeMeters, "InputVolumeMeters", eventData);
}

View File

@ -35,7 +35,7 @@ std::string GetMediaInputActionString(ObsMediaInputAction action) {
void EventHandler::SourceMediaPauseMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -49,7 +49,7 @@ void EventHandler::SourceMediaPauseMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaPlayMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -63,7 +63,7 @@ void EventHandler::SourceMediaPlayMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaRestartMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -77,7 +77,7 @@ void EventHandler::SourceMediaRestartMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaStopMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -91,7 +91,7 @@ void EventHandler::SourceMediaStopMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaNextMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -105,7 +105,7 @@ void EventHandler::SourceMediaNextMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaPreviousMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -117,9 +117,22 @@ void EventHandler::SourceMediaPreviousMultiHandler(void *param, calldata_t *data
eventHandler->HandleMediaInputActionTriggered(source, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS);
}
/**
* A media input has started playing.
*
* @dataField inputName | String | Name of the input
*
* @eventType MediaInputPlaybackStarted
* @eventSubscription MediaInputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category media inputs
*/
void EventHandler::HandleMediaInputPlaybackStarted(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -133,9 +146,22 @@ void EventHandler::HandleMediaInputPlaybackStarted(void *param, calldata_t *data
eventHandler->BroadcastEvent(EventSubscription::MediaInputs, "MediaInputPlaybackStarted", eventData);
}
/**
* A media input has finished playing.
*
* @dataField inputName | String | Name of the input
*
* @eventType MediaInputPlaybackEnded
* @eventSubscription MediaInputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category media inputs
*/
void EventHandler::HandleMediaInputPlaybackEnded(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -149,6 +175,20 @@ void EventHandler::HandleMediaInputPlaybackEnded(void *param, calldata_t *data)
eventHandler->BroadcastEvent(EventSubscription::MediaInputs, "MediaInputPlaybackEnded", eventData);
}
/**
* An action has been performed on an input.
*
* @dataField inputName | String | Name of the input
* @dataField mediaAction | String | Action performed on the input. See `ObsMediaInputAction` enum
*
* @eventType MediaInputActionTriggered
* @eventSubscription MediaInputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category media inputs
*/
void EventHandler::HandleMediaInputActionTriggered(obs_source_t *source, ObsMediaInputAction action)
{
json eventData;

View File

@ -19,21 +19,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "EventHandler.h"
#define CASE(x) case x: return #x;
std::string GetOutputStateString(ObsOutputState state) {
switch (state) {
default:
CASE(OBS_WEBSOCKET_OUTPUT_STARTING)
CASE(OBS_WEBSOCKET_OUTPUT_STARTED)
CASE(OBS_WEBSOCKET_OUTPUT_STOPPING)
CASE(OBS_WEBSOCKET_OUTPUT_STOPPED)
CASE(OBS_WEBSOCKET_OUTPUT_PAUSED)
CASE(OBS_WEBSOCKET_OUTPUT_RESUMED)
}
}
bool GetOutputStateActive(ObsOutputState state) {
static bool GetOutputStateActive(ObsOutputState state) {
switch(state) {
case OBS_WEBSOCKET_OUTPUT_STARTED:
case OBS_WEBSOCKET_OUTPUT_RESUMED:
@ -48,38 +34,107 @@ bool GetOutputStateActive(ObsOutputState state) {
}
}
/**
* The state of the stream output has changed.
*
* @dataField outputActive | Boolean | Whether the output is active
* @dataField outputState | String | The specific state of the output
*
* @eventType StreamStateChanged
* @eventSubscription Outputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category outputs
*/
void EventHandler::HandleStreamStateChanged(ObsOutputState state)
{
json eventData;
eventData["outputActive"] = GetOutputStateActive(state);
eventData["outputState"] = GetOutputStateString(state);
eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state);
BroadcastEvent(EventSubscription::Outputs, "StreamStateChanged", eventData);
}
/**
* The state of the record output has changed.
*
* @dataField outputActive | Boolean | Whether the output is active
* @dataField outputState | String | The specific state of the output
*
* @eventType RecordStateChanged
* @eventSubscription Outputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category outputs
*/
void EventHandler::HandleRecordStateChanged(ObsOutputState state)
{
json eventData;
eventData["outputActive"] = GetOutputStateActive(state);
eventData["outputState"] = GetOutputStateString(state);
eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state);
BroadcastEvent(EventSubscription::Outputs, "RecordStateChanged", eventData);
}
/**
* The state of the replay buffer output has changed.
*
* @dataField outputActive | Boolean | Whether the output is active
* @dataField outputState | String | The specific state of the output
*
* @eventType ReplayBufferStateChanged
* @eventSubscription Outputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category outputs
*/
void EventHandler::HandleReplayBufferStateChanged(ObsOutputState state)
{
json eventData;
eventData["outputActive"] = GetOutputStateActive(state);
eventData["outputState"] = GetOutputStateString(state);
eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state);
BroadcastEvent(EventSubscription::Outputs, "ReplayBufferStateChanged", eventData);
}
/**
* The state of the virtualcam output has changed.
*
* @dataField outputActive | Boolean | Whether the output is active
* @dataField outputState | String | The specific state of the output
*
* @eventType VirtualcamStateChanged
* @eventSubscription Outputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category outputs
*/
void EventHandler::HandleVirtualcamStateChanged(ObsOutputState state)
{
json eventData;
eventData["outputActive"] = GetOutputStateActive(state);
eventData["outputState"] = GetOutputStateString(state);
eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state);
BroadcastEvent(EventSubscription::Outputs, "VirtualcamStateChanged", eventData);
}
/**
* The replay buffer has been saved.
*
* @dataField savedReplayPath | String | Path of the saved replay file
*
* @eventType ReplayBufferSaved
* @eventSubscription Outputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category outputs
*/
void EventHandler::HandleReplayBufferSaved()
{
json eventData;

View File

@ -19,9 +19,25 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "EventHandler.h"
/**
* A scene item has been created.
*
* @dataField sceneName | String | Name of the scene the item was added to
* @dataField sourceName | String | Name of the underlying source (input/scene)
* @dataField sceneItemId | Number | Numeric ID of the scene item
* @dataField sceneItemIndex | Number | Index position of the item
*
* @eventType SceneItemCreated
* @eventSubscription SceneItems
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category scene items
*/
void EventHandler::HandleSceneItemCreated(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene)
@ -33,16 +49,32 @@ void EventHandler::HandleSceneItemCreated(void *param, calldata_t *data)
json eventData;
eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene));
eventData["inputName"] = obs_source_get_name(obs_sceneitem_get_source(sceneItem));
eventData["sourceName"] = obs_source_get_name(obs_sceneitem_get_source(sceneItem));
eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
eventData["sceneItemIndex"] = obs_sceneitem_get_order_position(sceneItem);
eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemCreated", eventData);
}
// Will not be emitted if an item is removed due to the parent scene being removed.
/**
* A scene item has been removed.
*
* This event is not emitted when the scene the item is in is removed.
*
* @dataField sceneName | String | Name of the scene the item was removed from
* @dataField sourceName | String | Name of the underlying source (input/scene)
* @dataField sceneItemId | Number | Numeric ID of the scene item
*
* @eventType SceneItemRemoved
* @eventSubscription SceneItems
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category scene items
*/
void EventHandler::HandleSceneItemRemoved(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene)
@ -54,15 +86,28 @@ void EventHandler::HandleSceneItemRemoved(void *param, calldata_t *data)
json eventData;
eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene));
eventData["inputName"] = obs_source_get_name(obs_sceneitem_get_source(sceneItem));
eventData["sourceName"] = obs_source_get_name(obs_sceneitem_get_source(sceneItem));
eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
eventData["sceneItemIndex"] = obs_sceneitem_get_order_position(sceneItem);
eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemRemoved", eventData);
}
/**
* A scene's item list has been reindexed.
*
* @dataField sceneName | String | Name of the scene
* @dataField sceneItems | Array<Object> | Array of scene item objects
*
* @eventType SceneItemListReindexed
* @eventSubscription SceneItems
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category scene items
*/
void EventHandler::HandleSceneItemListReindexed(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene)
@ -70,13 +115,28 @@ void EventHandler::HandleSceneItemListReindexed(void *param, calldata_t *data)
json eventData;
eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene));
eventData["sceneItems"] = Utils::Obs::ListHelper::GetSceneItemList(scene, true);
eventData["sceneItems"] = Utils::Obs::ArrayHelper::GetSceneItemList(scene, true);
eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemListReindexed", eventData);
}
/**
* A scene item's enable state has changed.
*
* @dataField sceneName | String | Name of the scene the item is in
* @dataField sceneItemId | Number | Numeric ID of the scene item
* @dataField sceneItemEnabled | Boolean | Whether the scene item is enabled (visible)
*
* @eventType SceneItemEnableStateChanged
* @eventSubscription SceneItems
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category scene items
*/
void EventHandler::HandleSceneItemEnableStateChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene)
@ -95,9 +155,24 @@ void EventHandler::HandleSceneItemEnableStateChanged(void *param, calldata_t *da
eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemEnableStateChanged", eventData);
}
/**
* A scene item's lock state has changed.
*
* @dataField sceneName | String | Name of the scene the item is in
* @dataField sceneItemId | Number | Numeric ID of the scene item
* @dataField sceneItemLocked | Boolean | Whether the scene item is locked
*
* @eventType SceneItemLockStateChanged
* @eventSubscription SceneItems
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category scene items
*/
void EventHandler::HandleSceneItemLockStateChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene)
@ -116,9 +191,56 @@ void EventHandler::HandleSceneItemLockStateChanged(void *param, calldata_t *data
eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemLockStateChanged", eventData);
}
/**
* A scene item has been selected in the Ui.
*
* @dataField sceneName | String | Name of the scene the item is in
* @dataField sceneItemId | Number | Numeric ID of the scene item
*
* @eventType SceneItemSelected
* @eventSubscription SceneItems
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category scene items
*/
void EventHandler::HandleSceneItemSelected(void *param, calldata_t *data)
{
auto eventHandler = static_cast<EventHandler*>(param);
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene)
return;
obs_sceneitem_t *sceneItem = GetCalldataPointer<obs_sceneitem_t>(data, "item");
if (!sceneItem)
return;
json eventData;
eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene));
eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemSelected", eventData);
}
/**
* The transform/crop of a scene item has changed.
*
* @dataField sceneName | String | The name of the scene the item is in
* @dataField sceneItemId | Number | Numeric ID of the scene item
* @dataField sceneItemTransform | Object | New transform/crop info of the scene item
*
* @eventType SceneItemTransformChanged
* @eventSubscription SceneItemTransformChanged
* @complexity 4
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category scene items
*/
void EventHandler::HandleSceneItemTransformChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
if (!eventHandler->_sceneItemTransformChangedRef.load())
return;
@ -134,6 +256,6 @@ void EventHandler::HandleSceneItemTransformChanged(void *param, calldata_t *data
json eventData;
eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene));
eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
eventData["sceneItemTransform"] = Utils::Obs::DataHelper::GetSceneItemTransform(sceneItem);
eventData["sceneItemTransform"] = Utils::Obs::ObjectHelper::GetSceneItemTransform(sceneItem);
eventHandler->BroadcastEvent(EventSubscription::SceneItemTransformChanged, "SceneItemTransformChanged", eventData);
}

View File

@ -19,6 +19,20 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "EventHandler.h"
/**
* A new scene has been created.
*
* @dataField sceneName | String | Name of the new scene
* @dataField isGroup | Boolean | Whether the new scene is a group
*
* @eventType SceneCreated
* @eventSubscription Scenes
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category scenes
*/
void EventHandler::HandleSceneCreated(obs_source_t *source)
{
json eventData;
@ -27,6 +41,20 @@ void EventHandler::HandleSceneCreated(obs_source_t *source)
BroadcastEvent(EventSubscription::Scenes, "SceneCreated", eventData);
}
/**
* A scene has been removed.
*
* @dataField sceneName | String | Name of the removed scene
* @dataField isGroup | Boolean | Whether the scene was a group
*
* @eventType SceneRemoved
* @eventSubscription Scenes
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category scenes
*/
void EventHandler::HandleSceneRemoved(obs_source_t *source)
{
json eventData;
@ -35,6 +63,20 @@ void EventHandler::HandleSceneRemoved(obs_source_t *source)
BroadcastEvent(EventSubscription::Scenes, "SceneRemoved", eventData);
}
/**
* The name of a scene has changed.
*
* @dataField oldSceneName | String | Old name of the scene
* @dataField sceneName | String | New name of the scene
*
* @eventType SceneNameChanged
* @eventSubscription Scenes
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category scenes
*/
void EventHandler::HandleSceneNameChanged(obs_source_t *, std::string oldSceneName, std::string sceneName)
{
json eventData;
@ -43,15 +85,41 @@ void EventHandler::HandleSceneNameChanged(obs_source_t *, std::string oldSceneNa
BroadcastEvent(EventSubscription::Scenes, "SceneNameChanged", eventData);
}
void EventHandler::HandleCurrentSceneChanged()
/**
* The current program scene has changed.
*
* @dataField sceneName | String | Name of the scene that was switched to
*
* @eventType CurrentProgramSceneChanged
* @eventSubscription Scenes
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category scenes
*/
void EventHandler::HandleCurrentProgramSceneChanged()
{
OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
json eventData;
eventData["sceneName"] = obs_source_get_name(currentScene);
BroadcastEvent(EventSubscription::Scenes, "CurrentSceneChanged", eventData);
BroadcastEvent(EventSubscription::Scenes, "CurrentProgramSceneChanged", eventData);
}
/**
* The current preview scene has changed.
*
* @dataField sceneName | String | Name of the scene that was switched to
*
* @eventType CurrentPreviewSceneChanged
* @eventSubscription Scenes
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category scenes
*/
void EventHandler::HandleCurrentPreviewSceneChanged()
{
OBSSourceAutoRelease currentPreviewScene = obs_frontend_get_current_preview_scene();
@ -65,9 +133,24 @@ void EventHandler::HandleCurrentPreviewSceneChanged()
BroadcastEvent(EventSubscription::Scenes, "CurrentPreviewSceneChanged", eventData);
}
/**
* The list of scenes has changed.
*
* TODO: Make OBS fire this event when scenes are reordered.
*
* @dataField scenes | Array<Object> | Updated array of scenes
*
* @eventType SceneListChanged
* @eventSubscription Scenes
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category scenes
*/
void EventHandler::HandleSceneListChanged()
{
json eventData;
eventData["scenes"] = Utils::Obs::ListHelper::GetSceneList();
eventData["scenes"] = Utils::Obs::ArrayHelper::GetSceneList();
BroadcastEvent(EventSubscription::Scenes, "SceneListChanged", eventData);
}

View File

@ -19,26 +19,44 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "EventHandler.h"
void EventHandler::HandleTransitionCreated(obs_source_t *source)
/**
* The current scene transition has changed.
*
* @dataField transitionName | String | Name of the new transition
*
* @eventType CurrentSceneTransitionChanged
* @eventSubscription Transitions
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category transitions
*/
void EventHandler::HandleCurrentSceneTransitionChanged()
{
OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
json eventData;
eventData["transitionName"] = obs_source_get_name(source);
eventData["transitionKind"] = obs_source_get_id(source);
eventData["transitionFixed"] = obs_transition_fixed(source);
BroadcastEvent(EventSubscription::Transitions, "TransitionCreated", eventData);
eventData["transitionName"] = obs_source_get_name(transition);
BroadcastEvent(EventSubscription::Transitions, "CurrentSceneTransitionChanged", eventData);
}
void EventHandler::HandleTransitionRemoved(obs_source_t *source)
/**
* The current scene transition duration has changed.
*
* @dataField transitionDuration | Number | Transition duration in milliseconds
*
* @eventType CurrentSceneTransitionDurationChanged
* @eventSubscription Transitions
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api events
* @category transitions
*/
void EventHandler::HandleCurrentSceneTransitionDurationChanged()
{
json eventData;
eventData["transitionName"] = obs_source_get_name(source);
BroadcastEvent(EventSubscription::Transitions, "TransitionRemoved", eventData);
}
void EventHandler::HandleTransitionNameChanged(obs_source_t *, std::string oldTransitionName, std::string transitionName)
{
json eventData;
eventData["oldTransitionName"] = oldTransitionName;
eventData["transitionName"] = transitionName;
BroadcastEvent(EventSubscription::Transitions, "TransitionNameChanged", eventData);
eventData["transitionDuration"] = obs_frontend_get_transition_duration();
BroadcastEvent(EventSubscription::Transitions, "CurrentSceneTransitionDurationChanged", eventData);
}

View File

@ -19,3 +19,22 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "EventHandler.h"
/**
* Studio mode has been enabled or disabled.
*
* @dataField studioModeEnabled | Boolean | True == Enabled, False == Disabled
*
* @eventType StudioModeStateChanged
* @eventSubscription Ui
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category ui
* @api events
*/
void EventHandler::HandleStudioModeStateChanged(bool enabled)
{
json eventData;
eventData["studioModeEnabled"] = enabled;
BroadcastEvent(EventSubscription::Ui, "StudioModeStateChanged", eventData);
}

View File

@ -130,7 +130,6 @@ namespace EventSubscription {
* @initialVersion 5.0.0
* @api enums
*/
// Receive events in the `MediaInputs` category
MediaInputs = (1 << 8),
/**
* Subscription value to receive the `VendorEvent` event.
@ -142,9 +141,19 @@ namespace EventSubscription {
* @initialVersion 5.0.0
* @api enums
*/
// Receive events from external OBS plugins and scripts
Vendors = (1 << 9),
/**
* Subscription value to receive events in the `Ui` category.
*
* @enumIdentifier Ui
* @enumValue (1 << 10)
* @enumType EventSubscription
* @rpcVersion -1
* @initialVersion 5.0.0
* @api enums
*/
Ui = (1 << 10),
/**
* Helper to receive all non-high-volume events.
*
* @enumIdentifier All
@ -154,8 +163,7 @@ namespace EventSubscription {
* @initialVersion 5.0.0
* @api enums
*/
// Receive all event categories (exclude high-volume)
All = (General | Config | Scenes | Inputs | Transitions | Filters | Outputs | SceneItems | MediaInputs | Vendors),
All = (General | Config | Scenes | Inputs | Transitions | Filters | Outputs | SceneItems | MediaInputs | Ui | Vendors),
/**
* Subscription value to receive the `InputVolumeMeters` high-volume event.
*
@ -166,7 +174,6 @@ namespace EventSubscription {
* @initialVersion 5.0.0
* @api enums
*/
// InputVolumeMeters event (high-volume)
InputVolumeMeters = (1 << 16),
/**
* Subscription value to receive the `InputActiveStateChanged` high-volume event.
@ -178,7 +185,6 @@ namespace EventSubscription {
* @initialVersion 5.0.0
* @api enums
*/
// InputActiveStateChanged event (high-volume)
InputActiveStateChanged = (1 << 17),
/**
* Subscription value to receive the `InputShowStateChanged` high-volume event.
@ -190,7 +196,6 @@ namespace EventSubscription {
* @initialVersion 5.0.0
* @api enums
*/
// InputShowStateChanged event (high-volume)
InputShowStateChanged = (1 << 18),
/**
* Subscription value to receive the `SceneItemTransformChanged` high-volume event.
@ -202,7 +207,6 @@ namespace EventSubscription {
* @initialVersion 5.0.0
* @api enums
*/
// SceneItemTransformChanged event (high-volume)
SceneItemTransformChanged = (1 << 19),
};
}

View File

@ -151,7 +151,7 @@
<number>65534</number>
</property>
<property name="value">
<number>4444</number>
<number>4455</number>
</property>
</widget>
</item>

View File

@ -35,12 +35,12 @@ OBS_MODULE_AUTHOR("OBSProject")
const char *obs_module_name(void) { return "obs-websocket"; }
const char *obs_module_description(void) { return obs_module_text("OBSWebSocket.Plugin.Description"); }
os_cpu_usage_info_t* _cpuUsageInfo;
ConfigPtr _config;
EventHandlerPtr _eventHandler;
WebSocketApiPtr _webSocketApi;
WebSocketServerPtr _webSocketServer;
EventHandlerPtr _eventHandler;
SettingsDialog *_settingsDialog = nullptr;
os_cpu_usage_info_t* _cpuUsageInfo;
void WebSocketApiEventCallback(std::string vendorName, std::string eventType, obs_data_t *obsEventData);
@ -48,32 +48,37 @@ bool obs_module_load(void)
{
blog(LOG_INFO, "[obs_module_load] you can haz websockets (Version: %s | RPC Version: %d)", OBS_WEBSOCKET_VERSION, OBS_WEBSOCKET_RPC_VERSION);
blog(LOG_INFO, "[obs_module_load] Qt version (compile-time): %s | Qt version (run-time): %s", QT_VERSION_STR, qVersion());
blog(LOG_INFO, "[obs_module_load] Linked ASIO Version: %d", ASIO_VERSION);
// Create the config object then load the parameters from storage
// Initialize the cpu stats
_cpuUsageInfo = os_cpu_usage_info_start();
// Create the config manager then load the parameters from storage
_config = ConfigPtr(new Config());
_config->Load();
// Initialize event handler before server, as the server configures the event handler.
// Initialize the event handler
_eventHandler = EventHandlerPtr(new EventHandler());
_webSocketApi = WebSocketApiPtr(new WebSocketApi(WebSocketApiEventCallback));
// Initialize the plugin/script API
_webSocketApi = WebSocketApiPtr(new WebSocketApi());
_webSocketApi->SetEventCallback(WebSocketApiEventCallback);
// Initialize the WebSocket server
_webSocketServer = WebSocketServerPtr(new WebSocketServer());
// Initialize the settings dialog
obs_frontend_push_ui_translation(obs_module_get_string);
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window());
QMainWindow* mainWindow = static_cast<QMainWindow*>(obs_frontend_get_main_window());
_settingsDialog = new SettingsDialog(mainWindow);
obs_frontend_pop_ui_translation();
// Add the settings dialog to the tools menu
const char* menuActionText = obs_module_text("OBSWebSocket.Settings.DialogTitle");
QAction* menuAction = (QAction*)obs_frontend_add_tools_menu_qaction(menuActionText);
QObject::connect(menuAction, &QAction::triggered, [] { _settingsDialog->ToggleShowHide(); });
_cpuUsageInfo = os_cpu_usage_info_start();
// Loading finished
blog(LOG_INFO, "[obs_module_load] Module loaded.");
return true;
}
@ -81,29 +86,46 @@ void obs_module_unload()
{
blog(LOG_INFO, "[obs_module_unload] Shutting down...");
// Shutdown the WebSocket server if it is running
if (_webSocketServer->IsListening()) {
blog_debug("[obs_module_unload] WebSocket server is running. Stopping...");
_webSocketServer->Stop();
}
// Destroy the WebSocket server
_webSocketServer.reset();
_eventHandler.reset();
// Destroy the plugin/script api
_webSocketApi.reset();
// Destroy the event handler
_eventHandler.reset();
// Save and destroy the config manager
_config->Save();
_config.reset();
// Destroy the cpu stats
os_cpu_usage_info_destroy(_cpuUsageInfo);
blog(LOG_INFO, "[obs_module_unload] Finished shutting down.");
}
os_cpu_usage_info_t* GetCpuUsageInfo()
{
return _cpuUsageInfo;
}
ConfigPtr GetConfig()
{
return _config;
}
EventHandlerPtr GetEventHandler()
{
return _eventHandler;
}
WebSocketApiPtr GetWebSocketApi()
{
return _webSocketApi;
@ -114,32 +136,11 @@ WebSocketServerPtr GetWebSocketServer()
return _webSocketServer;
}
EventHandlerPtr GetEventHandler()
{
return _eventHandler;
}
os_cpu_usage_info_t* GetCpuUsageInfo()
{
return _cpuUsageInfo;
}
bool IsDebugEnabled()
{
return !_config || _config->DebugEnabled;
}
void ___source_dummy_addref(obs_source_t*) {}
void ___weak_source_dummy_addref(obs_weak_source_t*) {}
void ___scene_dummy_addref(obs_scene_t*) {}
void ___sceneitem_dummy_addref(obs_sceneitem_t*) {}
void ___data_dummy_addref(obs_data_t*) {}
void ___data_array_dummy_addref(obs_data_array_t*) {}
void ___output_dummy_addref(obs_output_t*) {}
void ___data_item_dummy_addref(obs_data_item_t*) {}
void ___data_item_release(obs_data_item_t* dataItem){ obs_data_item_release(&dataItem); }
void ___properties_dummy_addref(obs_properties_t*) {}
/**
* An event has been emitted from a vendor.
*

View File

@ -21,58 +21,31 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <memory>
#include <obs.hpp>
#ifdef _MSC_VER
#pragma push_macro("strtoll")
#endif
#include <util/platform.h>
#ifdef _MSC_VER
#pragma pop_macro("strtoll")
#endif
#include "utils/Obs.h"
#include "plugin-macros.generated.h"
// Autorelease object definitions
void ___source_dummy_addref(obs_source_t*);
void ___weak_source_dummy_addref(obs_weak_source_t*);
void ___scene_dummy_addref(obs_scene_t*);
void ___sceneitem_dummy_addref(obs_sceneitem_t*);
void ___data_dummy_addref(obs_data_t*);
void ___data_array_dummy_addref(obs_data_array_t*);
void ___output_dummy_addref(obs_output_t*);
void ___data_item_dummy_addref(obs_data_item_t*);
void ___data_item_release(obs_data_item_t*);
void ___properties_dummy_addref(obs_properties_t*);
using OBSSourceAutoRelease = OBSRef<obs_source_t*, ___source_dummy_addref, obs_source_release>;
using OBSWeakSourceAutoRelease = OBSRef<obs_weak_source_t*, ___weak_source_dummy_addref, obs_weak_source_release>;
using OBSSceneAutoRelease = OBSRef<obs_scene_t*, ___scene_dummy_addref, obs_scene_release>;
using OBSSceneItemAutoRelease = OBSRef<obs_sceneitem_t*, ___sceneitem_dummy_addref, obs_sceneitem_release>;
using OBSDataAutoRelease = OBSRef<obs_data_t*, ___data_dummy_addref, obs_data_release>;
using OBSDataArrayAutoRelease = OBSRef<obs_data_array_t*, ___data_array_dummy_addref, obs_data_array_release>;
using OBSOutputAutoRelease = OBSRef<obs_output_t*, ___output_dummy_addref, obs_output_release>;
using OBSDataItemAutoRelease = OBSRef<obs_data_item_t*, ___data_item_dummy_addref, ___data_item_release>;
using OBSPropertiesAutoDestroy = OBSRef<obs_properties_t*, ___properties_dummy_addref, obs_properties_destroy>;
class Config;
typedef std::shared_ptr<Config> ConfigPtr;
class EventHandler;
typedef std::shared_ptr<EventHandler> EventHandlerPtr;
class WebSocketApi;
typedef std::shared_ptr<WebSocketApi> WebSocketApiPtr;
class WebSocketServer;
typedef std::shared_ptr<WebSocketServer> WebSocketServerPtr;
class EventHandler;
typedef std::shared_ptr<EventHandler> EventHandlerPtr;
os_cpu_usage_info_t* GetCpuUsageInfo();
ConfigPtr GetConfig();
EventHandlerPtr GetEventHandler();
WebSocketApiPtr GetWebSocketApi();
WebSocketServerPtr GetWebSocketServer();
EventHandlerPtr GetEventHandler();
os_cpu_usage_info_t* GetCpuUsageInfo();
bool IsDebugEnabled();

View File

@ -109,7 +109,7 @@ static void ObsTickCallback(void *param, float)
{
ScopeProfiler prof{"obs_websocket_request_batch_frame_tick"};
auto serialFrameBatch = reinterpret_cast<SerialFrameBatch*>(param);
auto serialFrameBatch = static_cast<SerialFrameBatch*>(param);
// Increment frame count
serialFrameBatch->frameCount++;

View File

@ -17,9 +17,13 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#ifdef PLUGIN_TESTS
#include <util/profiler.hpp>
#endif
#include "RequestHandler.h"
const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{
// General
{"GetVersion", &RequestHandler::GetVersion},
@ -29,8 +33,6 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"GetHotkeyList", &RequestHandler::GetHotkeyList},
{"TriggerHotkeyByName", &RequestHandler::TriggerHotkeyByName},
{"TriggerHotkeyByKeySequence", &RequestHandler::TriggerHotkeyByKeySequence},
{"GetStudioModeEnabled", &RequestHandler::GetStudioModeEnabled},
{"SetStudioModeEnabled", &RequestHandler::SetStudioModeEnabled},
{"Sleep", &RequestHandler::Sleep},
// Config
@ -49,14 +51,18 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"SetVideoSettings", &RequestHandler::SetVideoSettings},
{"GetStreamServiceSettings", &RequestHandler::GetStreamServiceSettings},
{"SetStreamServiceSettings", &RequestHandler::SetStreamServiceSettings},
{"GetRecordDirectory", &RequestHandler::GetRecordDirectory},
// Sources
{"GetSourceActive", &RequestHandler::GetSourceActive},
{"GetSourceScreenshot", &RequestHandler::GetSourceScreenshot},
{"SaveSourceScreenshot", &RequestHandler::SaveSourceScreenshot},
{"GetSourcePrivateSettings", &RequestHandler::GetSourcePrivateSettings},
{"SetSourcePrivateSettings", &RequestHandler::SetSourcePrivateSettings},
// Scenes
{"GetSceneList", &RequestHandler::GetSceneList},
{"GetGroupList", &RequestHandler::GetGroupList},
{"GetCurrentProgramScene", &RequestHandler::GetCurrentProgramScene},
{"SetCurrentProgramScene", &RequestHandler::SetCurrentProgramScene},
{"GetCurrentPreviewScene", &RequestHandler::GetCurrentPreviewScene},
@ -64,12 +70,15 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"CreateScene", &RequestHandler::CreateScene},
{"RemoveScene", &RequestHandler::RemoveScene},
{"SetSceneName", &RequestHandler::SetSceneName},
{"GetSceneSceneTransitionOverride", &RequestHandler::GetSceneSceneTransitionOverride},
{"SetSceneSceneTransitionOverride", &RequestHandler::SetSceneSceneTransitionOverride},
// Inputs
{"GetInputList", &RequestHandler::GetInputList},
{"GetInputKindList", &RequestHandler::GetInputKindList},
{"GetSpecialInputs", &RequestHandler::GetSpecialInputs},
{"CreateInput", &RequestHandler::CreateInput},
//{"RemoveInput", &RequestHandler::RemoveInput}, // Disabled for now. Pending obs-studio#5276
{"RemoveInput", &RequestHandler::RemoveInput},
{"SetInputName", &RequestHandler::SetInputName},
{"GetInputDefaultSettings", &RequestHandler::GetInputDefaultSettings},
{"GetInputSettings", &RequestHandler::GetInputSettings},
@ -79,13 +88,39 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"ToggleInputMute", &RequestHandler::ToggleInputMute},
{"GetInputVolume", &RequestHandler::GetInputVolume},
{"SetInputVolume", &RequestHandler::SetInputVolume},
{"GetInputAudioBalance", &RequestHandler::GetInputAudioBalance},
{"SetInputAudioBalance", &RequestHandler::SetInputAudioBalance},
{"GetInputAudioSyncOffset", &RequestHandler::GetInputAudioSyncOffset},
{"SetInputAudioSyncOffset", &RequestHandler::SetInputAudioSyncOffset},
{"GetInputAudioMonitorType", &RequestHandler::GetInputAudioMonitorType},
{"SetInputAudioMonitorType", &RequestHandler::SetInputAudioMonitorType},
{"GetInputAudioTracks", &RequestHandler::GetInputAudioTracks},
{"SetInputAudioTracks", &RequestHandler::SetInputAudioTracks},
{"GetInputPropertiesListPropertyItems", &RequestHandler::GetInputPropertiesListPropertyItems},
{"PressInputPropertiesButton", &RequestHandler::PressInputPropertiesButton},
// Transitions
{"GetTransitionKindList", &RequestHandler::GetTransitionKindList},
{"GetSceneTransitionList", &RequestHandler::GetSceneTransitionList},
{"GetCurrentSceneTransition", &RequestHandler::GetCurrentSceneTransition},
{"SetCurrentSceneTransition", &RequestHandler::SetCurrentSceneTransition},
{"SetCurrentSceneTransitionDuration", &RequestHandler::SetCurrentSceneTransitionDuration},
{"SetCurrentSceneTransitionSettings", &RequestHandler::SetCurrentSceneTransitionSettings},
{"GetCurrentSceneTransitionCursor", &RequestHandler::GetCurrentSceneTransitionCursor},
{"TriggerStudioModeTransition", &RequestHandler::TriggerStudioModeTransition},
{"SetTBarPosition", &RequestHandler::SetTBarPosition},
// Filters
{"GetSourceFilterList", &RequestHandler::GetSourceFilterList},
{"GetSourceFilterDefaultSettings", &RequestHandler::GetSourceFilterDefaultSettings},
{"CreateSourceFilter", &RequestHandler::CreateSourceFilter},
{"RemoveSourceFilter", &RequestHandler::RemoveSourceFilter},
{"SetSourceFilterName", &RequestHandler::SetSourceFilterName},
{"GetSourceFilter", &RequestHandler::GetSourceFilter},
{"SetSourceFilterIndex", &RequestHandler::SetSourceFilterIndex},
{"SetSourceFilterSettings", &RequestHandler::SetSourceFilterSettings},
{"SetSourceFilterEnabled", &RequestHandler::SetSourceFilterEnabled},
// Scene Items
{"GetSceneItemList", &RequestHandler::GetSceneItemList},
{"GetGroupSceneItemList", &RequestHandler::GetGroupSceneItemList},
@ -101,12 +136,27 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"SetSceneItemLocked", &RequestHandler::SetSceneItemLocked},
{"GetSceneItemIndex", &RequestHandler::GetSceneItemIndex},
{"SetSceneItemIndex", &RequestHandler::SetSceneItemIndex},
{"GetSceneItemBlendMode", &RequestHandler::GetSceneItemBlendMode},
{"SetSceneItemBlendMode", &RequestHandler::SetSceneItemBlendMode},
// Outputs
{"GetVirtualCamStatus", &RequestHandler::GetVirtualCamStatus},
{"ToggleVirtualCam", &RequestHandler::ToggleVirtualCam},
{"StartVirtualCam", &RequestHandler::StartVirtualCam},
{"StopVirtualCam", &RequestHandler::StopVirtualCam},
{"GetReplayBufferStatus", &RequestHandler::GetReplayBufferStatus},
{"ToggleReplayBuffer", &RequestHandler::ToggleReplayBuffer},
{"StartReplayBuffer", &RequestHandler::StartReplayBuffer},
{"StopReplayBuffer", &RequestHandler::StopReplayBuffer},
{"SaveReplayBuffer", &RequestHandler::SaveReplayBuffer},
{"GetLastReplayBufferReplay", &RequestHandler::GetLastReplayBufferReplay},
// Stream
{"GetStreamStatus", &RequestHandler::GetStreamStatus},
{"ToggleStream", &RequestHandler::ToggleStream},
{"StartStream", &RequestHandler::StartStream},
{"StopStream", &RequestHandler::StopStream},
{"SendStreamCaption", &RequestHandler::SendStreamCaption},
// Record
{"GetRecordStatus", &RequestHandler::GetRecordStatus},
@ -116,13 +166,19 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"ToggleRecordPause", &RequestHandler::ToggleRecordPause},
{"PauseRecord", &RequestHandler::PauseRecord},
{"ResumeRecord", &RequestHandler::ResumeRecord},
//{"GetRecordDirectory", &RequestHandler::GetRecordDirectory},
// Media Inputs
{"GetMediaInputStatus", &RequestHandler::GetMediaInputStatus},
{"SetMediaInputCursor", &RequestHandler::SetMediaInputCursor},
{"OffsetMediaInputCursor", &RequestHandler::OffsetMediaInputCursor},
{"TriggerMediaInputAction", &RequestHandler::TriggerMediaInputAction},
// Ui
{"GetStudioModeEnabled", &RequestHandler::GetStudioModeEnabled},
{"SetStudioModeEnabled", &RequestHandler::SetStudioModeEnabled},
{"OpenInputPropertiesDialog", &RequestHandler::OpenInputPropertiesDialog},
{"OpenInputFiltersDialog", &RequestHandler::OpenInputFiltersDialog},
{"OpenInputInteractDialog", &RequestHandler::OpenInputInteractDialog},
};
RequestHandler::RequestHandler(SessionPtr session) :
@ -132,6 +188,10 @@ RequestHandler::RequestHandler(SessionPtr session) :
RequestResult RequestHandler::ProcessRequest(const Request& request)
{
#ifdef PLUGIN_TESTS
ScopeProfiler prof{"obs_websocket_request_processing"};
#endif
if (!request.RequestData.is_object() && !request.RequestData.is_null())
return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "Your request data is not an object.");

View File

@ -19,7 +19,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#pragma once
#include <map>
#include <unordered_map>
#include <obs.hpp>
#include <obs-frontend-api.h>
@ -51,8 +51,6 @@ class RequestHandler {
RequestResult GetHotkeyList(const Request&);
RequestResult TriggerHotkeyByName(const Request&);
RequestResult TriggerHotkeyByKeySequence(const Request&);
RequestResult GetStudioModeEnabled(const Request&);
RequestResult SetStudioModeEnabled(const Request&);
RequestResult Sleep(const Request&);
// Config
@ -71,14 +69,18 @@ class RequestHandler {
RequestResult SetVideoSettings(const Request&);
RequestResult GetStreamServiceSettings(const Request&);
RequestResult SetStreamServiceSettings(const Request&);
RequestResult GetRecordDirectory(const Request&);
// Sources
RequestResult GetSourceActive(const Request&);
RequestResult GetSourceScreenshot(const Request&);
RequestResult SaveSourceScreenshot(const Request&);
RequestResult GetSourcePrivateSettings(const Request&);
RequestResult SetSourcePrivateSettings(const Request&);
// Scenes
RequestResult GetSceneList(const Request&);
RequestResult GetGroupList(const Request&);
RequestResult GetCurrentProgramScene(const Request&);
RequestResult SetCurrentProgramScene(const Request&);
RequestResult GetCurrentPreviewScene(const Request&);
@ -86,10 +88,13 @@ class RequestHandler {
RequestResult CreateScene(const Request&);
RequestResult RemoveScene(const Request&);
RequestResult SetSceneName(const Request&);
RequestResult GetSceneSceneTransitionOverride(const Request&);
RequestResult SetSceneSceneTransitionOverride(const Request&);
// Inputs
RequestResult GetInputList(const Request&);
RequestResult GetInputKindList(const Request&);
RequestResult GetSpecialInputs(const Request&);
RequestResult CreateInput(const Request&);
RequestResult RemoveInput(const Request&);
RequestResult SetInputName(const Request&);
@ -101,13 +106,39 @@ class RequestHandler {
RequestResult ToggleInputMute(const Request&);
RequestResult GetInputVolume(const Request&);
RequestResult SetInputVolume(const Request&);
RequestResult GetInputAudioBalance(const Request&);
RequestResult SetInputAudioBalance(const Request&);
RequestResult GetInputAudioSyncOffset(const Request&);
RequestResult SetInputAudioSyncOffset(const Request&);
RequestResult GetInputAudioMonitorType(const Request&);
RequestResult SetInputAudioMonitorType(const Request&);
RequestResult GetInputAudioTracks(const Request&);
RequestResult SetInputAudioTracks(const Request&);
RequestResult GetInputPropertiesListPropertyItems(const Request&);
RequestResult PressInputPropertiesButton(const Request&);
// Transitions
RequestResult GetTransitionKindList(const Request&);
RequestResult GetSceneTransitionList(const Request&);
RequestResult GetCurrentSceneTransition(const Request&);
RequestResult SetCurrentSceneTransition(const Request&);
RequestResult SetCurrentSceneTransitionDuration(const Request&);
RequestResult SetCurrentSceneTransitionSettings(const Request&);
RequestResult GetCurrentSceneTransitionCursor(const Request&);
RequestResult TriggerStudioModeTransition(const Request&);
RequestResult SetTBarPosition(const Request&);
// Filters
RequestResult GetSourceFilterList(const Request&);
RequestResult GetSourceFilterDefaultSettings(const Request&);
RequestResult CreateSourceFilter(const Request&);
RequestResult RemoveSourceFilter(const Request&);
RequestResult SetSourceFilterName(const Request&);
RequestResult GetSourceFilter(const Request&);
RequestResult SetSourceFilterIndex(const Request&);
RequestResult SetSourceFilterSettings(const Request&);
RequestResult SetSourceFilterEnabled(const Request&);
// Scene Items
RequestResult GetSceneItemList(const Request&);
RequestResult GetGroupSceneItemList(const Request&);
@ -123,12 +154,27 @@ class RequestHandler {
RequestResult SetSceneItemLocked(const Request&);
RequestResult GetSceneItemIndex(const Request&);
RequestResult SetSceneItemIndex(const Request&);
RequestResult GetSceneItemBlendMode(const Request&);
RequestResult SetSceneItemBlendMode(const Request&);
// Outputs
RequestResult GetVirtualCamStatus(const Request&);
RequestResult ToggleVirtualCam(const Request&);
RequestResult StartVirtualCam(const Request&);
RequestResult StopVirtualCam(const Request&);
RequestResult GetReplayBufferStatus(const Request&);
RequestResult ToggleReplayBuffer(const Request&);
RequestResult StartReplayBuffer(const Request&);
RequestResult StopReplayBuffer(const Request&);
RequestResult SaveReplayBuffer(const Request&);
RequestResult GetLastReplayBufferReplay(const Request&);
// Stream
RequestResult GetStreamStatus(const Request&);
RequestResult ToggleStream(const Request&);
RequestResult StartStream(const Request&);
RequestResult StopStream(const Request&);
RequestResult SendStreamCaption(const Request&);
// Record
RequestResult GetRecordStatus(const Request&);
@ -138,7 +184,6 @@ class RequestHandler {
RequestResult ToggleRecordPause(const Request&);
RequestResult PauseRecord(const Request&);
RequestResult ResumeRecord(const Request&);
RequestResult GetRecordDirectory(const Request&);
// Media Inputs
RequestResult GetMediaInputStatus(const Request&);
@ -146,6 +191,13 @@ class RequestHandler {
RequestResult OffsetMediaInputCursor(const Request&);
RequestResult TriggerMediaInputAction(const Request&);
// Ui
RequestResult GetStudioModeEnabled(const Request&);
RequestResult SetStudioModeEnabled(const Request&);
RequestResult OpenInputPropertiesDialog(const Request&);
RequestResult OpenInputFiltersDialog(const Request&);
RequestResult OpenInputInteractDialog(const Request&);
SessionPtr _session;
static const std::map<std::string, RequestMethodHandler> _handlerMap;
static const std::unordered_map<std::string, RequestMethodHandler> _handlerMap;
};

View File

@ -124,7 +124,7 @@ RequestResult RequestHandler::GetSceneCollectionList(const Request&)
{
json responseData;
responseData["currentSceneCollectionName"] = Utils::Obs::StringHelper::GetCurrentSceneCollection();
responseData["sceneCollections"] = Utils::Obs::ListHelper::GetSceneCollectionList();
responseData["sceneCollections"] = Utils::Obs::ArrayHelper::GetSceneCollectionList();
return RequestResult::Success(responseData);
}
@ -151,7 +151,7 @@ RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request)
std::string sceneCollectionName = request.RequestData["sceneCollectionName"];
auto sceneCollections = Utils::Obs::ListHelper::GetSceneCollectionList();
auto sceneCollections = Utils::Obs::ArrayHelper::GetSceneCollectionList();
if (std::find(sceneCollections.begin(), sceneCollections.end(), sceneCollectionName) == sceneCollections.end())
return RequestResult::Error(RequestStatus::ResourceNotFound);
@ -159,7 +159,7 @@ RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request)
// Avoid queueing tasks if nothing will change
if (currentSceneCollectionName != sceneCollectionName) {
obs_queue_task(OBS_TASK_UI, [](void* param) {
obs_frontend_set_current_scene_collection(reinterpret_cast<const char*>(param));
obs_frontend_set_current_scene_collection(static_cast<const char*>(param));
}, (void*)sceneCollectionName.c_str(), true);
}
@ -189,11 +189,11 @@ RequestResult RequestHandler::CreateSceneCollection(const Request& request)
std::string sceneCollectionName = request.RequestData["sceneCollectionName"];
auto sceneCollections = Utils::Obs::ListHelper::GetSceneCollectionList();
auto sceneCollections = Utils::Obs::ArrayHelper::GetSceneCollectionList();
if (std::find(sceneCollections.begin(), sceneCollections.end(), sceneCollectionName) != sceneCollections.end())
return RequestResult::Error(RequestStatus::ResourceAlreadyExists);
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window());
QMainWindow* mainWindow = static_cast<QMainWindow*>(obs_frontend_get_main_window());
bool success = false;
QMetaObject::invokeMethod(mainWindow, "AddSceneCollection", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, success), Q_ARG(bool, true), Q_ARG(QString, QString::fromStdString(sceneCollectionName)));
if (!success)
@ -219,7 +219,7 @@ RequestResult RequestHandler::GetProfileList(const Request&)
{
json responseData;
responseData["currentProfileName"] = Utils::Obs::StringHelper::GetCurrentProfile();
responseData["profiles"] = Utils::Obs::ListHelper::GetProfileList();
responseData["profiles"] = Utils::Obs::ArrayHelper::GetProfileList();
return RequestResult::Success(responseData);
}
@ -244,7 +244,7 @@ RequestResult RequestHandler::SetCurrentProfile(const Request& request)
std::string profileName = request.RequestData["profileName"];
auto profiles = Utils::Obs::ListHelper::GetProfileList();
auto profiles = Utils::Obs::ArrayHelper::GetProfileList();
if (std::find(profiles.begin(), profiles.end(), profileName) == profiles.end())
return RequestResult::Error(RequestStatus::ResourceNotFound);
@ -252,7 +252,7 @@ RequestResult RequestHandler::SetCurrentProfile(const Request& request)
// Avoid queueing tasks if nothing will change
if (currentProfileName != profileName) {
obs_queue_task(OBS_TASK_UI, [](void* param) {
obs_frontend_set_current_profile(reinterpret_cast<const char*>(param));
obs_frontend_set_current_profile(static_cast<const char*>(param));
}, (void*)profileName.c_str(), true);
}
@ -280,11 +280,11 @@ RequestResult RequestHandler::CreateProfile(const Request& request)
std::string profileName = request.RequestData["profileName"];
auto profiles = Utils::Obs::ListHelper::GetProfileList();
auto profiles = Utils::Obs::ArrayHelper::GetProfileList();
if (std::find(profiles.begin(), profiles.end(), profileName) != profiles.end())
return RequestResult::Error(RequestStatus::ResourceAlreadyExists);
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window());
QMainWindow* mainWindow = static_cast<QMainWindow*>(obs_frontend_get_main_window());
QMetaObject::invokeMethod(mainWindow, "NewProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName)));
return RequestResult::Success();
@ -311,14 +311,14 @@ RequestResult RequestHandler::RemoveProfile(const Request& request)
std::string profileName = request.RequestData["profileName"];
auto profiles = Utils::Obs::ListHelper::GetProfileList();
auto profiles = Utils::Obs::ArrayHelper::GetProfileList();
if (std::find(profiles.begin(), profiles.end(), profileName) == profiles.end())
return RequestResult::Error(RequestStatus::ResourceNotFound);
if (profiles.size() < 2)
return RequestResult::Error(RequestStatus::NotEnoughResources);
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window());
QMainWindow* mainWindow = static_cast<QMainWindow*>(obs_frontend_get_main_window());
QMetaObject::invokeMethod(mainWindow, "DeleteProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName)));
return RequestResult::Success();
@ -334,7 +334,7 @@ RequestResult RequestHandler::RemoveProfile(const Request& request)
* @responseField defaultParameterValue | String | Default value associated with the parameter. `null` if no default
*
* @requestType GetProfileParameter
* @complexity 3
* @complexity 4
* @rpcVersion -1
* @initialVersion 5.0.0
* @category config
@ -378,7 +378,7 @@ RequestResult RequestHandler::GetProfileParameter(const Request& request)
* @requestField parameterValue | String | Value of the parameter to set. Use `null` to delete
*
* @requestType SetProfileParameter
* @complexity 3
* @complexity 4
* @rpcVersion -1
* @initialVersion 5.0.0
* @category config
@ -408,6 +408,8 @@ RequestResult RequestHandler::SetProfileParameter(const Request& request)
return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "The field `parameterValue` must be a string.");
}
config_save(profile);
return RequestResult::Success();
}
@ -592,3 +594,23 @@ RequestResult RequestHandler::SetStreamServiceSettings(const Request& request)
return RequestResult::Success();
}
/**
* Gets the current directory that the record output is set to.
*
* @responseField recordDirectory | String | Output directory
*
* @requestType GetRecordDirectory
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category rconfig
*/
RequestResult RequestHandler::GetRecordDirectory(const Request&)
{
json responseData;
responseData["recordDirectory"] = Utils::Obs::StringHelper::GetCurrentRecordOutputPath();
return RequestResult::Success(responseData);
}

View File

@ -0,0 +1,334 @@
/*
obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@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 "RequestHandler.h"
/**
* Gets an array of all of a source's filters.
*
* @requestField sourceName | String | Name of the source
*
* @responseField filters | Array<Object> | Array of filters
*
* @requestType GetSourceFilterList
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category filters
*/
RequestResult RequestHandler::GetSourceFilterList(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment);
if(!source)
return RequestResult::Error(statusCode, comment);
json responseData;
responseData["filters"] = Utils::Obs::ArrayHelper::GetSourceFilterList(source);
return RequestResult::Success(responseData);
}
/**
* Gets the default settings for a filter kind.
*
* @requestField filterKind | String | Filter kind to get the default settings for
*
* @responseField defaultFilterSettings | Object | Object of default settings for the filter kind
*
* @requestType GetSourceFilterDefaultSettings
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category filters
*/
RequestResult RequestHandler::GetSourceFilterDefaultSettings(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
if (!request.ValidateString("filterKind", statusCode, comment))
return RequestResult::Error(statusCode, comment);
std::string filterKind = request.RequestData["filterKind"];
auto kinds = Utils::Obs::ArrayHelper::GetFilterKindList();
if (std::find(kinds.begin(), kinds.end(), filterKind) == kinds.end())
return RequestResult::Error(RequestStatus::InvalidFilterKind);
OBSDataAutoRelease defaultSettings = obs_get_source_defaults(filterKind.c_str());
if (!defaultSettings)
return RequestResult::Error(RequestStatus::InvalidFilterKind);
json responseData;
responseData["defaultFilterSettings"] = Utils::Json::ObsDataToJson(defaultSettings, true);
return RequestResult::Success(responseData);
}
/**
* Creates a new filter, adding it to the specified source.
*
* @requestField sourceName | String | Name of the source to add the filter to
* @requestField filterName | String | Name of the new filter to be created
* @requestField filterKind | String | The kind of filter to be created
* @requestField ?filterSettings | Object | Settings object to initialize the filter with | Default settings used
*
* @requestType CreateSourceFilter
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category filters
*/
RequestResult RequestHandler::CreateSourceFilter(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment);
if (!(source && request.ValidateString("filterName", statusCode, comment) && request.ValidateString("filterKind", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
std::string filterName = request.RequestData["filterName"];
OBSSourceAutoRelease existingFilter = obs_source_get_filter_by_name(source, filterName.c_str());
if (existingFilter)
return RequestResult::Error(RequestStatus::ResourceAlreadyExists, "A filter already exists by that name.");
std::string filterKind = request.RequestData["filterKind"];
auto kinds = Utils::Obs::ArrayHelper::GetFilterKindList();
if (std::find(kinds.begin(), kinds.end(), filterKind) == kinds.end())
return RequestResult::Error(RequestStatus::InvalidFilterKind, "Your specified filter kind is not supported by OBS. Check that any necessary plugins are loaded.");
OBSDataAutoRelease filterSettings = nullptr;
if (request.Contains("filterSettings")) {
if (!request.ValidateOptionalObject("filterSettings", statusCode, comment, true))
return RequestResult::Error(statusCode, comment);
filterSettings = Utils::Json::JsonToObsData(request.RequestData["filterSettings"]);
}
OBSSourceAutoRelease filter = Utils::Obs::ActionHelper::CreateSourceFilter(source, filterName, filterKind, filterSettings);
if(!filter)
return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Creation of the filter failed.");
return RequestResult::Success();
}
/**
* Removes a filter from a source.
*
* @requestField sourceName | String | Name of the source the filter is on
* @requestField filterName | String | Name of the filter to remove
*
* @requestType RemoveSourceFilter
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category filters
*/
RequestResult RequestHandler::RemoveSourceFilter(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
FilterPair pair = request.ValidateFilter("sourceName", "filterName", statusCode, comment);
if (!pair.filter)
return RequestResult::Error(statusCode, comment);
obs_source_filter_remove(pair.source, pair.filter);
return RequestResult::Success();
}
/**
* Sets the name of a source filter (rename).
*
* @requestField sourceName | String | Name of the source the filter is on
* @requestField filterName | String | Current name of the filter
* @requestField newFilterName | String | New name for the filter
*
* @requestType SetSourceFilterName
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category filters
*/
RequestResult RequestHandler::SetSourceFilterName(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
FilterPair pair = request.ValidateFilter("sourceName", "filterName", statusCode, comment);
if (!pair.filter || !request.ValidateString("newFilterName", statusCode, comment))
return RequestResult::Error(statusCode, comment);
std::string newFilterName = request.RequestData["newFilterName"];
OBSSourceAutoRelease existingFilter = obs_source_get_filter_by_name(pair.source, newFilterName.c_str());
if (existingFilter)
return RequestResult::Error(RequestStatus::ResourceAlreadyExists, "A filter already exists by that new name.");
obs_source_set_name(pair.filter, newFilterName.c_str());
return RequestResult::Success();
}
/**
* Gets the info for a specific source filter.
*
* @requestField sourceName | String | Name of the source
* @requestField filterName | String | Name of the filter
*
* @responseField filterEnabled | Boolean | Whether the filter is enabled
* @responseField filterIndex | Number | Index of the filter in the list, beginning at 0
* @responseField filterKind | String | The kind of filter
* @responseField filterSettings | Object | Settings object associated with the filter
*
* @requestType GetSourceFilter
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category filters
*/
RequestResult RequestHandler::GetSourceFilter(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
FilterPair pair = request.ValidateFilter("sourceName", "filterName", statusCode, comment);
if (!pair.filter)
return RequestResult::Error(statusCode, comment);
json responseData;
responseData["filterEnabled"] = obs_source_enabled(pair.filter);
responseData["filterIndex"] = Utils::Obs::NumberHelper::GetSourceFilterIndex(pair.source, pair.filter); // Todo: Use `GetSourceFilterlist` to select this filter maybe
responseData["filterKind"] = obs_source_get_id(pair.filter);
OBSDataAutoRelease filterSettings = obs_source_get_settings(pair.filter);
responseData["filterSettings"] = Utils::Json::ObsDataToJson(filterSettings);
return RequestResult::Success(responseData);
}
/**
* Sets the index position of a filter on a source.
*
* @requestField sourceName | String | Name of the source the filter is on
* @requestField filterName | String | Name of the filter
* @requestField filterIndex | Number | New index position of the filter | >= 0
*
* @requestType SetSourceFilterIndex
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category filters
*/
RequestResult RequestHandler::SetSourceFilterIndex(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
FilterPair pair = request.ValidateFilter("sourceName", "filterName", statusCode, comment);
if (!(pair.filter && request.ValidateNumber("filterIndex", statusCode, comment, 0, 8192)))
return RequestResult::Error(statusCode, comment);
int filterIndex = request.RequestData["filterIndex"];
Utils::Obs::ActionHelper::SetSourceFilterIndex(pair.source, pair.filter, filterIndex);
return RequestResult::Success();
}
/**
* Sets the settings of a source filter.
*
* @requestField sourceName | String | Name of the source the filter is on
* @requestField filterName | String | Name of the filter to set the settings of
* @requestField filterSettings | Object | Object of settings to apply
* @requestField ?overlay | Boolean | True == apply the settings on top of existing ones, False == reset the input to its defaults, then apply settings. | true
*
* @requestType SetSourceFilterSettings
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category filters
*/
RequestResult RequestHandler::SetSourceFilterSettings(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
FilterPair pair = request.ValidateFilter("sourceName", "filterName", statusCode, comment);
if (!(pair.filter && request.ValidateObject("filterSettings", statusCode, comment, true)))
return RequestResult::Error(statusCode, comment);
// Almost identical to SetInputSettings
bool overlay = true;
if (request.Contains("overlay")) {
if (!request.ValidateOptionalBoolean("overlay", statusCode, comment))
return RequestResult::Error(statusCode, comment);
overlay = request.RequestData["overlay"];
}
OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["filterSettings"]);
if (!newSettings)
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "An internal data conversion operation failed. Please report this!");
if (overlay)
obs_source_update(pair.filter, newSettings);
else
obs_source_reset_settings(pair.filter, newSettings);
obs_source_update_properties(pair.filter);
return RequestResult::Success();
}
/**
* Sets the enable state of a source filter.
*
* @requestField sourceName | String | Name of the source the filter is on
* @requestField filterName | Number | Name of the filter
* @requestField filterEnabled | Boolean | New enable state of the filter
*
* @requestType SetSourceFilterEnabled
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category filters
*/
RequestResult RequestHandler::SetSourceFilterEnabled(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
FilterPair pair = request.ValidateFilter("sourceName", "filterName", statusCode, comment);
if (!(pair.filter && request.ValidateBoolean("filterEnabled", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
bool filterEnabled = request.RequestData["filterEnabled"];
obs_source_set_enabled(pair.filter, filterEnabled);
return RequestResult::Success();
}

View File

@ -84,7 +84,7 @@ RequestResult RequestHandler::GetVersion(const Request&)
*/
RequestResult RequestHandler::GetStats(const Request&)
{
json responseData = Utils::Obs::DataHelper::GetStats();
json responseData = Utils::Obs::ObjectHelper::GetStats();
responseData["webSocketSessionIncomingMessages"] = _session->IncomingMessages();
responseData["webSocketSessionOutgoingMessages"] = _session->OutgoingMessages();
@ -195,7 +195,7 @@ RequestResult RequestHandler::CallVendorRequest(const Request& request)
RequestResult RequestHandler::GetHotkeyList(const Request&)
{
json responseData;
responseData["hotkeys"] = Utils::Obs::ListHelper::GetHotkeyNameList();
responseData["hotkeys"] = Utils::Obs::ArrayHelper::GetHotkeyNameList();
return RequestResult::Success(responseData);
}
@ -288,58 +288,6 @@ RequestResult RequestHandler::TriggerHotkeyByKeySequence(const Request& request)
return RequestResult::Success();
}
/**
* Gets whether studio is enabled.
*
* @responseField studioModeEnabled | Boolean | Whether studio mode is enabled
*
* @requestType GetStudioModeEnabled
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category general
* @api requests
*/
RequestResult RequestHandler::GetStudioModeEnabled(const Request&)
{
json responseData;
responseData["studioModeEnabled"] = obs_frontend_preview_program_mode_active();
return RequestResult::Success(responseData);
}
/**
* Enables or disables studio mode
*
* @requestField studioModeEnabled | Boolean | True == Enabled, False == Disabled
*
* @requestType SetStudioModeEnabled
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category general
* @api requests
*/
RequestResult RequestHandler::SetStudioModeEnabled(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
if (!request.ValidateBoolean("studioModeEnabled", statusCode, comment))
return RequestResult::Error(statusCode, comment);
// Avoid queueing tasks if nothing will change
if (obs_frontend_preview_program_mode_active() != request.RequestData["studioModeEnabled"]) {
// (Bad) Create a boolean then pass it as a reference to the task. Requires `wait` in obs_queue_task() to be true, else undefined behavior
bool studioModeEnabled = request.RequestData["studioModeEnabled"];
// Queue the task inside of the UI thread to prevent race conditions
obs_queue_task(OBS_TASK_UI, [](void* param) {
auto studioModeEnabled = (bool*)param;
obs_frontend_set_preview_program_mode(*studioModeEnabled);
}, &studioModeEnabled, true);
}
return RequestResult::Success();
}
/**
* Sleeps for a time duration or number of frames. Only available in request batches with types `SERIAL_REALTIME` or `SERIAL_FRAME`.
*

View File

@ -47,7 +47,7 @@ RequestResult RequestHandler::GetInputList(const Request& request)
}
json responseData;
responseData["inputs"] = Utils::Obs::ListHelper::GetInputList(inputKind);
responseData["inputs"] = Utils::Obs::ArrayHelper::GetInputList(inputKind);
return RequestResult::Success(responseData);
}
@ -79,7 +79,44 @@ RequestResult RequestHandler::GetInputKindList(const Request& request)
}
json responseData;
responseData["inputKinds"] = Utils::Obs::ListHelper::GetInputKindList(unversioned);
responseData["inputKinds"] = Utils::Obs::ArrayHelper::GetInputKindList(unversioned);
return RequestResult::Success(responseData);
}
/**
* Gets the names of all special inputs.
*
* @responseField desktop1 | String | Name of the Desktop Audio input
* @responseField desktop2 | String | Name of the Desktop Audio 2 input
* @responseField mic1 | String | Name of the Mic/Auxiliary Audio input
* @responseField mic2 | String | Name of the Mic/Auxiliary Audio 2 input
* @responseField mic3 | String | Name of the Mic/Auxiliary Audio 3 input
* @responseField mic4 | String | Name of the Mic/Auxiliary Audio 4 input
*
* @requestType GetSpecialInputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category inputs
*/
RequestResult RequestHandler::GetSpecialInputs(const Request&)
{
json responseData;
std::vector<std::string> channels = {"desktop1", "desktop2", "mic1", "mic2", "mic3", "mic4"};
size_t channelId = 1;
for (auto &channel : channels) {
OBSSourceAutoRelease input = obs_get_output_source(channelId);
if (!input)
responseData[channel] = nullptr;
else
responseData[channel] = obs_source_get_name(input);
channelId++;
}
return RequestResult::Success(responseData);
}
@ -115,7 +152,7 @@ RequestResult RequestHandler::CreateInput(const Request& request)
return RequestResult::Error(RequestStatus::ResourceAlreadyExists, "A source already exists by that input name.");
std::string inputKind = request.RequestData["inputKind"];
auto kinds = Utils::Obs::ListHelper::GetInputKindList();
auto kinds = Utils::Obs::ArrayHelper::GetInputKindList();
if (std::find(kinds.begin(), kinds.end(), inputKind) == kinds.end())
return RequestResult::Error(RequestStatus::InvalidInputKind, "Your specified input kind is not supported by OBS. Check that your specified kind is properly versioned and that any necessary plugins are loaded.");
@ -232,6 +269,9 @@ RequestResult RequestHandler::GetInputDefaultSettings(const Request& request)
return RequestResult::Error(statusCode, comment);
std::string inputKind = request.RequestData["inputKind"];
auto kinds = Utils::Obs::ArrayHelper::GetInputKindList();
if (std::find(kinds.begin(), kinds.end(), inputKind) == kinds.end())
return RequestResult::Error(RequestStatus::InvalidInputKind);
OBSDataAutoRelease defaultSettings = obs_get_source_defaults(inputKind.c_str());
if (!defaultSettings)
@ -278,8 +318,9 @@ RequestResult RequestHandler::GetInputSettings(const Request& request)
/**
* Sets the settings of an input.
*
* @requestField inputName | String | Name of the input to set the settings of
* @requestField inputSettings | Object | Object of settings to apply
* @requestField inputName | String | Name of the input to set the settings of
* @requestField inputSettings | Object | Object of settings to apply
* @requestField ?overlay | Boolean | True == apply the settings on top of existing ones, False == reset the input to its defaults, then apply settings. | true
*
* @requestType SetInputSettings
* @complexity 3
@ -345,6 +386,9 @@ RequestResult RequestHandler::GetInputMute(const Request& request)
if (!input)
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
json responseData;
responseData["inputMuted"] = obs_source_muted(input);
return RequestResult::Success(responseData);
@ -371,6 +415,9 @@ RequestResult RequestHandler::SetInputMute(const Request& request)
if (!(input && request.ValidateBoolean("inputMuted", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
obs_source_set_muted(input, request.RequestData["inputMuted"]);
return RequestResult::Success();
@ -398,6 +445,9 @@ RequestResult RequestHandler::ToggleInputMute(const Request& request)
if (!input)
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
bool inputMuted = !obs_source_muted(input);
obs_source_set_muted(input, inputMuted);
@ -429,6 +479,9 @@ RequestResult RequestHandler::GetInputVolume(const Request& request)
if (!input)
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
float inputVolumeMul = obs_source_get_volume(input);
float inputVolumeDb = obs_mul_to_db(inputVolumeMul);
if (inputVolumeDb == -INFINITY)
@ -445,7 +498,7 @@ RequestResult RequestHandler::GetInputVolume(const Request& request)
*
* @requestField inputName | String | Name of the input to set the volume of
* @requestField ?inputVolumeMul | Number | Volume setting in mul | >= 0, <= 20 | `inputVolumeDb` should be specified
* @requestField ?inputVolumeDb | Number | Volume setting in dB | >= -100, <= -26 | `inputVolumeMul` should be specified
* @requestField ?inputVolumeDb | Number | Volume setting in dB | >= -100, <= 26 | `inputVolumeMul` should be specified
*
* @requestType SetInputVolume
* @complexity 3
@ -462,6 +515,9 @@ RequestResult RequestHandler::SetInputVolume(const Request& request)
if (!input)
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
bool hasMul = request.Contains("inputVolumeMul");
if (hasMul && !request.ValidateOptionalNumber("inputVolumeMul", statusCode, comment, 0, 20))
return RequestResult::Error(statusCode, comment);
@ -487,6 +543,67 @@ RequestResult RequestHandler::SetInputVolume(const Request& request)
return RequestResult::Success();
}
/**
* Gets the audio balance of an input.
*
* @requestField inputName | String | Name of the input to get the audio balance of
*
* @responseField inputAudioBalance | Number | Audio balance value from 0.0-1.0
*
* @requestType GetInputAudioBalance
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category inputs
*/
RequestResult RequestHandler::GetInputAudioBalance(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!input)
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
json responseData;
responseData["inputAudioBalance"] = obs_source_get_balance_value(input);
return RequestResult::Success(responseData);
}
/**
* Sets the audio balance of an input.
*
* @requestField inputName | String | Name of the input to set the audio balance of
* @requestField inputAudioBalance | Number | New audio balance value | >= 0.0, <= 1.0
*
* @requestType SetInputAudioBalance
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category inputs
*/
RequestResult RequestHandler::SetInputAudioBalance(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!(input && request.ValidateNumber("inputAudioBalance", statusCode, comment, 0.0, 1.0)))
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
float inputAudioBalance = request.RequestData["inputAudioBalance"];
obs_source_set_balance_value(input, inputAudioBalance);
return RequestResult::Success();
}
/**
* Gets the audio sync offset of an input.
*
@ -511,6 +628,9 @@ RequestResult RequestHandler::GetInputAudioSyncOffset(const Request& request)
if (!input)
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
json responseData;
// Offset is stored in nanoseconds in OBS.
responseData["inputAudioSyncOffset"] = obs_source_get_sync_offset(input) / 1000000;
@ -539,6 +659,9 @@ RequestResult RequestHandler::SetInputAudioSyncOffset(const Request& request)
if (!(input && request.ValidateNumber("inputAudioSyncOffset", statusCode, comment, -950, 20000)))
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
int64_t syncOffset = request.RequestData["inputAudioSyncOffset"];
obs_source_set_sync_offset(input, syncOffset * 1000000);
@ -573,6 +696,9 @@ RequestResult RequestHandler::GetInputAudioMonitorType(const Request& request)
if (!input)
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
json responseData;
responseData["monitorType"] = Utils::Obs::StringHelper::GetInputMonitorType(input);
@ -600,6 +726,12 @@ RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request)
if (!(input && request.ValidateString("monitorType", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
if (!obs_audio_monitoring_available())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Audio monitoring is not available on this platform.");
enum obs_monitoring_type monitorType;
std::string monitorTypeString = request.RequestData["monitorType"];
if (monitorTypeString == "OBS_MONITORING_TYPE_NONE")
@ -616,30 +748,93 @@ RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request)
return RequestResult::Success();
}
std::vector<json> GetListPropertyItems(obs_property_t *property)
/**
* Gets the enable state of all audio tracks of an input.
*
* @requestField inputName | String | Name of the input
*
* @responseField inputAudioTracks | Object | Object of audio tracks and associated enable states
*
* @requestType GetInputAudioTracks
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category inputs
*/
RequestResult RequestHandler::GetInputAudioTracks(const Request& request)
{
std::vector<json> ret;
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!input)
return RequestResult::Error(statusCode, comment);
enum obs_combo_format itemFormat = obs_property_list_format(property);
size_t itemCount = obs_property_list_item_count(property);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
for (size_t i = 0; i < itemCount; i++) {
json itemData;
itemData["itemName"] = obs_property_list_item_name(property, i);
itemData["itemEnabled"] = !obs_property_list_item_disabled(property, i);
if (itemFormat == OBS_COMBO_FORMAT_INT) {
itemData["itemValue"] = obs_property_list_item_int(property, i);
} else if (itemFormat == OBS_COMBO_FORMAT_FLOAT) {
itemData["itemValue"] = obs_property_list_item_float(property, i);
} else if (itemFormat == OBS_COMBO_FORMAT_STRING) {
itemData["itemValue"] = obs_property_list_item_string(property, i);
} else {
itemData["itemValue"] = nullptr;
}
ret.push_back(itemData);
long long tracks = obs_source_get_audio_mixers(input);
json inputAudioTracks;
for (long long i = 0; i < MAX_AUDIO_MIXES; i++) {
inputAudioTracks[std::to_string(i + 1)] = (bool)((tracks >> i) & 1);
}
return ret;
json responseData;
responseData["inputAudioTracks"] = inputAudioTracks;
return RequestResult::Success(responseData);
}
/**
* Sets the enable state of audio tracks of an input.
*
* @requestField inputName | String | Name of the input
* @requestField inputAudioTracks | Object | Track settings to apply
*
* @requestType SetInputAudioTracks
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category inputs
*/
RequestResult RequestHandler::SetInputAudioTracks(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!input || !request.ValidateObject("inputAudioTracks", statusCode, comment))
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
json inputAudioTracks = request.RequestData["inputAudioTracks"];
long long mixers = obs_source_get_audio_mixers(input);
for (long long i = 0; i < MAX_AUDIO_MIXES; i++) {
std::string track = std::to_string(i + 1);
if (!Utils::Json::Contains(inputAudioTracks, track))
continue;
if (!inputAudioTracks[track].is_boolean())
return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "The value of one of your tracks is not a boolean.");
bool enabled = inputAudioTracks[track];
if (enabled)
mixers |= (1 << i);
else
mixers &= ~(1 << i);
}
// Decided that checking if tracks have actually changed is unnecessary
obs_source_set_audio_mixers(input, mixers);
return RequestResult::Success();
}
/**
@ -677,7 +872,7 @@ RequestResult RequestHandler::GetInputPropertiesListPropertyItems(const Request&
return RequestResult::Error(RequestStatus::InvalidResourceType, "The property found is not a list.");
json responseData;
responseData["propertyItems"] = GetListPropertyItems(property);
responseData["propertyItems"] = Utils::Obs::ArrayHelper::GetListPropertyItems(property);
return RequestResult::Success(responseData);
}

View File

@ -25,6 +25,32 @@ bool IsMediaTimeValid(obs_source_t *input)
return mediaState == OBS_MEDIA_STATE_PLAYING || mediaState == OBS_MEDIA_STATE_PAUSED;
}
/**
* Gets the status of a media input.
*
* Media States:
* - `OBS_MEDIA_STATE_NONE`
* - `OBS_MEDIA_STATE_PLAYING`
* - `OBS_MEDIA_STATE_OPENING`
* - `OBS_MEDIA_STATE_BUFFERING`
* - `OBS_MEDIA_STATE_PAUSED`
* - `OBS_MEDIA_STATE_STOPPED`
* - `OBS_MEDIA_STATE_ENDED`
* - `OBS_MEDIA_STATE_ERROR`
*
* @requestField inputName | String | Name of the media input
*
* @responseField mediaState | String | State of the media input
* @responseField mediaDuration | Number | Total duration of the playing media in milliseconds. `null` if not playing
* @responseField mediaCursor | Number | Position of the cursor in milliseconds. `null` if not playing
*
* @requestType GetMediaInputStatus
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category media inputs
*/
RequestResult RequestHandler::GetMediaInputStatus(const Request& request)
{
RequestStatus::RequestStatus statusCode;
@ -47,6 +73,21 @@ RequestResult RequestHandler::GetMediaInputStatus(const Request& request)
return RequestResult::Success(responseData);
}
/**
* Sets the cursor position of a media input.
*
* This request does not perform bounds checking of the cursor position.
*
* @requestField inputName | String | Name of the media input
* @requestField mediaCursor | Number | New cursor position to set | >= 0
*
* @requestType SetMediaInputCursor
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category media inputs
*/
RequestResult RequestHandler::SetMediaInputCursor(const Request& request)
{
RequestStatus::RequestStatus statusCode;
@ -66,6 +107,21 @@ RequestResult RequestHandler::SetMediaInputCursor(const Request& request)
return RequestResult::Success();
}
/**
* Offsets the current cursor position of a media input by the specified value.
*
* This request does not perform bounds checking of the cursor position.
*
* @requestField inputName | String | Name of the media input
* @requestField mediaCursorOffset | Number | Value to offset the current cursor position by | None
*
* @requestType OffsetMediaInputCursor
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category media inputs
*/
RequestResult RequestHandler::OffsetMediaInputCursor(const Request& request)
{
RequestStatus::RequestStatus statusCode;
@ -88,6 +144,19 @@ RequestResult RequestHandler::OffsetMediaInputCursor(const Request& request)
return RequestResult::Success();
}
/**
* Triggers an action on a media input.
*
* @requestField inputName | String | Name of the media input
* @requestField mediaAction | String | Identifier of the `ObsMediaInputAction` enum
*
* @requestType TriggerMediaInputAction
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category media inputs
*/
RequestResult RequestHandler::TriggerMediaInputAction(const Request& request)
{
RequestStatus::RequestStatus statusCode;

View File

@ -0,0 +1,277 @@
/*
obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@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 "RequestHandler.h"
static bool VirtualCamAvailable()
{
OBSDataAutoRelease privateData = obs_get_private_data();
if (!privateData)
return false;
return obs_data_get_bool(privateData, "vcamEnabled");
}
static bool ReplayBufferAvailable()
{
OBSOutputAutoRelease output = obs_frontend_get_replay_buffer_output();
return output != nullptr;
}
/**
* Gets the status of the virtualcam output.
*
* @responseField outputActive | Boolean | Whether the output is active
*
* @requestType GetVirtualCamStatus
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category outputs
* @api requests
*/
RequestResult RequestHandler::GetVirtualCamStatus(const Request&)
{
if (!VirtualCamAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available.");
json responseData;
responseData["outputActive"] = obs_frontend_virtualcam_active();
return RequestResult::Success(responseData);
}
/**
* Toggles the state of the virtualcam output.
*
* @responseField outputActive | Boolean | Whether the output is active
*
* @requestType ToggleVirtualCam
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category outputs
* @api requests
*/
RequestResult RequestHandler::ToggleVirtualCam(const Request&)
{
if (!VirtualCamAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available.");
bool outputActive = obs_frontend_virtualcam_active();
if (outputActive)
obs_frontend_stop_virtualcam();
else
obs_frontend_start_virtualcam();
json responseData;
responseData["outputActive"] = !outputActive;
return RequestResult::Success(responseData);
}
/**
* Starts the virtualcam output.
*
* @requestType StartVirtualCam
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category outputs
*/
RequestResult RequestHandler::StartVirtualCam(const Request&)
{
if (!VirtualCamAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available.");
if (obs_frontend_virtualcam_active())
return RequestResult::Error(RequestStatus::OutputRunning);
obs_frontend_start_virtualcam();
return RequestResult::Success();
}
/**
* Stops the virtualcam output.
*
* @requestType StopVirtualCam
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category outputs
*/
RequestResult RequestHandler::StopVirtualCam(const Request&)
{
if (!VirtualCamAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available.");
if (!obs_frontend_virtualcam_active())
return RequestResult::Error(RequestStatus::OutputNotRunning);
obs_frontend_stop_virtualcam();
return RequestResult::Success();
}
/**
* Gets the status of the replay buffer output.
*
* @responseField outputActive | Boolean | Whether the output is active
*
* @requestType GetReplayBufferStatus
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category outputs
* @api requests
*/
RequestResult RequestHandler::GetReplayBufferStatus(const Request&)
{
if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
json responseData;
responseData["outputActive"] = obs_frontend_replay_buffer_active();
return RequestResult::Success(responseData);
}
/**
* Toggles the state of the replay buffer output.
*
* @responseField outputActive | Boolean | Whether the output is active
*
* @requestType ToggleReplayBuffer
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category outputs
* @api requests
*/
RequestResult RequestHandler::ToggleReplayBuffer(const Request&)
{
if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
bool outputActive = obs_frontend_replay_buffer_active();
if (outputActive)
obs_frontend_replay_buffer_stop();
else
obs_frontend_replay_buffer_start();
json responseData;
responseData["outputActive"] = !outputActive;
return RequestResult::Success(responseData);
}
/**
* Starts the replay buffer output.
*
* @requestType StartReplayBuffer
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category outputs
*/
RequestResult RequestHandler::StartReplayBuffer(const Request&)
{
if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
if (obs_frontend_replay_buffer_active())
return RequestResult::Error(RequestStatus::OutputRunning);
obs_frontend_replay_buffer_start();
return RequestResult::Success();
}
/**
* Stops the replay buffer output.
*
* @requestType StopReplayBuffer
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category outputs
*/
RequestResult RequestHandler::StopReplayBuffer(const Request&)
{
if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
if (!obs_frontend_replay_buffer_active())
return RequestResult::Error(RequestStatus::OutputNotRunning);
obs_frontend_replay_buffer_stop();
return RequestResult::Success();
}
/**
* Saves the contents of the replay buffer output.
*
* @requestType SaveReplayBuffer
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category outputs
*/
RequestResult RequestHandler::SaveReplayBuffer(const Request&)
{
if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
if (!obs_frontend_replay_buffer_active())
return RequestResult::Error(RequestStatus::OutputNotRunning);
obs_frontend_replay_buffer_save();
return RequestResult::Success();
}
/**
* Gets the filename of the last replay buffer save file.
*
* @responseField savedReplayPath | String | File path
*
* @requestType GetLastReplayBufferReplay
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category outputs
*/
RequestResult RequestHandler::GetLastReplayBufferReplay(const Request&)
{
if (!ReplayBufferAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Replay buffer is not available.");
if (!obs_frontend_replay_buffer_active())
return RequestResult::Error(RequestStatus::OutputNotRunning);
json responseData;
responseData["savedReplayPath"] = Utils::Obs::StringHelper::GetLastReplayBufferFilePath();
return RequestResult::Success(responseData);
}

View File

@ -19,9 +19,25 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "RequestHandler.h"
/**
* Gets the status of the record output.
*
* @responseField outputActive | Boolean | Whether the output is active
* @responseField ouputPaused | Boolean | Whether the output is paused
* @responseField outputTimecode | String | Current formatted timecode string for the output
* @responseField outputDuration | Number | Current duration in milliseconds for the output
* @responseField outputBytes | Number | Number of bytes sent by the output
*
* @requestType GetRecordStatus
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category record
*/
RequestResult RequestHandler::GetRecordStatus(const Request&)
{
OBSOutputAutoRelease recordOutput = obs_frontend_get_streaming_output();
OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output();
uint64_t outputDuration = Utils::Obs::NumberHelper::GetOutputDuration(recordOutput);
@ -35,6 +51,16 @@ RequestResult RequestHandler::GetRecordStatus(const Request&)
return RequestResult::Success(responseData);
}
/**
* Toggles the status of the record output.
*
* @requestType ToggleRecord
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category record
*/
RequestResult RequestHandler::ToggleRecord(const Request&)
{
json responseData;
@ -49,6 +75,16 @@ RequestResult RequestHandler::ToggleRecord(const Request&)
return RequestResult::Success(responseData);
}
/**
* Starts the record output.
*
* @requestType StartRecord
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category record
*/
RequestResult RequestHandler::StartRecord(const Request&)
{
if (obs_frontend_recording_active())
@ -60,6 +96,16 @@ RequestResult RequestHandler::StartRecord(const Request&)
return RequestResult::Success();
}
/**
* Stops the record output.
*
* @requestType StopRecord
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category record
*/
RequestResult RequestHandler::StopRecord(const Request&)
{
if (!obs_frontend_recording_active())
@ -71,6 +117,16 @@ RequestResult RequestHandler::StopRecord(const Request&)
return RequestResult::Success();
}
/**
* Toggles pause on the record output.
*
* @requestType ToggleRecordPause
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category record
*/
RequestResult RequestHandler::ToggleRecordPause(const Request&)
{
json responseData;
@ -85,6 +141,16 @@ RequestResult RequestHandler::ToggleRecordPause(const Request&)
return RequestResult::Success(responseData);
}
/**
* Pauses the record output.
*
* @requestType PauseRecord
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category record
*/
RequestResult RequestHandler::PauseRecord(const Request&)
{
if (obs_frontend_recording_paused())
@ -96,6 +162,16 @@ RequestResult RequestHandler::PauseRecord(const Request&)
return RequestResult::Success();
}
/**
* Resumes the record output.
*
* @requestType ResumeRecord
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category record
*/
RequestResult RequestHandler::ResumeRecord(const Request&)
{
if (!obs_frontend_recording_paused())
@ -106,11 +182,3 @@ RequestResult RequestHandler::ResumeRecord(const Request&)
return RequestResult::Success();
}
RequestResult RequestHandler::GetRecordDirectory(const Request&)
{
json responseData;
responseData["recordDirectory"] = Utils::Obs::StringHelper::GetCurrentRecordOutputPath();
return RequestResult::Success(responseData);
}

View File

@ -19,6 +19,22 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "RequestHandler.h"
/**
* Gets a list of all scene items in a scene.
*
* Scenes only
*
* @requestField sceneName | String | Name of the scene to get the items of
*
* @responseField sceneItems | Array<Object> | Array of scene items in the scene
*
* @requestType GetSceneItemList
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::GetSceneItemList(const Request& request)
{
RequestStatus::RequestStatus statusCode;
@ -28,11 +44,29 @@ RequestResult RequestHandler::GetSceneItemList(const Request& request)
return RequestResult::Error(statusCode, comment);
json responseData;
responseData["sceneItems"] = Utils::Obs::ListHelper::GetSceneItemList(obs_scene_from_source(scene));
responseData["sceneItems"] = Utils::Obs::ArrayHelper::GetSceneItemList(obs_scene_from_source(scene));
return RequestResult::Success(responseData);
}
/**
* Basically GetSceneItemList, but for groups.
*
* Using groups at all in OBS is discouraged, as they are very broken under the hood.
*
* Groups only
*
* @requestField sceneName | String | Name of the group to get the items of
*
* @responseField sceneItems | Array<Object> | Array of scene items in the group
*
* @requestType GetGroupItemList
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::GetGroupSceneItemList(const Request& request)
{
RequestStatus::RequestStatus statusCode;
@ -42,26 +76,36 @@ RequestResult RequestHandler::GetGroupSceneItemList(const Request& request)
return RequestResult::Error(statusCode, comment);
json responseData;
responseData["sceneItems"] = Utils::Obs::ListHelper::GetSceneItemList(obs_group_from_source(scene));
responseData["sceneItems"] = Utils::Obs::ArrayHelper::GetSceneItemList(obs_group_from_source(scene));
return RequestResult::Success(responseData);
}
/**
* Searches a scene for a source, and returns its id.
*
* Scenes and Groups
*
* @requestField sceneName | String | Name of the scene or group to search in
* @requestField sourceName | String | Name of the source to find
*
* @responseField sceneItemId | Number | Numeric ID of the scene item
*
* @requestType GetSceneItemId
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::GetSceneItemId(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease sceneSource = request.ValidateScene("sceneName", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!(sceneSource && request.ValidateString("sourceName", statusCode, comment)))
OBSSceneAutoRelease scene = request.ValidateScene2("sceneName", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!(scene && request.ValidateString("sourceName", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
OBSScene scene = obs_scene_from_source(sceneSource);
if (!scene) {
scene = obs_group_from_source(sceneSource);
if (!scene) // This should never happen
return RequestResult::Error(RequestStatus::GenericError, "Somehow the scene was found but the scene object could not be fetched. Please report this to the obs-websocket developers.");
}
std::string sourceName = request.RequestData["sourceName"];
OBSSceneItemAutoRelease item = Utils::Obs::SearchHelper::GetSceneItemByName(scene, sourceName);
@ -74,6 +118,24 @@ RequestResult RequestHandler::GetSceneItemId(const Request& request)
return RequestResult::Success(responseData);
}
/**
* Creates a new scene item using a source.
*
* Scenes only
*
* @requestField sceneName | String | Name of the scene to create the new item in
* @requestField sourceName | String | Name of the source to add to the scene
* @requestField ?sceneItemEnabled | Boolean | Enable state to apply to the scene item on creation | True
*
* @responseField sceneItemId | Number | Numeric ID of the scene item
*
* @requestType CreateSceneItem
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::CreateSceneItem(const Request& request)
{
RequestStatus::RequestStatus statusCode;
@ -108,6 +170,21 @@ RequestResult RequestHandler::CreateSceneItem(const Request& request)
return RequestResult::Success(responseData);
}
/**
* Removes a scene item from a scene.
*
* Scenes only
*
* @requestField sceneName | String | Name of the scene the item is in
* @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0
*
* @requestType RemoveSceneItem
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::RemoveSceneItem(const Request& request)
{
RequestStatus::RequestStatus statusCode;
@ -116,11 +193,30 @@ RequestResult RequestHandler::RemoveSceneItem(const Request& request)
if (!sceneItem)
return RequestResult::Error(statusCode, comment);
// Makes the UI log `User Removed source '[source]' from scene '(null)'`. This is not a problem, just a side effect.
obs_sceneitem_remove(sceneItem);
return RequestResult::Success();
}
/**
* Duplicates a scene item, copying all transform and crop info.
*
* Scenes only
*
* @requestField sceneName | String | Name of the scene the item is in
* @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0
* @requestField ?destinationSceneName | String | Name of the scene to create the duplicated item in | `sceneName` is assumed
*
* @responseField sceneItemId | Number | Numeric ID of the duplicated scene item
*
* @requestType DuplicateSceneItem
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::DuplicateSceneItem(const Request& request)
{
RequestStatus::RequestStatus statusCode;
@ -136,8 +232,9 @@ RequestResult RequestHandler::DuplicateSceneItem(const Request& request)
if (!destinationScene)
return RequestResult::Error(statusCode, comment);
} else {
destinationScene = obs_sceneitem_get_scene(sceneItem);
obs_scene_addref(destinationScene);
destinationScene = obs_scene_get_ref(obs_sceneitem_get_scene(sceneItem));
if (!destinationScene)
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Internal error: Failed to get ref for scene of scene item.");
}
if (obs_sceneitem_is_group(sceneItem) && obs_sceneitem_get_scene(sceneItem) == destinationScene) {
@ -165,6 +262,23 @@ RequestResult RequestHandler::DuplicateSceneItem(const Request& request)
return RequestResult::Success(responseData);
}
/**
* Gets the transform and crop info of a scene item.
*
* Scenes and Groups
*
* @requestField sceneName | String | Name of the scene the item is in
* @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0
*
* @responseField sceneItemTransform | Object | Object containing scene item transform info
*
* @requestType GetSceneItemTransform
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::GetSceneItemTransform(const Request& request)
{
RequestStatus::RequestStatus statusCode;
@ -174,11 +288,25 @@ RequestResult RequestHandler::GetSceneItemTransform(const Request& request)
return RequestResult::Error(statusCode, comment);
json responseData;
responseData["sceneItemTransform"] = Utils::Obs::DataHelper::GetSceneItemTransform(sceneItem);
responseData["sceneItemTransform"] = Utils::Obs::ObjectHelper::GetSceneItemTransform(sceneItem);
return RequestResult::Success(responseData);
}
/**
* Sets the transform and crop info of a scene item.
*
* @requestField sceneName | String | Name of the scene the item is in
* @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0
* @requestField sceneItemTransform | Object | Object containing scene item transform info to update
*
* @requestType SetSceneItemTransform
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::SetSceneItemTransform(const Request& request)
{
RequestStatus::RequestStatus statusCode;
@ -317,6 +445,23 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request& request)
return RequestResult::Success();
}
/**
* Gets the enable state of a scene item.
*
* Scenes and Groups
*
* @requestField sceneName | String | Name of the scene the item is in
* @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0
*
* @responseField sceneItemEnabled | Boolean | Whether the scene item is enabled. `true` for enabled, `false` for disabled
*
* @requestType GetSceneItemEnabled
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::GetSceneItemEnabled(const Request& request)
{
RequestStatus::RequestStatus statusCode;
@ -331,6 +476,22 @@ RequestResult RequestHandler::GetSceneItemEnabled(const Request& request)
return RequestResult::Success(responseData);
}
/**
* Sets the enable state of a scene item.
*
* Scenes and Groups
*
* @requestField sceneName | String | Name of the scene the item is in
* @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0
* @requestField sceneItemEnabled | Boolean | New enable state of the scene item
*
* @requestType SetSceneItemEnabled
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::SetSceneItemEnabled(const Request& request)
{
RequestStatus::RequestStatus statusCode;
@ -346,6 +507,23 @@ RequestResult RequestHandler::SetSceneItemEnabled(const Request& request)
return RequestResult::Success();
}
/**
* Gets the lock state of a scene item.
*
* Scenes and Groups
*
* @requestField sceneName | String | Name of the scene the item is in
* @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0
*
* @responseField sceneItemLocked | Boolean | Whether the scene item is locked. `true` for locked, `false` for unlocked
*
* @requestType GetSceneItemLocked
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::GetSceneItemLocked(const Request& request)
{
RequestStatus::RequestStatus statusCode;
@ -360,6 +538,22 @@ RequestResult RequestHandler::GetSceneItemLocked(const Request& request)
return RequestResult::Success(responseData);
}
/**
* Sets the lock state of a scene item.
*
* Scenes and Group
*
* @requestField sceneName | String | Name of the scene the item is in
* @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0
* @requestField sceneItemLocked | Boolean | New lock state of the scene item
*
* @requestType SetSceneItemLocked
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::SetSceneItemLocked(const Request& request)
{
RequestStatus::RequestStatus statusCode;
@ -375,6 +569,25 @@ RequestResult RequestHandler::SetSceneItemLocked(const Request& request)
return RequestResult::Success();
}
/**
* Gets the index position of a scene item in a scene.
*
* An index of 0 is at the bottom of the source list in the UI.
*
* Scenes and Groups
*
* @requestField sceneName | String | Name of the scene the item is in
* @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0
*
* @responseField sceneItemIndex | Number | Index position of the scene item
*
* @requestType GetSceneItemIndex
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::GetSceneItemIndex(const Request& request)
{
RequestStatus::RequestStatus statusCode;
@ -389,6 +602,22 @@ RequestResult RequestHandler::GetSceneItemIndex(const Request& request)
return RequestResult::Success(responseData);
}
/**
* Sets the index position of a scene item in a scene.
*
* Scenes and Groups
*
* @requestField sceneName | String | Name of the scene the item is in
* @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0
* @requestField sceneItemIndex | Number | New index position of the scene item | >= 0
*
* @requestType SetSceneItemIndex
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::SetSceneItemIndex(const Request& request)
{
RequestStatus::RequestStatus statusCode;
@ -403,3 +632,81 @@ RequestResult RequestHandler::SetSceneItemIndex(const Request& request)
return RequestResult::Success();
}
/**
* Gets the blend mode of a scene item.
*
* Blend modes:
*
* - `OBS_BLEND_NORMAL`
* - `OBS_BLEND_ADDITIVE`
* - `OBS_BLEND_SUBTRACT`
* - `OBS_BLEND_SCREEN`
* - `OBS_BLEND_MULTIPLY`
* - `OBS_BLEND_LIGHTEN`
* - `OBS_BLEND_DARKEN`
*
* Scenes and Groups
*
* @requestField sceneName | String | Name of the scene the item is in
* @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0
*
* @responseField sceneItemBlendMode | String | Current blend mode
*
* @requestType GetSceneItemBlendMode
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::GetSceneItemBlendMode(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!sceneItem)
return RequestResult::Error(statusCode, comment);
auto blendMode = obs_sceneitem_get_blending_mode(sceneItem);
json responseData;
responseData["sceneItemBlendMode"] = Utils::Obs::StringHelper::GetSceneItemBlendMode(blendMode);
return RequestResult::Success(responseData);
}
/**
* Sets the blend mode of a scene item.
*
* Scenes and Groups
*
* @requestField sceneName | String | Name of the scene the item is in
* @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0
* @requestField sceneItemBlendMode | String | New blend mode
*
* @requestType SetSceneItemBlendMode
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::SetSceneItemBlendMode(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!(sceneItem && request.ValidateString("sceneItemBlendMode", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
std::string blendModeString = request.RequestData["sceneItemBlendMode"];
auto blendMode = Utils::Obs::EnumHelper::GetSceneItemBlendMode(blendModeString);
if (blendMode == OBS_BLEND_NORMAL && blendModeString != "OBS_BLEND_NORMAL")
return RequestResult::Error(RequestStatus::InvalidRequestField, "The field sceneItemBlendMode has an invalid value.");
obs_sceneitem_set_blending_mode(sceneItem, blendMode);
return RequestResult::Success();
}

View File

@ -22,9 +22,9 @@ with this program. If not, see <https://www.gnu.org/licenses/>
/**
* Gets an array of all scenes in OBS.
*
* @responseField scenes | Array<String> | Array of scenes in OBS
* @responseField currentProgramSceneName | String | Current program scene
* @responseField currentPreviewSceneName | String | Current preview scene. `null` if not in studio mode
* @responseField scenes | Array<Object> | Array of scenes
*
* @requestType GetSceneList
* @complexity 2
@ -49,7 +49,30 @@ RequestResult RequestHandler::GetSceneList(const Request&)
else
responseData["currentPreviewSceneName"] = nullptr;
responseData["scenes"] = Utils::Obs::ListHelper::GetSceneList();
responseData["scenes"] = Utils::Obs::ArrayHelper::GetSceneList();
return RequestResult::Success(responseData);
}
/**
* Gets an array of all groups in OBS.
*
* Groups in OBS are actually scenes, but renamed and modified. In obs-websocket, we treat them as scenes where we can.
*
* @responseField groups | Array<String> | Array of group names
*
* @requestType GetGroupList
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scenes
*/
RequestResult RequestHandler::GetGroupList(const Request&)
{
json responseData;
responseData["groups"] = Utils::Obs::ArrayHelper::GetGroupList();
return RequestResult::Success(responseData);
}
@ -250,3 +273,105 @@ RequestResult RequestHandler::SetSceneName(const Request& request)
return RequestResult::Success();
}
/**
* Gets the scene transition overridden for a scene.
*
* @requestField sceneName | String | Name of the scene
*
* @responseField transitionName | String | Name of the overridden scene transition, else `null`
* @responseField transitionDuration | Number | Duration of the overridden scene transition, else `null`
*
* @requestType GetSceneSceneTransitionOverride
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scenes
*/
RequestResult RequestHandler::GetSceneSceneTransitionOverride(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease scene = request.ValidateScene("sceneName", statusCode, comment);
if (!scene)
return RequestResult::Error(statusCode, comment);
OBSDataAutoRelease privateSettings = obs_source_get_private_settings(scene);
json responseData;
const char *transitionName = obs_data_get_string(privateSettings, "transition");
if (transitionName && strlen(transitionName))
responseData["transitionName"] = transitionName;
else
responseData["transitionName"] = nullptr;
if (obs_data_has_user_value(privateSettings, "transition_duration"))
responseData["transitionDuration"] = obs_data_get_int(privateSettings, "transition_duration");
else
responseData["transitionDuration"] = nullptr;
return RequestResult::Success(responseData);
}
/**
* Gets the scene transition overridden for a scene.
*
* @requestField sceneName | String | Name of the scene
* @requestField ?transitionName | String | Name of the scene transition to use as override. Specify `null` to remove | Unchanged
* @requestField ?transitionDuration | Number | Duration to use for any overridden transition. Specify `null` to remove | >= 50, <= 20000 | Unchanged
*
* @requestType SetSceneSceneTransitionOverride
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scenes
*/
RequestResult RequestHandler::SetSceneSceneTransitionOverride(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease scene = request.ValidateScene("sceneName", statusCode, comment);
if (!scene)
return RequestResult::Error(statusCode, comment);
OBSDataAutoRelease privateSettings = obs_source_get_private_settings(scene);
bool hasName = request.RequestData.contains("transitionName");
if (hasName && !request.RequestData["transitionName"].is_null()) {
if (!request.ValidateOptionalString("transitionName", statusCode, comment))
return RequestResult::Error(statusCode, comment);
OBSSourceAutoRelease transition = Utils::Obs::SearchHelper::GetSceneTransitionByName(request.RequestData["transitionName"]);
if (!transition)
return RequestResult::Error(RequestStatus::ResourceNotFound, "No scene transition was found by that name.");
}
bool hasDuration = request.RequestData.contains("transitionDuration");
if (hasDuration && !request.RequestData["transitionDuration"].is_null()) {
if (!request.ValidateOptionalNumber("transitionDuration", statusCode, comment, 50, 20000))
return RequestResult::Error(statusCode, comment);
}
if (!hasName && !hasDuration)
return RequestResult::Error(RequestStatus::MissingRequestField, "Your request data must include either `transitionName` or `transitionDuration`.");
if (hasName) {
if (request.RequestData["transitionName"].is_null()) {
obs_data_erase(privateSettings, "transition");
} else {
std::string transitionName = request.RequestData["transitionName"];
obs_data_set_string(privateSettings, "transition", transitionName.c_str());
}
}
if (hasDuration) {
if (request.RequestData["transitionDuration"].is_null()) {
obs_data_erase(privateSettings, "transition_duration");
} else {
obs_data_set_int(privateSettings, "transition_duration", request.RequestData["transitionDuration"]);
}
}
return RequestResult::Success();
}

View File

@ -312,3 +312,39 @@ RequestResult RequestHandler::SaveSourceScreenshot(const Request& request)
return RequestResult::Success();
}
// Intentionally undocumented
RequestResult RequestHandler::GetSourcePrivateSettings(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment);
if (!source)
return RequestResult::Error(statusCode, comment);
OBSDataAutoRelease privateSettings = obs_source_get_private_settings(source);
json responseData;
responseData["sourceSettings"] = Utils::Json::ObsDataToJson(privateSettings);
return RequestResult::Success(responseData);
}
// Intentionally undocumented
RequestResult RequestHandler::SetSourcePrivateSettings(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment);
if (!source || !request.ValidateObject("sourceSettings", statusCode, comment))
return RequestResult::Error(statusCode, comment);
OBSDataAutoRelease privateSettings = obs_source_get_private_settings(source);
OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["sourceSettings"]);
// Always overlays to prevent destroying internal source data unintentionally
obs_data_apply(privateSettings, newSettings);
return RequestResult::Success();
}

View File

@ -19,6 +19,24 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "RequestHandler.h"
/**
* Gets the status of the stream output.
*
* @responseField outputActive | Boolean | Whether the output is active
* @responseField outputReconnecting | Boolean | Whether the output is currently reconnecting
* @responseField outputTimecode | String | Current formatted timecode string for the output
* @responseField outputDuration | Number | Current duration in milliseconds for the output
* @responseField outputBytes | Number | Number of bytes sent by the output
* @responseField outputSkippedFrames | Number | Number of frames skipped by the output's process
* @responseField outputTotalFrames | Number | Total number of frames delivered by the output's process
*
* @requestType GetStreamStatus
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category stream
*/
RequestResult RequestHandler::GetStreamStatus(const Request&)
{
OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output();
@ -37,6 +55,18 @@ RequestResult RequestHandler::GetStreamStatus(const Request&)
return RequestResult::Success(responseData);
}
/**
* Toggles the status of the stream output.
*
* @responseField outputActive | Boolean | New state of the stream output
*
* @requestType ToggleStream
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category stream
*/
RequestResult RequestHandler::ToggleStream(const Request&)
{
json responseData;
@ -51,6 +81,16 @@ RequestResult RequestHandler::ToggleStream(const Request&)
return RequestResult::Success(responseData);
}
/**
* Starts the stream output.
*
* @requestType StartStream
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category stream
*/
RequestResult RequestHandler::StartStream(const Request&)
{
if (obs_frontend_streaming_active())
@ -62,6 +102,16 @@ RequestResult RequestHandler::StartStream(const Request&)
return RequestResult::Success();
}
/**
* Stops the stream output.
*
* @requestType StopStream
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category stream
*/
RequestResult RequestHandler::StopStream(const Request&)
{
if (!obs_frontend_streaming_active())
@ -72,3 +122,35 @@ RequestResult RequestHandler::StopStream(const Request&)
return RequestResult::Success();
}
/**
* Sends CEA-608 caption text over the stream output.
*
* @requestField captionText | String | Caption text
*
* @requestType SendStreamCaption
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @category stream
* @api requests
*/
RequestResult RequestHandler::SendStreamCaption(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
if (!request.ValidateString("captionText", statusCode, comment, true))
return RequestResult::Error(statusCode, comment);
if (!obs_frontend_streaming_active())
return RequestResult::Error(RequestStatus::OutputNotRunning);
std::string captionText = request.RequestData["captionText"];
OBSOutputAutoRelease output = obs_frontend_get_streaming_output();
// 0.0 means no delay until the next caption can be sent
obs_output_output_caption_text2(output, captionText.c_str(), 0.0);
return RequestResult::Success();
}

View File

@ -0,0 +1,322 @@
/*
obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@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 <math.h>
#include "RequestHandler.h"
/**
* Gets an array of all available transition kinds.
*
* Similar to `GetInputKindList`
*
* @responseField transitionKinds | Array<String> | Array of transition kinds
*
* @requestType GetTransitionKindList
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category transitions
*/
RequestResult RequestHandler::GetTransitionKindList(const Request&)
{
json responseData;
responseData["transitionKinds"] = Utils::Obs::ArrayHelper::GetTransitionKindList();
return RequestResult::Success(responseData);
}
/**
* Gets an array of all scene transitions in OBS.
*
* @responseField currentSceneTransitionName | String | Name of the current scene transition. Can be null
* @responseField currentSceneTransitionKind | String | Kind of the current scene transition. Can be null
* @responseField transitions | Array<Object> | Array of transitions
*
* @requestType GetSceneTransitionList
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category transitions
*/
RequestResult RequestHandler::GetSceneTransitionList(const Request&)
{
json responseData;
OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
if (transition) {
responseData["currentSceneTransitionName"] = obs_source_get_name(transition);
responseData["currentSceneTransitionKind"] = obs_source_get_id(transition);
} else {
responseData["currentSceneTransitionName"] = nullptr;
responseData["currentSceneTransitionKind"] = nullptr;
}
responseData["transitions"] = Utils::Obs::ArrayHelper::GetSceneTransitionList();
return RequestResult::Success(responseData);
}
/**
* Gets information about the current scene transition.
*
* @responseField transitionName | String | Name of the transition
* @responseField transitionKind | String | Kind of the transition
* @responseField transitionFixed | Boolean | Whether the transition uses a fixed (unconfigurable) duration
* @responseField transitionDuration | Number | Configured transition duration in milliseconds. `null` if transition is fixed
* @responseField transitionConfigurable | Boolean | Whether the transition supports being configured
* @responseField transitionSettings | Object | Object of settings for the transition. `null` if transition is not configurable
*
* @requestType GetCurrentSceneTransition
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category transitions
*/
RequestResult RequestHandler::GetCurrentSceneTransition(const Request&)
{
OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
if (!transition)
return RequestResult::Error(RequestStatus::InvalidResourceState, "OBS does not currently have a scene transition set."); // This should not happen!
json responseData;
responseData["transitionName"] = obs_source_get_name(transition);
responseData["transitionKind"] = obs_source_get_id(transition);
if (obs_transition_fixed(transition)) {
responseData["transitionFixed"] = true;
responseData["transitionDuration"] = nullptr;
} else {
responseData["transitionFixed"] = false;
responseData["transitionDuration"] = obs_frontend_get_transition_duration();
}
if (obs_source_configurable(transition)) {
responseData["transitionConfigurable"] = true;
OBSDataAutoRelease transitionSettings = obs_source_get_settings(transition);
responseData["transitionSettings"] = Utils::Json::ObsDataToJson(transitionSettings);
} else {
responseData["transitionConfigurable"] = false;
responseData["transitionSettings"] = nullptr;
}
return RequestResult::Success(responseData);
}
/**
* Sets the current scene transition.
*
* Small note: While the namespace of scene transitions is generally unique, that uniqueness is not a guarantee as it is with other resources like inputs.
*
* @requestField transitionName | String | Name of the transition to make active
*
* @requestType SetCurrentSceneTransition
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category transitions
*/
RequestResult RequestHandler::SetCurrentSceneTransition(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
if (!request.ValidateString("transitionName", statusCode, comment))
return RequestResult::Error(statusCode, comment);
std::string transitionName = request.RequestData["transitionName"];
OBSSourceAutoRelease transition = Utils::Obs::SearchHelper::GetSceneTransitionByName(transitionName);
if (!transition)
return RequestResult::Error(RequestStatus::ResourceNotFound, "No scene transition was found by that name.");
obs_frontend_set_current_transition(transition);
return RequestResult::Success();
}
/**
* Sets the duration of the current scene transition, if it is not fixed.
*
* @requestField transitionDuration | Number | Duration in milliseconds | >= 50, <= 20000
*
* @requestType SetCurrentSceneTransitionDuration
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category transitions
*/
RequestResult RequestHandler::SetCurrentSceneTransitionDuration(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
if (!request.ValidateNumber("transitionDuration", statusCode, comment, 50, 20000))
return RequestResult::Error(statusCode, comment);
int transitionDuration = request.RequestData["transitionDuration"];
obs_frontend_set_transition_duration(transitionDuration);
return RequestResult::Success();
}
/**
* Sets the settings of the current scene transition.
*
* @requestField transitionSettings | Object | Settings object to apply to the transition. Can be `{}`
* @requestField ?overlay | Boolean | Whether to overlay over the current settings or replace them | true
*
* @requestType SetCurrentSceneTransitionSettings
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category transitions
*/
RequestResult RequestHandler::SetCurrentSceneTransitionSettings(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
if (!request.ValidateObject("transitionSettings", statusCode, comment, true))
return RequestResult::Error(statusCode, comment);
OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
if (!transition)
return RequestResult::Error(RequestStatus::InvalidResourceState, "OBS does not currently have a scene transition set."); // This should not happen!
if (!obs_source_configurable(transition))
return RequestResult::Error(RequestStatus::ResourceNotConfigurable, "The current transition does not support custom settings.");
bool overlay = true;
if (request.Contains("overlay")) {
if (!request.ValidateOptionalBoolean("overlay", statusCode, comment))
return RequestResult::Error(statusCode, comment);
overlay = request.RequestData["overlay"];
}
OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["transitionSettings"]);
if (!newSettings)
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "An internal data conversion operation failed. Please report this!");
if (overlay)
obs_source_update(transition, newSettings);
else
obs_source_reset_settings(transition, newSettings);
obs_source_update_properties(transition);
return RequestResult::Success();
}
/**
* Gets the cursor position of the current scene transition.
*
* Note: `transitionCursor` will return 1.0 when the transition is inactive.
*
* @responseField transitionCursor | Number | Cursor position, between 0.0 and 1.0
*
* @requestType GetCurrentSceneTransitionCursor
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category transitions
*/
RequestResult RequestHandler::GetCurrentSceneTransitionCursor(const Request&)
{
OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
if (!transition)
return RequestResult::Error(RequestStatus::InvalidResourceState, "OBS does not currently have a scene transition set."); // This should not happen!
json responseData;
responseData["transitionCursor"] = obs_transition_get_time(transition);
return RequestResult::Success(responseData);
}
/**
* Triggers the current scene transition. Same functionality as the `Transition` button in studio mode.
*
* @requestType TriggerStudioModeTransition
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category transitions
*/
RequestResult RequestHandler::TriggerStudioModeTransition(const Request&)
{
if (!obs_frontend_preview_program_mode_active())
return RequestResult::Error(RequestStatus::StudioModeNotActive);
OBSSourceAutoRelease previewScene = obs_frontend_get_current_preview_scene();
obs_frontend_set_current_scene(previewScene);
return RequestResult::Success();
}
/**
* Sets the position of the TBar.
*
* **Very important note**: This will be deprecated and replaced in a future version of obs-websocket.
*
* @requestField position | Number | New position | >= 0.0, <= 1.0
* @requestField ?release | Boolean | Whether to release the TBar. Only set `false` if you know that you will be sending another position update | `true`
*
* @requestType SetTBarPosition
* @complexity 3
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category transitions
*/
RequestResult RequestHandler::SetTBarPosition(const Request& request)
{
if (!obs_frontend_preview_program_mode_active())
return RequestResult::Error(RequestStatus::StudioModeNotActive);
RequestStatus::RequestStatus statusCode;
std::string comment;
if (!request.ValidateNumber("position", statusCode, comment, 0.0, 1.0))
return RequestResult::Error(statusCode, comment);
bool release = true;
if (request.Contains("release")) {
if (!request.ValidateOptionalBoolean("release", statusCode, comment))
return RequestResult::Error(statusCode, comment);
}
OBSSourceAutoRelease transition = obs_frontend_get_current_transition();
if (!transition)
return RequestResult::Error(RequestStatus::InvalidResourceState, "OBS does not currently have a scene transition set."); // This should not happen!
float position = request.RequestData["position"];
obs_frontend_set_tbar_position((int)round(position * 1024.0));
if (release)
obs_frontend_release_tbar();
return RequestResult::Success();
}

View File

@ -0,0 +1,150 @@
/*
obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@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 "RequestHandler.h"
/**
* Gets whether studio is enabled.
*
* @responseField studioModeEnabled | Boolean | Whether studio mode is enabled
*
* @requestType GetStudioModeEnabled
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category ui
* @api requests
*/
RequestResult RequestHandler::GetStudioModeEnabled(const Request&)
{
json responseData;
responseData["studioModeEnabled"] = obs_frontend_preview_program_mode_active();
return RequestResult::Success(responseData);
}
/**
* Enables or disables studio mode
*
* @requestField studioModeEnabled | Boolean | True == Enabled, False == Disabled
*
* @requestType SetStudioModeEnabled
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category ui
* @api requests
*/
RequestResult RequestHandler::SetStudioModeEnabled(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
if (!request.ValidateBoolean("studioModeEnabled", statusCode, comment))
return RequestResult::Error(statusCode, comment);
// Avoid queueing tasks if nothing will change
if (obs_frontend_preview_program_mode_active() != request.RequestData["studioModeEnabled"]) {
// (Bad) Create a boolean then pass it as a reference to the task. Requires `wait` in obs_queue_task() to be true, else undefined behavior
bool studioModeEnabled = request.RequestData["studioModeEnabled"];
// Queue the task inside of the UI thread to prevent race conditions
obs_queue_task(OBS_TASK_UI, [](void* param) {
auto studioModeEnabled = (bool*)param;
obs_frontend_set_preview_program_mode(*studioModeEnabled);
}, &studioModeEnabled, true);
}
return RequestResult::Success();
}
/**
* Opens the properties dialog of an input.
*
* @requestField inputName | String | Name of the input to open the dialog of
*
* @requestType OpenInputPropertiesDialog
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category ui
* @api requests
*/
RequestResult RequestHandler::OpenInputPropertiesDialog(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!input)
return RequestResult::Error(statusCode, comment);
obs_frontend_open_source_properties(input);
return RequestResult::Success();
}
/**
* Opens the filters dialog of an input.
*
* @requestField inputName | String | Name of the input to open the dialog of
*
* @requestType OpenInputFiltersDialog
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category ui
* @api requests
*/
RequestResult RequestHandler::OpenInputFiltersDialog(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!input)
return RequestResult::Error(statusCode, comment);
obs_frontend_open_source_filters(input);
return RequestResult::Success();
}
/**
* Opens the interact dialog of an input.
*
* @requestField inputName | String | Name of the input to open the dialog of
*
* @requestType OpenInputInteractDialog
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category ui
* @api requests
*/
RequestResult RequestHandler::OpenInputInteractDialog(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!input)
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_INTERACTION))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support interaction.");
obs_frontend_open_source_interaction(input);
return RequestResult::Success();
}

View File

@ -264,18 +264,14 @@ obs_scene_t *Request::ValidateScene2(const std::string &keyName, RequestStatus::
comment = "The specified source is not a scene. (Is group)";
return nullptr;
}
OBSScene ret = obs_group_from_source(sceneSource);
obs_scene_addref(ret);
return ret;
return obs_scene_get_ref(obs_group_from_source(sceneSource));
} else {
if (filter == OBS_WEBSOCKET_SCENE_FILTER_GROUP_ONLY) {
statusCode = RequestStatus::InvalidResourceType;
comment = "The specified source is not a group. (Is scene)";
return nullptr;
}
OBSScene ret = obs_scene_from_source(sceneSource);
obs_scene_addref(ret);
return ret;
return obs_scene_get_ref(obs_scene_from_source(sceneSource));
}
}
@ -295,24 +291,35 @@ obs_source_t *Request::ValidateInput(const std::string &keyName, RequestStatus::
return ret;
}
FilterPair Request::ValidateFilter(const std::string &sourceKeyName, const std::string &filterKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const
{
obs_source_t *source = ValidateSource(sourceKeyName, statusCode, comment);
if (!source)
return FilterPair{source, nullptr};
if (!ValidateString(filterKeyName, statusCode, comment))
return FilterPair{source, nullptr};
std::string filterName = RequestData[filterKeyName];
obs_source_t *filter = obs_source_get_filter_by_name(source, filterName.c_str());
if (!filter) {
statusCode = RequestStatus::ResourceNotFound;
comment = std::string("No filter was found in the source `") + RequestData[sourceKeyName].get<std::string>() + "` with the name `" + filterName + "`.";
return FilterPair{source, nullptr};
}
return FilterPair{source, filter};
}
obs_sceneitem_t *Request::ValidateSceneItem(const std::string &sceneKeyName, const std::string &sceneItemIdKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter) const
{
OBSSourceAutoRelease sceneSource = ValidateScene(sceneKeyName, statusCode, comment, filter);
if (!sceneSource)
OBSSceneAutoRelease scene = ValidateScene2(sceneKeyName, statusCode, comment, filter);
if (!scene)
return nullptr;
if (!ValidateNumber(sceneItemIdKeyName, statusCode, comment, 0))
return nullptr;
OBSScene scene = obs_scene_from_source(sceneSource);
if (!scene) {
scene = obs_group_from_source(sceneSource);
if (!scene) { // This should never happen
statusCode = RequestStatus::GenericError;
comment = "Somehow the scene was found but the scene object could not be fetched. Please report this to the obs-websocket developers.";
return nullptr;
}
}
int64_t sceneItemId = RequestData[sceneItemIdKeyName];

View File

@ -29,6 +29,12 @@ enum ObsWebSocketSceneFilter {
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP,
};
// We return filters as a pair because `obs_filter_get_parent()` is apparently volatile
struct FilterPair {
OBSSourceAutoRelease source;
OBSSourceAutoRelease filter;
};
struct Request
{
Request(const std::string &requestType, const json &requestData = nullptr, const RequestBatchExecutionType::RequestBatchExecutionType executionType = RequestBatchExecutionType::None);
@ -53,6 +59,7 @@ struct Request
obs_source_t *ValidateScene(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
obs_scene_t *ValidateScene2(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
obs_source_t *ValidateInput(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const;
FilterPair ValidateFilter(const std::string &sourceKeyName, const std::string &filterKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const;
obs_sceneitem_t *ValidateSceneItem(const std::string &sceneKeyName, const std::string &sceneItemIdKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
std::string RequestType;

View File

@ -22,7 +22,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <stdint.h>
namespace RequestBatchExecutionType {
enum RequestBatchExecutionType {
enum RequestBatchExecutionType: int8_t {
/**
* Not a request batch.
*
@ -77,7 +77,7 @@ namespace RequestBatchExecutionType {
Parallel = 2,
};
inline bool IsValid(uint8_t executionType)
inline bool IsValid(int8_t executionType)
{
return executionType >= None && executionType <= Parallel;
}

View File

@ -332,6 +332,30 @@ namespace RequestStatus {
* @api enums
*/
InvalidInputKind = 605,
/**
* The resource does not support being configured.
*
* This is particularly relevant to transitions, where they do not always have changeable settings.
*
* @enumIdentifier ResourceNotConfigurable
* @enumValue 606
* @enumType RequestStatus
* @rpcVersion -1
* @initialVersion 5.0.0
* @api enums
*/
ResourceNotConfigurable = 606,
/**
* The specified filter (obs_source_t-OBS_SOURCE_TYPE_FILTER) had the wrong kind.
*
* @enumIdentifier InvalidFilterKind
* @enumValue 607
* @enumType RequestStatus
* @rpcVersion -1
* @initialVersion 5.0.0
* @api enums
*/
InvalidFilterKind = 607,
/**
* Creating the resource failed.

View File

@ -21,7 +21,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Platform.h"
#include "../plugin-macros.generated.h"
bool Utils::Json::JsonArrayIsValidObsArray(json j)
bool Utils::Json::JsonArrayIsValidObsArray(const json &j)
{
for (auto it : j) {
if (!it.is_object())
@ -191,7 +191,7 @@ bool Utils::Json::GetJsonFileContent(std::string fileName, json &content)
return true;
}
bool Utils::Json::SetJsonFileContent(std::string fileName, json content, bool createNew)
bool Utils::Json::SetJsonFileContent(std::string fileName, const json &content, bool createNew)
{
std::string textContent = content.dump(2);
return Utils::Platform::SetTextFileContent(fileName, textContent, createNew);

View File

@ -27,10 +27,11 @@ using json = nlohmann::json;
namespace Utils {
namespace Json {
bool JsonArrayIsValidObsArray(json j);
bool JsonArrayIsValidObsArray(const json &j);
obs_data_t *JsonToObsData(json j);
json ObsDataToJson(obs_data_t *d, bool includeDefault = false);
bool GetJsonFileContent(std::string fileName, json &content);
bool SetJsonFileContent(std::string fileName, json content, bool createNew = true);
bool SetJsonFileContent(std::string fileName, const json &content, bool createNew = true);
static inline bool Contains(const json &j, std::string key) { return j.contains(key) && !j[key].is_null(); }
}
}

View File

@ -17,562 +17,5 @@ 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 <algorithm>
#include <QString>
#include <obs-frontend-api.h>
#include <util/config-file.h>
#include <util/util_uint64.h>
#include "Obs.h"
#include "../obs-websocket.h"
#include "../plugin-macros.generated.h"
#define CASE(x) case x: return #x;
#define RET_COMPARE(str, x) if (str == #x) return x;
std::vector<std::string> ConvertStringArray(char **array)
{
std::vector<std::string> ret;
if (!array)
return ret;
size_t index = 0;
char* value = nullptr;
do {
value = array[index];
if (value)
ret.push_back(value);
index++;
} while (value);
return ret;
}
std::string Utils::Obs::StringHelper::GetObsVersion()
{
uint32_t version = obs_get_version();
uint8_t major, minor, patch;
major = (version >> 24) & 0xFF;
minor = (version >> 16) & 0xFF;
patch = version & 0xFF;
QString combined = QString("%1.%2.%3").arg(major).arg(minor).arg(patch);
return combined.toStdString();
}
std::string Utils::Obs::StringHelper::GetCurrentSceneCollection()
{
char *sceneCollectionName = obs_frontend_get_current_scene_collection();
std::string ret = sceneCollectionName;
bfree(sceneCollectionName);
return ret;
}
std::string Utils::Obs::StringHelper::GetCurrentProfile()
{
char *profileName = obs_frontend_get_current_profile();
std::string ret = profileName;
bfree(profileName);
return ret;
}
std::string Utils::Obs::StringHelper::GetCurrentProfilePath()
{
char *profilePath = obs_frontend_get_current_profile_path();
std::string ret = profilePath;
bfree(profilePath);
return ret;
}
std::string Utils::Obs::StringHelper::GetCurrentRecordOutputPath()
{
//char *recordOutputPath = obs_frontend_get_current_record_output_path();
//std::string ret = recordOutputPath;
//bfree(recordOutputPath);
//return ret;
return "";
}
std::string Utils::Obs::StringHelper::GetSourceType(obs_source_t *source)
{
obs_source_type sourceType = obs_source_get_type(source);
switch (sourceType) {
default:
CASE(OBS_SOURCE_TYPE_INPUT)
CASE(OBS_SOURCE_TYPE_FILTER)
CASE(OBS_SOURCE_TYPE_TRANSITION)
CASE(OBS_SOURCE_TYPE_SCENE)
}
}
std::string Utils::Obs::StringHelper::GetInputMonitorType(obs_source_t *input)
{
obs_monitoring_type monitorType = obs_source_get_monitoring_type(input);
switch (monitorType) {
default:
CASE(OBS_MONITORING_TYPE_NONE)
CASE(OBS_MONITORING_TYPE_MONITOR_ONLY)
CASE(OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT)
}
}
std::string Utils::Obs::StringHelper::GetMediaInputState(obs_source_t *input)
{
obs_media_state mediaState = obs_source_media_get_state(input);
switch (mediaState) {
default:
CASE(OBS_MEDIA_STATE_NONE)
CASE(OBS_MEDIA_STATE_PLAYING)
CASE(OBS_MEDIA_STATE_OPENING)
CASE(OBS_MEDIA_STATE_BUFFERING)
CASE(OBS_MEDIA_STATE_PAUSED)
CASE(OBS_MEDIA_STATE_STOPPED)
CASE(OBS_MEDIA_STATE_ENDED)
CASE(OBS_MEDIA_STATE_ERROR)
}
}
std::string Utils::Obs::StringHelper::GetLastReplayBufferFilePath()
{
OBSOutputAutoRelease output = obs_frontend_get_replay_buffer_output();
calldata_t cd = {0};
proc_handler_t *ph = obs_output_get_proc_handler(output);
proc_handler_call(ph, "get_last_replay", &cd);
auto ret = calldata_string(&cd, "path");
calldata_free(&cd);
return ret;
}
std::string Utils::Obs::StringHelper::GetSceneItemBoundsType(enum obs_bounds_type type)
{
switch (type) {
default:
CASE(OBS_BOUNDS_NONE)
CASE(OBS_BOUNDS_STRETCH)
CASE(OBS_BOUNDS_SCALE_INNER)
CASE(OBS_BOUNDS_SCALE_OUTER)
CASE(OBS_BOUNDS_SCALE_TO_WIDTH)
CASE(OBS_BOUNDS_SCALE_TO_HEIGHT)
CASE(OBS_BOUNDS_MAX_ONLY)
}
}
std::string Utils::Obs::StringHelper::DurationToTimecode(uint64_t ms)
{
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;
QString formatted = QString::asprintf("%02" PRIu64 ":%02" PRIu64 ":%02" PRIu64 ".%03" PRIu64, hoursPart, minutesPart, secsPart, msPart);
return formatted.toStdString();
}
enum obs_bounds_type Utils::Obs::EnumHelper::GetSceneItemBoundsType(std::string boundsType)
{
RET_COMPARE(boundsType, OBS_BOUNDS_NONE);
RET_COMPARE(boundsType, OBS_BOUNDS_STRETCH);
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_INNER);
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_OUTER);
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_TO_WIDTH);
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_TO_HEIGHT);
RET_COMPARE(boundsType, OBS_BOUNDS_MAX_ONLY);
return OBS_BOUNDS_NONE;
}
enum ObsMediaInputAction Utils::Obs::EnumHelper::GetMediaInputAction(std::string mediaAction)
{
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY);
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE);
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP);
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART);
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT);
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS);
return OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE;
}
uint64_t Utils::Obs::NumberHelper::GetOutputDuration(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 util_mul_div64(totalFrames, frameTimeNs, 1000000ULL);
}
size_t Utils::Obs::NumberHelper::GetSceneCount()
{
size_t ret;
auto sceneEnumProc = [](void *param, obs_source_t *scene) {
auto ret = reinterpret_cast<size_t*>(param);
if (obs_source_is_group(scene))
return true;
(*ret)++;
return true;
};
obs_enum_scenes(sceneEnumProc, &ret);
return ret;
}
std::vector<std::string> Utils::Obs::ListHelper::GetSceneCollectionList()
{
char** sceneCollections = obs_frontend_get_scene_collections();
auto ret = ConvertStringArray(sceneCollections);
bfree(sceneCollections);
return ret;
}
std::vector<std::string> Utils::Obs::ListHelper::GetProfileList()
{
char** profiles = obs_frontend_get_profiles();
auto ret = ConvertStringArray(profiles);
bfree(profiles);
return ret;
}
std::vector<obs_hotkey_t *> Utils::Obs::ListHelper::GetHotkeyList()
{
std::vector<obs_hotkey_t *> ret;
obs_enum_hotkeys([](void* data, obs_hotkey_id, obs_hotkey_t* hotkey) {
auto ret = reinterpret_cast<std::vector<obs_hotkey_t *> *>(data);
ret->push_back(hotkey);
return true;
}, &ret);
return ret;
}
std::vector<std::string> Utils::Obs::ListHelper::GetHotkeyNameList()
{
auto hotkeys = GetHotkeyList();
std::vector<std::string> ret;
for (auto hotkey : hotkeys) {
ret.emplace_back(obs_hotkey_get_name(hotkey));
}
return ret;
}
std::vector<json> Utils::Obs::ListHelper::GetSceneList()
{
obs_frontend_source_list sceneList = {};
obs_frontend_get_scenes(&sceneList);
std::vector<json> ret;
for (size_t i = 0; i < sceneList.sources.num; i++) {
obs_source_t *scene = sceneList.sources.array[i];
if (obs_source_is_group(scene))
continue;
json sceneJson;
sceneJson["sceneName"] = obs_source_get_name(scene);
sceneJson["sceneIndex"] = sceneList.sources.num - i - 1;
ret.push_back(sceneJson);
}
obs_frontend_source_list_free(&sceneList);
// Reverse the vector order to match other array returns
std::reverse(ret.begin(), ret.end());
return ret;
}
std::vector<json> Utils::Obs::ListHelper::GetSceneItemList(obs_scene_t *scene, bool basic)
{
std::pair<std::vector<json>, bool> enumData;
enumData.second = basic;
obs_scene_enum_items(scene, [](obs_scene_t*, obs_sceneitem_t* sceneItem, void* param) {
auto enumData = reinterpret_cast<std::pair<std::vector<json>, bool>*>(param);
json item;
item["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
// Should be slightly faster than calling obs_sceneitem_get_order_position()
item["sceneItemIndex"] = enumData->first.size();
if (!enumData->second) {
OBSSource itemSource = obs_sceneitem_get_source(sceneItem);
item["sourceName"] = obs_source_get_name(itemSource);
item["sourceType"] = StringHelper::GetSourceType(itemSource);
if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_INPUT)
item["inputKind"] = obs_source_get_id(itemSource);
else
item["inputKind"] = nullptr;
if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_SCENE)
item["isGroup"] = obs_source_is_group(itemSource);
else
item["isGroup"] = nullptr;
}
enumData->first.push_back(item);
return true;
}, &enumData);
return enumData.first;
}
std::vector<json> Utils::Obs::ListHelper::GetTransitionList()
{
obs_frontend_source_list transitionList = {};
obs_frontend_get_transitions(&transitionList);
std::vector<json> ret;
for (size_t i = 0; i < transitionList.sources.num; i++) {
obs_source_t *transition = transitionList.sources.array[i];
json transitionJson;
transitionJson["transitionName"] = obs_source_get_name(transition);
transitionJson["transitionKind"] = obs_source_get_id(transition);
transitionJson["transitionFixed"] = obs_transition_fixed(transition);
ret.push_back(transitionJson);
}
obs_frontend_source_list_free(&transitionList);
return ret;
}
struct EnumInputInfo {
std::string inputKind; // For searching by input kind
std::vector<json> inputs;
};
std::vector<json> Utils::Obs::ListHelper::GetInputList(std::string inputKind)
{
EnumInputInfo inputInfo;
inputInfo.inputKind = inputKind;
auto inputEnumProc = [](void *param, obs_source_t *input) {
// Sanity check in case the API changes
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
return true;
auto inputInfo = reinterpret_cast<EnumInputInfo*>(param);
std::string inputKind = obs_source_get_id(input);
if (!inputInfo->inputKind.empty() && inputInfo->inputKind != inputKind)
return true;
json inputJson;
inputJson["inputName"] = obs_source_get_name(input);
inputJson["inputKind"] = inputKind;
inputJson["unversionedInputKind"] = obs_source_get_unversioned_id(input);
inputInfo->inputs.push_back(inputJson);
return true;
};
// Actually enumerates only public inputs, despite the name
obs_enum_sources(inputEnumProc, &inputInfo);
return inputInfo.inputs;
}
std::vector<std::string> Utils::Obs::ListHelper::GetInputKindList(bool unversioned, bool includeDisabled)
{
std::vector<std::string> ret;
size_t idx = 0;
const char *kind;
const char *unversioned_kind;
while (obs_enum_input_types2(idx++, &kind, &unversioned_kind)) {
uint32_t caps = obs_get_source_output_flags(kind);
if (!includeDisabled && (caps & OBS_SOURCE_CAP_DISABLED) != 0)
continue;
if (unversioned)
ret.push_back(unversioned_kind);
else
ret.push_back(kind);
}
return ret;
}
json Utils::Obs::DataHelper::GetStats()
{
json ret;
config_t* currentProfile = obs_frontend_get_profile_config();
const char* outputMode = config_get_string(currentProfile, "Output", "Mode");
const char* recordPath = strcmp(outputMode, "Advanced") ? config_get_string(currentProfile, "SimpleOutput", "FilePath") : config_get_string(currentProfile, "AdvOut", "RecFilePath");
video_t* video = obs_get_video();
ret["cpuUsage"] = os_cpu_usage_info_query(GetCpuUsageInfo());
ret["memoryUsage"] = (double)os_get_proc_resident_size() / (1024.0 * 1024.0);
ret["availableDiskSpace"] = (double)os_get_free_disk_space(recordPath) / (1024.0 * 1024.0);
ret["activeFps"] = obs_get_active_fps();
ret["averageFrameRenderTime"] = (double)obs_get_average_frame_time_ns() / 1000000.0;
ret["renderSkippedFrames"] = obs_get_lagged_frames();
ret["renderTotalFrames"] = obs_get_total_frames();
ret["outputSkippedFrames"] = video_output_get_skipped_frames(video);
ret["outputTotalFrames"] = video_output_get_total_frames(video);
return ret;
}
json Utils::Obs::DataHelper::GetSceneItemTransform(obs_sceneitem_t *item)
{
json ret;
obs_transform_info osi;
obs_sceneitem_crop crop;
obs_sceneitem_get_info(item, &osi);
obs_sceneitem_get_crop(item, &crop);
OBSSource source = obs_sceneitem_get_source(item);
float sourceWidth = float(obs_source_get_width(source));
float sourceHeight = float(obs_source_get_height(source));
ret["sourceWidth"] = sourceWidth;
ret["sourceHeight"] = sourceHeight;
ret["positionX"] = osi.pos.x;
ret["positionY"] = osi.pos.y;
ret["rotation"] = osi.rot;
ret["scaleX"] = osi.scale.x;
ret["scaleY"] = osi.scale.y;
ret["width"] = osi.scale.x * sourceWidth;
ret["height"] = osi.scale.y * sourceHeight;
ret["alignment"] = osi.alignment;
ret["boundsType"] = StringHelper::GetSceneItemBoundsType(osi.bounds_type);
ret["boundsAlignment"] = osi.bounds_alignment;
ret["boundsWidth"] = osi.bounds.x;
ret["boundsHeight"] = osi.bounds.y;
ret["cropLeft"] = int(crop.left);
ret["cropRight"] = int(crop.right);
ret["cropTop"] = int(crop.top);
ret["cropBottom"] = int(crop.bottom);
return ret;
}
obs_hotkey_t *Utils::Obs::SearchHelper::GetHotkeyByName(std::string name)
{
if (name.empty())
return nullptr;
auto hotkeys = ListHelper::GetHotkeyList();
for (auto hotkey : hotkeys) {
if (obs_hotkey_get_name(hotkey) == name)
return hotkey;
}
return nullptr;
}
// Increments item ref. Use OBSSceneItemAutoRelease
obs_sceneitem_t *Utils::Obs::SearchHelper::GetSceneItemByName(obs_scene_t *scene, std::string name)
{
if (name.empty())
return nullptr;
// Finds first matching scene item in scene, search starts at index 0
OBSSceneItem ret = obs_scene_find_source(scene, name.c_str());
obs_sceneitem_addref(ret);
return ret;
}
struct CreateSceneItemData {
obs_source_t *source; // In
bool sceneItemEnabled; // In
obs_transform_info *sceneItemTransform = nullptr; // In
obs_sceneitem_crop *sceneItemCrop = nullptr; // In
OBSSceneItem sceneItem; // Out
};
void CreateSceneItemHelper(void *_data, obs_scene_t *scene)
{
auto *data = reinterpret_cast<CreateSceneItemData*>(_data);
data->sceneItem = obs_scene_add(scene, data->source);
if (data->sceneItemTransform)
obs_sceneitem_set_info(data->sceneItem, data->sceneItemTransform);
if (data->sceneItemCrop)
obs_sceneitem_set_crop(data->sceneItem, data->sceneItemCrop);
obs_sceneitem_set_visible(data->sceneItem, data->sceneItemEnabled);
}
obs_sceneitem_t *Utils::Obs::ActionHelper::CreateSceneItem(obs_source_t *source, obs_scene_t *scene, bool sceneItemEnabled, obs_transform_info *sceneItemTransform, obs_sceneitem_crop *sceneItemCrop)
{
// Sanity check for valid scene
if (!(source && scene))
return nullptr;
// Create data struct and populate for scene item creation
CreateSceneItemData data;
data.source = source;
data.sceneItemEnabled = sceneItemEnabled;
data.sceneItemTransform = sceneItemTransform;
data.sceneItemCrop = sceneItemCrop;
// Enter graphics context and create the scene item
obs_enter_graphics();
obs_scene_atomic_update(scene, CreateSceneItemHelper, &data);
obs_leave_graphics();
obs_sceneitem_addref(data.sceneItem);
return data.sceneItem;
}
obs_sceneitem_t *Utils::Obs::ActionHelper::CreateInput(std::string inputName, std::string inputKind, obs_data_t *inputSettings, obs_scene_t *scene, bool sceneItemEnabled)
{
// Create the input
OBSSourceAutoRelease input = obs_source_create(inputKind.c_str(), inputName.c_str(), inputSettings, nullptr);
// Check that everything was created properly
if (!input)
return nullptr;
// Apparently not all default input properties actually get applied on creation (smh)
uint32_t flags = obs_source_get_output_flags(input);
if ((flags & OBS_SOURCE_MONITOR_BY_DEFAULT) != 0)
obs_source_set_monitoring_type(input, OBS_MONITORING_TYPE_MONITOR_ONLY);
// Create a scene item for the input
obs_sceneitem_t *ret = CreateSceneItem(input, scene, sceneItemEnabled);
// If creation failed, remove the input
if (!ret)
obs_source_remove(input);
return ret;
}

View File

@ -21,33 +21,132 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <string>
#include <obs.hpp>
#include <obs-frontend-api.h>
#include "Json.h"
// Autorelease object definitions
inline void ___properties_dummy_addref(obs_properties_t*){}
using OBSPropertiesAutoDestroy = OBSRef<obs_properties_t*, ___properties_dummy_addref, obs_properties_destroy>;
#if !defined(OBS_AUTORELEASE)
inline void ___source_dummy_addref(obs_source_t*){}
inline void ___scene_dummy_addref(obs_scene_t*){}
inline void ___sceneitem_dummy_addref(obs_sceneitem_t*){}
inline void ___data_dummy_addref(obs_data_t*){}
inline void ___data_array_dummy_addref(obs_data_array_t*){}
inline void ___output_dummy_addref(obs_output_t*){}
inline void ___encoder_dummy_addref(obs_encoder_t *){}
inline void ___service_dummy_addref(obs_service_t *){}
inline void ___weak_source_dummy_addref(obs_weak_source_t*){}
inline void ___weak_output_dummy_addref(obs_weak_output_t *){}
inline void ___weak_encoder_dummy_addref(obs_weak_encoder_t *){}
inline void ___weak_service_dummy_addref(obs_weak_service_t *){}
using OBSSourceAutoRelease = OBSRef<obs_source_t*, ___source_dummy_addref, obs_source_release>;
using OBSSceneAutoRelease = OBSRef<obs_scene_t*, ___scene_dummy_addref, obs_scene_release>;
using OBSSceneItemAutoRelease = OBSRef<obs_sceneitem_t*, ___sceneitem_dummy_addref, obs_sceneitem_release>;
using OBSDataAutoRelease = OBSRef<obs_data_t*, ___data_dummy_addref, obs_data_release>;
using OBSDataArrayAutoRelease = OBSRef<obs_data_array_t*, ___data_array_dummy_addref, obs_data_array_release>;
using OBSOutputAutoRelease = OBSRef<obs_output_t*, ___output_dummy_addref, obs_output_release>;
using OBSEncoderAutoRelease = OBSRef<obs_encoder_t *, ___encoder_dummy_addref, obs_encoder_release>;
using OBSServiceAutoRelease = OBSRef<obs_service_t *, ___service_dummy_addref, obs_service_release>;
using OBSWeakSourceAutoRelease = OBSRef<obs_weak_source_t*, ___weak_source_dummy_addref, obs_weak_source_release>;
using OBSWeakOutputAutoRelease = OBSRef<obs_weak_output_t *, ___weak_output_dummy_addref, obs_weak_output_release>;
using OBSWeakEncoderAutoRelease = OBSRef<obs_weak_encoder_t *, ___weak_encoder_dummy_addref, obs_weak_encoder_release>;
using OBSWeakServiceAutoRelease = OBSRef<obs_weak_service_t *, ___weak_service_dummy_addref, obs_weak_service_release>;
#endif
template <typename T> T* GetCalldataPointer(const calldata_t *data, const char* name) {
void *ptr = nullptr;
calldata_get_ptr(data, name, &ptr);
return reinterpret_cast<T*>(ptr);
return static_cast<T*>(ptr);
}
enum ObsOutputState {
OBS_WEBSOCKET_OUTPUT_UNKNOWN,
OBS_WEBSOCKET_OUTPUT_STARTING,
OBS_WEBSOCKET_OUTPUT_STARTED,
OBS_WEBSOCKET_OUTPUT_STOPPING,
OBS_WEBSOCKET_OUTPUT_STOPPED,
OBS_WEBSOCKET_OUTPUT_RECONNECTING,
OBS_WEBSOCKET_OUTPUT_PAUSED,
OBS_WEBSOCKET_OUTPUT_RESUMED
OBS_WEBSOCKET_OUTPUT_RESUMED,
};
enum ObsMediaInputAction {
/**
* No action.
*
* @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE
* @enumType ObsMediaInputAction
* @rpcVersion 1
* @initialVersion 5.0.0
* @api enums
*/
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE,
/**
* Play the media input.
*
* @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY
* @enumType ObsMediaInputAction
* @rpcVersion 1
* @initialVersion 5.0.0
* @api enums
*/
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY,
/**
* Pause the media input.
*
* @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE
* @enumType ObsMediaInputAction
* @rpcVersion 1
* @initialVersion 5.0.0
* @api enums
*/
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE,
/**
* Stop the media input.
*
* @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP
* @enumType ObsMediaInputAction
* @rpcVersion 1
* @initialVersion 5.0.0
* @api enums
*/
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP,
/**
* Restart the media input.
*
* @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART
* @enumType ObsMediaInputAction
* @rpcVersion 1
* @initialVersion 5.0.0
* @api enums
*/
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART,
/**
* Go to the next playlist item.
*
* @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT
* @enumType ObsMediaInputAction
* @rpcVersion 1
* @initialVersion 5.0.0
* @api enums
*/
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT,
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS
/**
* Go to the previous playlist item.
*
* @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS
* @enumType ObsMediaInputAction
* @rpcVersion 1
* @initialVersion 5.0.0
* @api enums
*/
OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS,
};
namespace Utils {
@ -59,48 +158,61 @@ namespace Utils {
std::string GetCurrentProfilePath();
std::string GetCurrentRecordOutputPath();
std::string GetSourceType(obs_source_t *source);
std::string GetInputMonitorType(enum obs_monitoring_type monitorType);
std::string GetInputMonitorType(obs_source_t *input);
std::string GetMediaInputState(obs_source_t *input);
std::string GetLastReplayBufferFilePath();
std::string GetSceneItemBoundsType(enum obs_bounds_type type);
std::string GetSceneItemBlendMode(enum obs_blending_type mode);
std::string DurationToTimecode(uint64_t);
std::string GetOutputState(ObsOutputState state);
}
namespace EnumHelper {
enum obs_bounds_type GetSceneItemBoundsType(std::string boundsType);
enum ObsMediaInputAction GetMediaInputAction(std::string mediaAction);
enum obs_blending_type GetSceneItemBlendMode(std::string mode);
}
namespace NumberHelper {
uint64_t GetOutputDuration(obs_output_t *output);
size_t GetSceneCount();
size_t GetSourceFilterIndex(obs_source_t *source, obs_source_t *filter);
}
namespace ListHelper {
namespace ArrayHelper {
std::vector<std::string> GetSceneCollectionList();
std::vector<std::string> GetProfileList();
std::vector<obs_hotkey_t *> GetHotkeyList();
std::vector<std::string> GetHotkeyNameList();
std::vector<json> GetSceneList();
std::vector<std::string> GetGroupList();
std::vector<json> GetSceneItemList(obs_scene_t *scene, bool basic = false);
std::vector<json> GetTransitionList();
std::vector<json> GetInputList(std::string inputKind = "");
std::vector<std::string> GetInputKindList(bool unversioned = false, bool includeDisabled = false);
std::vector<json> GetListPropertyItems(obs_property_t *property);
std::vector<std::string> GetTransitionKindList();
std::vector<json> GetSceneTransitionList();
std::vector<json> GetSourceFilterList(obs_source_t *source);
std::vector<std::string> GetFilterKindList();
}
namespace DataHelper {
namespace ObjectHelper {
json GetStats();
json GetSceneItemTransform(obs_sceneitem_t *item);
}
namespace SearchHelper {
obs_hotkey_t *GetHotkeyByName(std::string name);
obs_source_t *GetSceneTransitionByName(std::string name); // Increments source ref. Use OBSSourceAutoRelease
obs_sceneitem_t *GetSceneItemByName(obs_scene_t *scene, std::string name); // Increments ref. Use OBSSceneItemAutoRelease
}
namespace ActionHelper {
obs_sceneitem_t *CreateSceneItem(obs_source_t *source, obs_scene_t *scene, bool sceneItemEnabled = true, obs_transform_info *sceneItemTransform = nullptr, obs_sceneitem_crop *sceneItemCrop = nullptr); // Increments ref. Use OBSSceneItemAutoRelease
obs_sceneitem_t *CreateInput(std::string inputName, std::string inputKind, obs_data_t *inputSettings, obs_scene_t *scene, bool sceneItemEnabled = true); // Increments ref. Use OBSSceneItemAutoRelease
obs_source_t *CreateSourceFilter(obs_source_t *source, std::string filterName, std::string filterKind, obs_data_t *filterSettings); // Increments source ref. Use OBSSourceAutoRelease
void SetSourceFilterIndex(obs_source_t *source, obs_source_t *filter, size_t index);
}
}
}

View File

@ -0,0 +1,116 @@
/*
obs-websocket
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include "Obs.h"
#include "../plugin-macros.generated.h"
struct CreateSceneItemData {
obs_source_t *source; // In
bool sceneItemEnabled; // In
obs_transform_info *sceneItemTransform = nullptr; // In
obs_sceneitem_crop *sceneItemCrop = nullptr; // In
OBSSceneItem sceneItem; // Out
};
void CreateSceneItemHelper(void *_data, obs_scene_t *scene)
{
auto *data = static_cast<CreateSceneItemData*>(_data);
data->sceneItem = obs_scene_add(scene, data->source);
if (data->sceneItemTransform)
obs_sceneitem_set_info(data->sceneItem, data->sceneItemTransform);
if (data->sceneItemCrop)
obs_sceneitem_set_crop(data->sceneItem, data->sceneItemCrop);
obs_sceneitem_set_visible(data->sceneItem, data->sceneItemEnabled);
}
obs_sceneitem_t *Utils::Obs::ActionHelper::CreateSceneItem(obs_source_t *source, obs_scene_t *scene, bool sceneItemEnabled, obs_transform_info *sceneItemTransform, obs_sceneitem_crop *sceneItemCrop)
{
// Sanity check for valid scene
if (!(source && scene))
return nullptr;
// Create data struct and populate for scene item creation
CreateSceneItemData data;
data.source = source;
data.sceneItemEnabled = sceneItemEnabled;
data.sceneItemTransform = sceneItemTransform;
data.sceneItemCrop = sceneItemCrop;
// Enter graphics context and create the scene item
obs_enter_graphics();
obs_scene_atomic_update(scene, CreateSceneItemHelper, &data);
obs_leave_graphics();
obs_sceneitem_addref(data.sceneItem);
return data.sceneItem;
}
obs_sceneitem_t *Utils::Obs::ActionHelper::CreateInput(std::string inputName, std::string inputKind, obs_data_t *inputSettings, obs_scene_t *scene, bool sceneItemEnabled)
{
// Create the input
OBSSourceAutoRelease input = obs_source_create(inputKind.c_str(), inputName.c_str(), inputSettings, nullptr);
// Check that everything was created properly
if (!input)
return nullptr;
// Apparently not all default input properties actually get applied on creation (smh)
uint32_t flags = obs_source_get_output_flags(input);
if ((flags & OBS_SOURCE_MONITOR_BY_DEFAULT) != 0)
obs_source_set_monitoring_type(input, OBS_MONITORING_TYPE_MONITOR_ONLY);
// Create a scene item for the input
obs_sceneitem_t *ret = CreateSceneItem(input, scene, sceneItemEnabled);
// If creation failed, remove the input
if (!ret)
obs_source_remove(input);
return ret;
}
obs_source_t *Utils::Obs::ActionHelper::CreateSourceFilter(obs_source_t *source, std::string filterName, std::string filterKind, obs_data_t *filterSettings)
{
obs_source_t *filter = obs_source_create_private(filterKind.c_str(), filterName.c_str(), filterSettings);
if (!filter)
return nullptr;
obs_source_filter_add(source, filter);
return filter;
}
void Utils::Obs::ActionHelper::SetSourceFilterIndex(obs_source_t *source, obs_source_t *filter, size_t index)
{
size_t currentIndex = Utils::Obs::NumberHelper::GetSourceFilterIndex(source, filter);
obs_order_movement direction = index > currentIndex ? OBS_ORDER_MOVE_DOWN : OBS_ORDER_MOVE_UP;
while(currentIndex != index) {
obs_source_filter_set_order(source, filter, direction);
if (direction == OBS_ORDER_MOVE_DOWN)
currentIndex++;
else
currentIndex--;
}
}

View File

@ -0,0 +1,313 @@
/*
obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@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 <algorithm>
#include "Obs.h"
#include "../plugin-macros.generated.h"
static std::vector<std::string> ConvertStringArray(char **array)
{
std::vector<std::string> ret;
if (!array)
return ret;
size_t index = 0;
char* value = nullptr;
do {
value = array[index];
if (value)
ret.push_back(value);
index++;
} while (value);
return ret;
}
std::vector<std::string> Utils::Obs::ArrayHelper::GetSceneCollectionList()
{
char** sceneCollections = obs_frontend_get_scene_collections();
auto ret = ConvertStringArray(sceneCollections);
bfree(sceneCollections);
return ret;
}
std::vector<std::string> Utils::Obs::ArrayHelper::GetProfileList()
{
char** profiles = obs_frontend_get_profiles();
auto ret = ConvertStringArray(profiles);
bfree(profiles);
return ret;
}
std::vector<obs_hotkey_t *> Utils::Obs::ArrayHelper::GetHotkeyList()
{
std::vector<obs_hotkey_t *> ret;
obs_enum_hotkeys([](void* data, obs_hotkey_id, obs_hotkey_t* hotkey) {
auto ret = static_cast<std::vector<obs_hotkey_t *> *>(data);
ret->push_back(hotkey);
return true;
}, &ret);
return ret;
}
std::vector<std::string> Utils::Obs::ArrayHelper::GetHotkeyNameList()
{
auto hotkeys = GetHotkeyList();
std::vector<std::string> ret;
for (auto hotkey : hotkeys)
ret.emplace_back(obs_hotkey_get_name(hotkey));
return ret;
}
std::vector<json> Utils::Obs::ArrayHelper::GetSceneList()
{
obs_frontend_source_list sceneList = {};
obs_frontend_get_scenes(&sceneList);
std::vector<json> ret;
for (size_t i = 0; i < sceneList.sources.num; i++) {
obs_source_t *scene = sceneList.sources.array[i];
json sceneJson;
sceneJson["sceneName"] = obs_source_get_name(scene);
sceneJson["sceneIndex"] = sceneList.sources.num - i - 1;
ret.push_back(sceneJson);
}
obs_frontend_source_list_free(&sceneList);
// Reverse the vector order to match other array returns
std::reverse(ret.begin(), ret.end());
return ret;
}
std::vector<std::string> Utils::Obs::ArrayHelper::GetGroupList()
{
std::vector<std::string> ret;
auto cb = [](void *priv_data, obs_source_t *scene) {
auto ret = static_cast<std::vector<std::string>*>(priv_data);
if (!obs_source_is_group(scene))
return true;
ret->emplace_back(obs_source_get_name(scene));
return true;
};
obs_enum_scenes(cb, &ret);
return ret;
}
std::vector<json> Utils::Obs::ArrayHelper::GetSceneItemList(obs_scene_t *scene, bool basic)
{
std::pair<std::vector<json>, bool> enumData;
enumData.second = basic;
obs_scene_enum_items(scene, [](obs_scene_t*, obs_sceneitem_t* sceneItem, void* param) {
auto enumData = static_cast<std::pair<std::vector<json>, bool>*>(param);
json item;
item["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
// Should be slightly faster than calling obs_sceneitem_get_order_position()
item["sceneItemIndex"] = enumData->first.size();
if (!enumData->second) {
OBSSource itemSource = obs_sceneitem_get_source(sceneItem);
item["sourceName"] = obs_source_get_name(itemSource);
item["sourceType"] = StringHelper::GetSourceType(itemSource);
if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_INPUT)
item["inputKind"] = obs_source_get_id(itemSource);
else
item["inputKind"] = nullptr;
if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_SCENE)
item["isGroup"] = obs_source_is_group(itemSource);
else
item["isGroup"] = nullptr;
}
enumData->first.push_back(item);
return true;
}, &enumData);
return enumData.first;
}
struct EnumInputInfo {
std::string inputKind; // For searching by input kind
std::vector<json> inputs;
};
std::vector<json> Utils::Obs::ArrayHelper::GetInputList(std::string inputKind)
{
EnumInputInfo inputInfo;
inputInfo.inputKind = inputKind;
auto inputEnumProc = [](void *param, obs_source_t *input) {
// Sanity check in case the API changes
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
return true;
auto inputInfo = static_cast<EnumInputInfo*>(param);
std::string inputKind = obs_source_get_id(input);
if (!inputInfo->inputKind.empty() && inputInfo->inputKind != inputKind)
return true;
json inputJson;
inputJson["inputName"] = obs_source_get_name(input);
inputJson["inputKind"] = inputKind;
inputJson["unversionedInputKind"] = obs_source_get_unversioned_id(input);
inputInfo->inputs.push_back(inputJson);
return true;
};
// Actually enumerates only public inputs, despite the name
obs_enum_sources(inputEnumProc, &inputInfo);
return inputInfo.inputs;
}
std::vector<std::string> Utils::Obs::ArrayHelper::GetInputKindList(bool unversioned, bool includeDisabled)
{
std::vector<std::string> ret;
size_t idx = 0;
const char *kind;
const char *unversioned_kind;
while (obs_enum_input_types2(idx++, &kind, &unversioned_kind)) {
uint32_t caps = obs_get_source_output_flags(kind);
if (!includeDisabled && (caps & OBS_SOURCE_CAP_DISABLED) != 0)
continue;
if (unversioned)
ret.push_back(unversioned_kind);
else
ret.push_back(kind);
}
return ret;
}
std::vector<json> Utils::Obs::ArrayHelper::GetListPropertyItems(obs_property_t *property)
{
std::vector<json> ret;
enum obs_combo_format itemFormat = obs_property_list_format(property);
size_t itemCount = obs_property_list_item_count(property);
for (size_t i = 0; i < itemCount; i++) {
json itemData;
itemData["itemName"] = obs_property_list_item_name(property, i);
itemData["itemEnabled"] = !obs_property_list_item_disabled(property, i);
if (itemFormat == OBS_COMBO_FORMAT_INT) {
itemData["itemValue"] = obs_property_list_item_int(property, i);
} else if (itemFormat == OBS_COMBO_FORMAT_FLOAT) {
itemData["itemValue"] = obs_property_list_item_float(property, i);
} else if (itemFormat == OBS_COMBO_FORMAT_STRING) {
itemData["itemValue"] = obs_property_list_item_string(property, i);
} else {
itemData["itemValue"] = nullptr;
}
ret.push_back(itemData);
}
return ret;
}
std::vector<std::string> Utils::Obs::ArrayHelper::GetTransitionKindList()
{
std::vector<std::string> ret;
size_t idx = 0;
const char *kind;
while (obs_enum_transition_types(idx++, &kind))
ret.emplace_back(kind);
return ret;
}
std::vector<json> Utils::Obs::ArrayHelper::GetSceneTransitionList()
{
obs_frontend_source_list transitionList = {};
obs_frontend_get_transitions(&transitionList);
std::vector<json> ret;
for (size_t i = 0; i < transitionList.sources.num; i++) {
obs_source_t *transition = transitionList.sources.array[i];
json transitionJson;
transitionJson["transitionName"] = obs_source_get_name(transition);
transitionJson["transitionKind"] = obs_source_get_id(transition);
transitionJson["transitionFixed"] = obs_transition_fixed(transition);
transitionJson["transitionConfigurable"] = obs_source_configurable(transition);
ret.push_back(transitionJson);
}
obs_frontend_source_list_free(&transitionList);
return ret;
}
std::vector<std::string> Utils::Obs::ArrayHelper::GetFilterKindList()
{
std::vector<std::string> ret;
size_t idx = 0;
const char *kind;
while(obs_enum_filter_types(idx++, &kind))
ret.push_back(kind);
return ret;
}
std::vector<json> Utils::Obs::ArrayHelper::GetSourceFilterList(obs_source_t *source)
{
std::vector<json> filters;
auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param) {
auto filters = reinterpret_cast<std::vector<json>*>(param);
json filterJson;
filterJson["filterEnabled"] = obs_source_enabled(filter);
filterJson["filterIndex"] = filters->size();
filterJson["filterKind"] = obs_source_get_id(filter);
filterJson["filterName"] = obs_source_get_name(filter);
OBSDataAutoRelease filterSettings = obs_source_get_settings(filter);
filterJson["filterSettings"] = Utils::Json::ObsDataToJson(filterSettings);
filters->push_back(filterJson);
};
obs_source_enum_filters(source, enumFilters, &filters);
return filters;
}

View File

@ -0,0 +1,60 @@
/*
obs-websocket
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include "Obs.h"
#include "../plugin-macros.generated.h"
#define RET_COMPARE(str, x) if (str == #x) return x;
enum obs_bounds_type Utils::Obs::EnumHelper::GetSceneItemBoundsType(std::string boundsType)
{
RET_COMPARE(boundsType, OBS_BOUNDS_NONE)
RET_COMPARE(boundsType, OBS_BOUNDS_STRETCH)
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_INNER)
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_OUTER)
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_TO_WIDTH)
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_TO_HEIGHT)
RET_COMPARE(boundsType, OBS_BOUNDS_MAX_ONLY)
return OBS_BOUNDS_NONE;
}
enum ObsMediaInputAction Utils::Obs::EnumHelper::GetMediaInputAction(std::string mediaAction)
{
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY)
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE)
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP)
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART)
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT)
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS)
return OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE;
}
enum obs_blending_type Utils::Obs::EnumHelper::GetSceneItemBlendMode(std::string mode)
{
RET_COMPARE(mode, OBS_BLEND_NORMAL)
RET_COMPARE(mode, OBS_BLEND_ADDITIVE)
RET_COMPARE(mode, OBS_BLEND_SUBTRACT)
RET_COMPARE(mode, OBS_BLEND_SCREEN)
RET_COMPARE(mode, OBS_BLEND_MULTIPLY)
RET_COMPARE(mode, OBS_BLEND_LIGHTEN)
RET_COMPARE(mode, OBS_BLEND_DARKEN)
return OBS_BLEND_NORMAL;
}

View File

@ -0,0 +1,79 @@
/*
obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@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 <stdint.h>
#include <util/util_uint64.h>
#include "Obs.h"
#include "../plugin-macros.generated.h"
uint64_t Utils::Obs::NumberHelper::GetOutputDuration(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 util_mul_div64(totalFrames, frameTimeNs, 1000000ULL);
}
size_t Utils::Obs::NumberHelper::GetSceneCount()
{
size_t ret;
auto sceneEnumProc = [](void *param, obs_source_t *scene) {
auto ret = static_cast<size_t*>(param);
if (obs_source_is_group(scene))
return true;
(*ret)++;
return true;
};
obs_enum_scenes(sceneEnumProc, &ret);
return ret;
}
size_t Utils::Obs::NumberHelper::GetSourceFilterIndex(obs_source_t *source, obs_source_t *filter)
{
struct FilterSearch {
obs_source_t *filter;
bool found;
size_t index;
};
auto search = [](obs_source_t *, obs_source_t *filter, void *priv_data) {
auto filterSearch = static_cast<FilterSearch*>(priv_data);
if (filter == filterSearch->filter)
filterSearch->found = true;
if (!filterSearch->found)
filterSearch->index++;
};
FilterSearch filterSearch = {filter, 0, 0};
obs_source_enum_filters(source, search, &filterSearch);
return filterSearch.index;
}

View File

@ -0,0 +1,87 @@
/*
obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@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 <util/config-file.h>
#include "Obs.h"
#include "../obs-websocket.h"
#include "../plugin-macros.generated.h"
json Utils::Obs::ObjectHelper::GetStats()
{
json ret;
std::string outputPath = Utils::Obs::StringHelper::GetCurrentRecordOutputPath();
video_t* video = obs_get_video();
ret["cpuUsage"] = os_cpu_usage_info_query(GetCpuUsageInfo());
ret["memoryUsage"] = (double)os_get_proc_resident_size() / (1024.0 * 1024.0);
ret["availableDiskSpace"] = (double)os_get_free_disk_space(outputPath.c_str()) / (1024.0 * 1024.0);
ret["activeFps"] = obs_get_active_fps();
ret["averageFrameRenderTime"] = (double)obs_get_average_frame_time_ns() / 1000000.0;
ret["renderSkippedFrames"] = obs_get_lagged_frames();
ret["renderTotalFrames"] = obs_get_total_frames();
ret["outputSkippedFrames"] = video_output_get_skipped_frames(video);
ret["outputTotalFrames"] = video_output_get_total_frames(video);
return ret;
}
json Utils::Obs::ObjectHelper::GetSceneItemTransform(obs_sceneitem_t *item)
{
json ret;
obs_transform_info osi;
obs_sceneitem_crop crop;
obs_sceneitem_get_info(item, &osi);
obs_sceneitem_get_crop(item, &crop);
OBSSource source = obs_sceneitem_get_source(item);
float sourceWidth = float(obs_source_get_width(source));
float sourceHeight = float(obs_source_get_height(source));
ret["sourceWidth"] = sourceWidth;
ret["sourceHeight"] = sourceHeight;
ret["positionX"] = osi.pos.x;
ret["positionY"] = osi.pos.y;
ret["rotation"] = osi.rot;
ret["scaleX"] = osi.scale.x;
ret["scaleY"] = osi.scale.y;
ret["width"] = osi.scale.x * sourceWidth;
ret["height"] = osi.scale.y * sourceHeight;
ret["alignment"] = osi.alignment;
ret["boundsType"] = StringHelper::GetSceneItemBoundsType(osi.bounds_type);
ret["boundsAlignment"] = osi.bounds_alignment;
ret["boundsWidth"] = osi.bounds.x;
ret["boundsHeight"] = osi.bounds.y;
ret["cropLeft"] = int(crop.left);
ret["cropRight"] = int(crop.right);
ret["cropTop"] = int(crop.top);
ret["cropBottom"] = int(crop.bottom);
return ret;
}

View File

@ -0,0 +1,68 @@
/*
obs-websocket
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include "Obs.h"
#include "../plugin-macros.generated.h"
obs_hotkey_t *Utils::Obs::SearchHelper::GetHotkeyByName(std::string name)
{
if (name.empty())
return nullptr;
auto hotkeys = ArrayHelper::GetHotkeyList();
for (auto hotkey : hotkeys) {
if (obs_hotkey_get_name(hotkey) == name)
return hotkey;
}
return nullptr;
}
// Increments source ref. Use OBSSourceAutoRelease
obs_source_t *Utils::Obs::SearchHelper::GetSceneTransitionByName(std::string name)
{
obs_frontend_source_list transitionList = {};
obs_frontend_get_transitions(&transitionList);
obs_source_t *ret = nullptr;
for (size_t i = 0; i < transitionList.sources.num; i++) {
obs_source_t *transition = transitionList.sources.array[i];
if (obs_source_get_name(transition) == name) {
ret = obs_source_get_ref(transition);
break;
}
}
obs_frontend_source_list_free(&transitionList);
return ret;
}
// Increments item ref. Use OBSSceneItemAutoRelease
obs_sceneitem_t *Utils::Obs::SearchHelper::GetSceneItemByName(obs_scene_t *scene, std::string name)
{
if (name.empty())
return nullptr;
// Finds first matching scene item in scene, search starts at index 0
OBSSceneItem ret = obs_scene_find_source(scene, name.c_str());
obs_sceneitem_addref(ret);
return ret;
}

View File

@ -0,0 +1,192 @@
/*
obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@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 <QString>
#include "Obs.h"
#include "../plugin-macros.generated.h"
#define CASE(x) case x: return #x;
std::string Utils::Obs::StringHelper::GetObsVersion()
{
uint32_t version = obs_get_version();
uint8_t major, minor, patch;
major = (version >> 24) & 0xFF;
minor = (version >> 16) & 0xFF;
patch = version & 0xFF;
QString combined = QString("%1.%2.%3").arg(major).arg(minor).arg(patch);
return combined.toStdString();
}
std::string Utils::Obs::StringHelper::GetCurrentSceneCollection()
{
char *sceneCollectionName = obs_frontend_get_current_scene_collection();
std::string ret = sceneCollectionName;
bfree(sceneCollectionName);
return ret;
}
std::string Utils::Obs::StringHelper::GetCurrentProfile()
{
char *profileName = obs_frontend_get_current_profile();
std::string ret = profileName;
bfree(profileName);
return ret;
}
std::string Utils::Obs::StringHelper::GetCurrentProfilePath()
{
char *profilePath = obs_frontend_get_current_profile_path();
std::string ret = profilePath;
bfree(profilePath);
return ret;
}
std::string Utils::Obs::StringHelper::GetCurrentRecordOutputPath()
{
char *recordOutputPath = obs_frontend_get_current_record_output_path();
std::string ret = recordOutputPath;
bfree(recordOutputPath);
return ret;
}
std::string Utils::Obs::StringHelper::GetSourceType(obs_source_t *source)
{
obs_source_type sourceType = obs_source_get_type(source);
switch (sourceType) {
default:
CASE(OBS_SOURCE_TYPE_INPUT)
CASE(OBS_SOURCE_TYPE_FILTER)
CASE(OBS_SOURCE_TYPE_TRANSITION)
CASE(OBS_SOURCE_TYPE_SCENE)
}
}
std::string Utils::Obs::StringHelper::GetInputMonitorType(enum obs_monitoring_type monitorType)
{
switch (monitorType) {
default:
CASE(OBS_MONITORING_TYPE_NONE)
CASE(OBS_MONITORING_TYPE_MONITOR_ONLY)
CASE(OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT)
}
}
std::string Utils::Obs::StringHelper::GetInputMonitorType(obs_source_t *input)
{
obs_monitoring_type monitorType = obs_source_get_monitoring_type(input);
return GetInputMonitorType(monitorType);
}
std::string Utils::Obs::StringHelper::GetMediaInputState(obs_source_t *input)
{
obs_media_state mediaState = obs_source_media_get_state(input);
switch (mediaState) {
default:
CASE(OBS_MEDIA_STATE_NONE)
CASE(OBS_MEDIA_STATE_PLAYING)
CASE(OBS_MEDIA_STATE_OPENING)
CASE(OBS_MEDIA_STATE_BUFFERING)
CASE(OBS_MEDIA_STATE_PAUSED)
CASE(OBS_MEDIA_STATE_STOPPED)
CASE(OBS_MEDIA_STATE_ENDED)
CASE(OBS_MEDIA_STATE_ERROR)
}
}
std::string Utils::Obs::StringHelper::GetLastReplayBufferFilePath()
{
OBSOutputAutoRelease output = obs_frontend_get_replay_buffer_output();
if (!output)
return "";
calldata_t cd = {0};
proc_handler_t *ph = obs_output_get_proc_handler(output);
proc_handler_call(ph, "get_last_replay", &cd);
const char *savedReplayPath = calldata_string(&cd, "path");
calldata_free(&cd);
if (!savedReplayPath)
return "";
return savedReplayPath;
}
std::string Utils::Obs::StringHelper::GetSceneItemBoundsType(enum obs_bounds_type type)
{
switch (type) {
default:
CASE(OBS_BOUNDS_NONE)
CASE(OBS_BOUNDS_STRETCH)
CASE(OBS_BOUNDS_SCALE_INNER)
CASE(OBS_BOUNDS_SCALE_OUTER)
CASE(OBS_BOUNDS_SCALE_TO_WIDTH)
CASE(OBS_BOUNDS_SCALE_TO_HEIGHT)
CASE(OBS_BOUNDS_MAX_ONLY)
}
}
std::string Utils::Obs::StringHelper::GetSceneItemBlendMode(enum obs_blending_type mode)
{
switch (mode) {
default:
CASE(OBS_BLEND_NORMAL)
CASE(OBS_BLEND_ADDITIVE)
CASE(OBS_BLEND_SUBTRACT)
CASE(OBS_BLEND_SCREEN)
CASE(OBS_BLEND_MULTIPLY)
CASE(OBS_BLEND_LIGHTEN)
CASE(OBS_BLEND_DARKEN)
}
}
std::string Utils::Obs::StringHelper::DurationToTimecode(uint64_t ms)
{
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;
QString formatted = QString::asprintf("%02" PRIu64 ":%02" PRIu64 ":%02" PRIu64 ".%03" PRIu64, hoursPart, minutesPart, secsPart, msPart);
return formatted.toStdString();
}
std::string Utils::Obs::StringHelper::GetOutputState(ObsOutputState state)
{
switch (state) {
default:
CASE(OBS_WEBSOCKET_OUTPUT_UNKNOWN)
CASE(OBS_WEBSOCKET_OUTPUT_STARTING)
CASE(OBS_WEBSOCKET_OUTPUT_STARTED)
CASE(OBS_WEBSOCKET_OUTPUT_STOPPING)
CASE(OBS_WEBSOCKET_OUTPUT_STOPPED)
CASE(OBS_WEBSOCKET_OUTPUT_PAUSED)
CASE(OBS_WEBSOCKET_OUTPUT_RESUMED)
}
}

View File

@ -1,355 +1,353 @@
/*
obs-websocket
Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de>
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@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 <cmath>
#include <algorithm>
#include "Obs.h"
#include "ObsVolumeMeter.h"
#include "ObsVolumeMeter_Helpers.h"
Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input) :
PeakMeterType(SAMPLE_PEAK_METER),
_input(obs_source_get_weak_source(input)),
_channels(0),
_lastUpdate(0),
_volume(obs_source_get_volume(input))
{
signal_handler_t *sh = obs_source_get_signal_handler(input);
signal_handler_connect(sh, "volume", Meter::InputVolumeCallback, this);
obs_source_add_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this);
blog_debug("[Utils::Obs::VolumeMeter::Meter::Meter] Meter created for input: %s", obs_source_get_name(input));
}
Utils::Obs::VolumeMeter::Meter::~Meter()
{
OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
if (!input) {
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::~Meter] Failed to get strong reference to input. Has it been destroyed?");
return;
}
signal_handler_t *sh = obs_source_get_signal_handler(input);
signal_handler_disconnect(sh, "volume", Meter::InputVolumeCallback, this);
obs_source_remove_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this);
blog_debug("[Utils::Obs::VolumeMeter::Meter::~Meter] Meter destroyed for input: %s", obs_source_get_name(input));
}
bool Utils::Obs::VolumeMeter::Meter::InputValid()
{
// return !obs_weak_source_expired(_input);
return true;
}
json Utils::Obs::VolumeMeter::Meter::GetMeterData()
{
json ret;
OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
if (!input) {
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::GetMeterData] Failed to get strong reference to input. Has it been destroyed?");
return ret;
}
std::vector<std::vector<float>> levels;
const float volume = _muted ? 0.0f : _volume.load();
std::unique_lock<std::mutex> l(_mutex);
if (_lastUpdate != 0 && (os_gettime_ns() - _lastUpdate) * 0.000000001 > 0.3)
ResetAudioLevels();
for (int channel = 0; channel < _channels; channel++) {
std::vector<float> level;
level.push_back(_magnitude[channel] * volume);
level.push_back(_peak[channel] * volume);
level.push_back(_peak[channel]);
levels.push_back(level);
}
l.unlock();
ret["inputName"] = obs_source_get_name(input);
ret["inputLevelsMul"] = levels;
return ret;
}
// MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ResetAudioLevels()
{
_lastUpdate = 0;
for (int channelNumber = 0; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++) {
_magnitude[channelNumber] = 0;
_peak[channelNumber] = 0;
}
}
// MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ProcessAudioChannels(const struct audio_data *data)
{
int channels = 0;
for (int i = 0; i < MAX_AV_PLANES; i++) {
if (data->data[i])
channels++;
}
bool channelsChanged = _channels != channels;
_channels = std::clamp(channels, 0, MAX_AUDIO_CHANNELS);
if (channelsChanged)
ResetAudioLevels();
}
// MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data)
{
size_t sampleCount = data->frames;
int channelNumber = 0;
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
float *samples = (float*)data->data[planeNumber];
if (!samples)
continue;
if (((uintptr_t)samples & 0xf) > 0) {
_peak[channelNumber] = 1.0f;
channelNumber++;
continue;
}
__m128 previousSamples = _mm_loadu_ps(_previousSamples[channelNumber]);
float peak;
switch (PeakMeterType) {
default:
case SAMPLE_PEAK_METER:
peak = GetSamplePeak(previousSamples, samples, sampleCount);
break;
case TRUE_PEAK_METER:
peak = GetTruePeak(previousSamples, samples, sampleCount);
break;
}
switch (sampleCount) {
case 0:
break;
case 1:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][1];
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][2];
_previousSamples[channelNumber][2] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
break;
case 2:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][2];
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
break;
case 3:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][1] = samples[sampleCount - 3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
break;
default:
_previousSamples[channelNumber][0] = samples[sampleCount - 4];
_previousSamples[channelNumber][1] = samples[sampleCount - 3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
}
_peak[channelNumber] = peak;
channelNumber++;
}
for (; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++)
_peak[channelNumber] = 0.0;
}
// MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(const struct audio_data *data)
{
size_t sampleCount = data->frames;
int channelNumber = 0;
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
float *samples = (float*)data->data[planeNumber];
if (!samples)
continue;
float sum = 0.0;
for (size_t i = 0; i < sampleCount; i++) {
float sample = samples[i];
sum += sample * sample;
}
_magnitude[channelNumber] = std::sqrt(sum / sampleCount);
channelNumber++;
}
}
void Utils::Obs::VolumeMeter::Meter::InputAudioCaptureCallback(void *priv_data, obs_source_t *, const struct audio_data *data, bool muted)
{
auto c = static_cast<Meter*>(priv_data);
std::unique_lock<std::mutex> l(c->_mutex);
c->_muted = muted;
c->ProcessAudioChannels(data);
c->ProcessPeak(data);
c->ProcessMagnitude(data);
c->_lastUpdate = os_gettime_ns();
}
void Utils::Obs::VolumeMeter::Meter::InputVolumeCallback(void *priv_data, calldata_t *cd)
{
auto c = static_cast<Meter*>(priv_data);
c->_volume = (float)calldata_float(cd, "volume");
}
Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod) :
_updateCallback(cb),
_updatePeriod(updatePeriod),
_running(false)
{
signal_handler_t *sh = obs_get_signal_handler();
if (!sh)
return;
auto enumProc = [](void *priv_data, obs_source_t *input) {
auto c = static_cast<Handler*>(priv_data);
if (!obs_source_active(input))
return true;
uint32_t flags = obs_source_get_output_flags(input);
if ((flags & OBS_SOURCE_AUDIO) == 0)
return true;
c->_meters.emplace_back(std::move(new Meter(input)));
return true;
};
obs_enum_sources(enumProc, this);
signal_handler_connect(sh, "source_activate", Handler::InputActivateCallback, this);
signal_handler_connect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
_running = true;
_updateThread = std::thread(&Handler::UpdateThread, this);
blog_debug("[Utils::Obs::VolumeMeter::Handler::Handler] Handler created.");
}
Utils::Obs::VolumeMeter::Handler::~Handler()
{
signal_handler_t *sh = obs_get_signal_handler();
if (!sh)
return;
signal_handler_disconnect(sh, "source_activate", Handler::InputActivateCallback, this);
signal_handler_disconnect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
if (_running) {
_mutex.lock();
_running = false;
_mutex.unlock();
_cond.notify_all();
}
if (_updateThread.joinable())
_updateThread.join();
blog_debug("[Utils::Obs::VolumeMeter::Handler::~Handler] Handler destroyed.");
}
void Utils::Obs::VolumeMeter::Handler::UpdateThread()
{
blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread started.");
while (_running) {
{
std::unique_lock<std::mutex> l(_mutex);
if (_cond.wait_for(l, std::chrono::milliseconds(_updatePeriod), [this]{ return !_running; }))
break;
}
std::vector<json> inputs;
std::unique_lock<std::mutex> l(_meterMutex);
for (auto &meter : _meters) {
if (meter->InputValid())
inputs.push_back(meter->GetMeterData());
}
l.unlock();
if (_updateCallback)
_updateCallback(inputs);
}
blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread stopped.");
}
void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data, calldata_t *cd)
{
auto c = static_cast<Handler*>(priv_data);
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
if (!input)
return;
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
return;
uint32_t flags = obs_source_get_output_flags(input);
if ((flags & OBS_SOURCE_AUDIO) == 0)
return;
std::unique_lock<std::mutex> l(c->_meterMutex);
c->_meters.emplace_back(std::move(new Meter(input)));
}
void Utils::Obs::VolumeMeter::Handler::InputDeactivateCallback(void *priv_data, calldata_t *cd)
{
auto c = static_cast<Handler*>(priv_data);
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
if (!input)
return;
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
return;
// Don't ask me why, but using std::remove_if segfaults trying this.
std::unique_lock<std::mutex> l(c->_meterMutex);
std::vector<MeterPtr>::iterator iter;
for (iter = c->_meters.begin(); iter != c->_meters.end();) {
if (obs_weak_source_references_source(iter->get()->GetWeakInput(), input))
iter = c->_meters.erase(iter);
else
++iter;
}
}
/*
obs-websocket
Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de>
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@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 <cmath>
#include <algorithm>
#include "Obs.h"
#include "Obs_VolumeMeter.h"
#include "Obs_VolumeMeter_Helpers.h"
#include "../obs-websocket.h"
Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input) :
PeakMeterType(SAMPLE_PEAK_METER),
_input(obs_source_get_weak_source(input)),
_channels(0),
_lastUpdate(0),
_volume(obs_source_get_volume(input))
{
signal_handler_t *sh = obs_source_get_signal_handler(input);
signal_handler_connect(sh, "volume", Meter::InputVolumeCallback, this);
obs_source_add_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this);
blog_debug("[Utils::Obs::VolumeMeter::Meter::Meter] Meter created for input: %s", obs_source_get_name(input));
}
Utils::Obs::VolumeMeter::Meter::~Meter()
{
OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
if (!input) {
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::~Meter] Failed to get strong reference to input. Has it been destroyed?");
return;
}
signal_handler_t *sh = obs_source_get_signal_handler(input);
signal_handler_disconnect(sh, "volume", Meter::InputVolumeCallback, this);
obs_source_remove_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this);
blog_debug("[Utils::Obs::VolumeMeter::Meter::~Meter] Meter destroyed for input: %s", obs_source_get_name(input));
}
bool Utils::Obs::VolumeMeter::Meter::InputValid()
{
return !obs_weak_source_expired(_input);
}
json Utils::Obs::VolumeMeter::Meter::GetMeterData()
{
json ret;
OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
if (!input) {
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::GetMeterData] Failed to get strong reference to input. Has it been destroyed?");
return ret;
}
std::vector<std::vector<float>> levels;
const float volume = _muted ? 0.0f : _volume.load();
std::unique_lock<std::mutex> l(_mutex);
if (_lastUpdate != 0 && (os_gettime_ns() - _lastUpdate) * 0.000000001 > 0.3)
ResetAudioLevels();
for (int channel = 0; channel < _channels; channel++) {
std::vector<float> level;
level.push_back(_magnitude[channel] * volume);
level.push_back(_peak[channel] * volume);
level.push_back(_peak[channel]);
levels.push_back(level);
}
l.unlock();
ret["inputName"] = obs_source_get_name(input);
ret["inputLevelsMul"] = levels;
return ret;
}
// MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ResetAudioLevels()
{
_lastUpdate = 0;
for (int channelNumber = 0; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++) {
_magnitude[channelNumber] = 0;
_peak[channelNumber] = 0;
}
}
// MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ProcessAudioChannels(const struct audio_data *data)
{
int channels = 0;
for (int i = 0; i < MAX_AV_PLANES; i++) {
if (data->data[i])
channels++;
}
bool channelsChanged = _channels != channels;
_channels = std::clamp(channels, 0, MAX_AUDIO_CHANNELS);
if (channelsChanged)
ResetAudioLevels();
}
// MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data)
{
size_t sampleCount = data->frames;
int channelNumber = 0;
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
float *samples = (float*)data->data[planeNumber];
if (!samples)
continue;
if (((uintptr_t)samples & 0xf) > 0) {
_peak[channelNumber] = 1.0f;
channelNumber++;
continue;
}
__m128 previousSamples = _mm_loadu_ps(_previousSamples[channelNumber]);
float peak;
switch (PeakMeterType) {
default:
case SAMPLE_PEAK_METER:
peak = GetSamplePeak(previousSamples, samples, sampleCount);
break;
case TRUE_PEAK_METER:
peak = GetTruePeak(previousSamples, samples, sampleCount);
break;
}
switch (sampleCount) {
case 0:
break;
case 1:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][1];
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][2];
_previousSamples[channelNumber][2] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
break;
case 2:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][2];
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
break;
case 3:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][1] = samples[sampleCount - 3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
break;
default:
_previousSamples[channelNumber][0] = samples[sampleCount - 4];
_previousSamples[channelNumber][1] = samples[sampleCount - 3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
}
_peak[channelNumber] = peak;
channelNumber++;
}
for (; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++)
_peak[channelNumber] = 0.0;
}
// MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(const struct audio_data *data)
{
size_t sampleCount = data->frames;
int channelNumber = 0;
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
float *samples = (float*)data->data[planeNumber];
if (!samples)
continue;
float sum = 0.0;
for (size_t i = 0; i < sampleCount; i++) {
float sample = samples[i];
sum += sample * sample;
}
_magnitude[channelNumber] = std::sqrt(sum / sampleCount);
channelNumber++;
}
}
void Utils::Obs::VolumeMeter::Meter::InputAudioCaptureCallback(void *priv_data, obs_source_t *, const struct audio_data *data, bool muted)
{
auto c = static_cast<Meter*>(priv_data);
std::unique_lock<std::mutex> l(c->_mutex);
c->_muted = muted;
c->ProcessAudioChannels(data);
c->ProcessPeak(data);
c->ProcessMagnitude(data);
c->_lastUpdate = os_gettime_ns();
}
void Utils::Obs::VolumeMeter::Meter::InputVolumeCallback(void *priv_data, calldata_t *cd)
{
auto c = static_cast<Meter*>(priv_data);
c->_volume = (float)calldata_float(cd, "volume");
}
Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod) :
_updateCallback(cb),
_updatePeriod(updatePeriod),
_running(false)
{
signal_handler_t *sh = obs_get_signal_handler();
if (!sh)
return;
auto enumProc = [](void *priv_data, obs_source_t *input) {
auto c = static_cast<Handler*>(priv_data);
if (!obs_source_active(input))
return true;
uint32_t flags = obs_source_get_output_flags(input);
if ((flags & OBS_SOURCE_AUDIO) == 0)
return true;
c->_meters.emplace_back(std::move(new Meter(input)));
return true;
};
obs_enum_sources(enumProc, this);
signal_handler_connect(sh, "source_activate", Handler::InputActivateCallback, this);
signal_handler_connect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
_running = true;
_updateThread = std::thread(&Handler::UpdateThread, this);
blog_debug("[Utils::Obs::VolumeMeter::Handler::Handler] Handler created.");
}
Utils::Obs::VolumeMeter::Handler::~Handler()
{
signal_handler_t *sh = obs_get_signal_handler();
if (!sh)
return;
signal_handler_disconnect(sh, "source_activate", Handler::InputActivateCallback, this);
signal_handler_disconnect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
if (_running) {
_running = false;
_cond.notify_all();
}
if (_updateThread.joinable())
_updateThread.join();
blog_debug("[Utils::Obs::VolumeMeter::Handler::~Handler] Handler destroyed.");
}
void Utils::Obs::VolumeMeter::Handler::UpdateThread()
{
blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread started.");
while (_running) {
{
std::unique_lock<std::mutex> l(_mutex);
if (_cond.wait_for(l, std::chrono::milliseconds(_updatePeriod), [this]{ return !_running; }))
break;
}
std::vector<json> inputs;
std::unique_lock<std::mutex> l(_meterMutex);
for (auto &meter : _meters) {
if (meter->InputValid())
inputs.push_back(meter->GetMeterData());
}
l.unlock();
if (_updateCallback)
_updateCallback(inputs);
}
blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread stopped.");
}
void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data, calldata_t *cd)
{
auto c = static_cast<Handler*>(priv_data);
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
if (!input)
return;
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
return;
uint32_t flags = obs_source_get_output_flags(input);
if ((flags & OBS_SOURCE_AUDIO) == 0)
return;
std::unique_lock<std::mutex> l(c->_meterMutex);
c->_meters.emplace_back(std::move(new Meter(input)));
}
void Utils::Obs::VolumeMeter::Handler::InputDeactivateCallback(void *priv_data, calldata_t *cd)
{
auto c = static_cast<Handler*>(priv_data);
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
if (!input)
return;
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
return;
// Don't ask me why, but using std::remove_if segfaults trying this.
std::unique_lock<std::mutex> l(c->_meterMutex);
std::vector<MeterPtr>::iterator iter;
for (iter = c->_meters.begin(); iter != c->_meters.end();) {
if (obs_weak_source_references_source(iter->get()->GetWeakInput(), input))
iter = c->_meters.erase(iter);
else
++iter;
}
}

View File

@ -1,99 +1,99 @@
/*
obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@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 <atomic>
#include <mutex>
#include <condition_variable>
#include <memory>
#include <thread>
#include <obs.hpp>
#include "../obs-websocket.h"
#include "Json.h"
namespace Utils {
namespace Obs {
namespace VolumeMeter {
// Some code copied from https://github.com/obsproject/obs-studio/blob/master/libobs/obs-audio-controls.c
// Keeps a running tally of the current audio levels, for a specific input
class Meter {
public:
Meter(obs_source_t *input);
~Meter();
bool InputValid();
obs_weak_source_t *GetWeakInput() { return _input; }
json GetMeterData();
std::atomic<enum obs_peak_meter_type> PeakMeterType;
private:
OBSWeakSourceAutoRelease _input;
// All values in mul
std::mutex _mutex;
bool _muted;
int _channels;
float _magnitude[MAX_AUDIO_CHANNELS];
float _peak[MAX_AUDIO_CHANNELS];
float _previousSamples[MAX_AUDIO_CHANNELS][4];
std::atomic<uint64_t> _lastUpdate;
std::atomic<float> _volume;
void ResetAudioLevels();
void ProcessAudioChannels(const struct audio_data *data);
void ProcessPeak(const struct audio_data *data);
void ProcessMagnitude(const struct audio_data *data);
static void InputAudioCaptureCallback(void *priv_data, obs_source_t *source, const struct audio_data *data, bool muted);
static void InputVolumeCallback(void *priv_data, calldata_t *cd);
};
// Maintains an array of active inputs
class Handler {
typedef std::function<void(std::vector<json>)> UpdateCallback;
typedef std::unique_ptr<Meter> MeterPtr;
public:
Handler(UpdateCallback cb, uint64_t updatePeriod = 50);
~Handler();
private:
UpdateCallback _updateCallback;
std::mutex _meterMutex;
std::vector<MeterPtr> _meters;
uint64_t _updatePeriod;
std::mutex _mutex;
std::condition_variable _cond;
bool _running;
std::thread _updateThread;
void UpdateThread();
static void InputActivateCallback(void *priv_data, calldata_t *cd);
static void InputDeactivateCallback(void *priv_data, calldata_t *cd);
};
}
}
}
/*
obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@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 <atomic>
#include <mutex>
#include <condition_variable>
#include <memory>
#include <thread>
#include <obs.hpp>
#include "Obs.h"
#include "Json.h"
namespace Utils {
namespace Obs {
namespace VolumeMeter {
// Some code copied from https://github.com/obsproject/obs-studio/blob/master/libobs/obs-audio-controls.c
// Keeps a running tally of the current audio levels, for a specific input
class Meter {
public:
Meter(obs_source_t *input);
~Meter();
bool InputValid();
obs_weak_source_t *GetWeakInput() { return _input; }
json GetMeterData();
std::atomic<enum obs_peak_meter_type> PeakMeterType;
private:
OBSWeakSourceAutoRelease _input;
// All values in mul
std::mutex _mutex;
bool _muted;
int _channels;
float _magnitude[MAX_AUDIO_CHANNELS];
float _peak[MAX_AUDIO_CHANNELS];
float _previousSamples[MAX_AUDIO_CHANNELS][4];
std::atomic<uint64_t> _lastUpdate;
std::atomic<float> _volume;
void ResetAudioLevels();
void ProcessAudioChannels(const struct audio_data *data);
void ProcessPeak(const struct audio_data *data);
void ProcessMagnitude(const struct audio_data *data);
static void InputAudioCaptureCallback(void *priv_data, obs_source_t *source, const struct audio_data *data, bool muted);
static void InputVolumeCallback(void *priv_data, calldata_t *cd);
};
// Maintains an array of active inputs
class Handler {
typedef std::function<void(std::vector<json>)> UpdateCallback;
typedef std::unique_ptr<Meter> MeterPtr;
public:
Handler(UpdateCallback cb, uint64_t updatePeriod = 50);
~Handler();
private:
UpdateCallback _updateCallback;
std::mutex _meterMutex;
std::vector<MeterPtr> _meters;
uint64_t _updatePeriod;
std::mutex _mutex;
std::condition_variable _cond;
std::atomic<bool> _running;
std::thread _updateThread;
void UpdateThread();
static void InputActivateCallback(void *priv_data, calldata_t *cd);
static void InputDeactivateCallback(void *priv_data, calldata_t *cd);
};
}
}
}

View File

@ -56,7 +56,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
r = fmaxf(r, x4_mem[3]); \
} while (false)
float GetSamplePeak(__m128 previousSamples, const float *samples, size_t sampleCount)
static float GetSamplePeak(__m128 previousSamples, const float *samples, size_t sampleCount)
{
__m128 peak = previousSamples;
for (size_t i = 0; (i + 3) < sampleCount; i += 4) {
@ -69,7 +69,7 @@ float GetSamplePeak(__m128 previousSamples, const float *samples, size_t sampleC
return ret;
}
float GetTruePeak(__m128 previousSamples, const float *samples, size_t sampleCount)
static float GetTruePeak(__m128 previousSamples, const float *samples, size_t sampleCount)
{
const __m128 m3 = _mm_set_ps(-0.155915f, 0.935489f, 0.233872f, -0.103943f);
const __m128 m1 = _mm_set_ps(-0.216236f, 0.756827f, 0.504551f, -0.189207f);

View File

@ -111,9 +111,9 @@ void Utils::Platform::SendTrayNotification(QSystemTrayIcon::MessageIcon icon, QS
obs_queue_task(OBS_TASK_UI, [](void* param) {
void *systemTrayPtr = obs_frontend_get_system_tray();
auto systemTray = reinterpret_cast<QSystemTrayIcon*>(systemTrayPtr);
auto systemTray = static_cast<QSystemTrayIcon*>(systemTrayPtr);
auto notification = reinterpret_cast<SystemTrayNotification*>(param);
auto notification = static_cast<SystemTrayNotification*>(param);
systemTray->showMessage(notification->title, notification->body, notification->icon);
delete notification;
}, (void*)notification, false);

View File

@ -22,6 +22,6 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Crypto.h"
#include "Json.h"
#include "Obs.h"
#include "ObsVolumeMeter.h"
#include "Obs_VolumeMeter.h"
#include "Platform.h"
#include "Compat.h"

View File

@ -23,6 +23,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <QObject>
#include <QThreadPool>
#include <QString>
#include <asio.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>

View File

@ -233,7 +233,7 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
return;
}
uint8_t requestedExecutionType = payloadData["executionType"];
int8_t requestedExecutionType = payloadData["executionType"];
if (!RequestBatchExecutionType::IsValid(requestedExecutionType) || requestedExecutionType == RequestBatchExecutionType::None) {
ret.closeCode = WebSocketCloseCode::InvalidDataFieldValue;
ret.closeReason = "Your `executionType` has an invalid value.";
@ -296,7 +296,7 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
} return;
default:
ret.closeCode = WebSocketCloseCode::UnknownOpCode;
ret.closeReason = std::string("Unknown OpCode: %s") + std::to_string(opCode);
ret.closeReason = std::string("Unknown OpCode: ") + std::to_string(opCode);
return;
}
}