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 label: obs-websocket Version
description: What version of obs-websocket are you using? description: What version of obs-websocket are you using?
options: options:
- 5.0.0-alpha3
- 5.0.0-alpha2 - 5.0.0-alpha2
- 5.0.0-alpha1
- 4.9.1 - 4.9.1
- 4.9.0 - 4.9.0
- Git - Git

View File

@ -1,4 +1,4 @@
name: "CI Multiplatform Build" name: 'CI Multiplatform Build'
on: on:
push: push:
@ -18,15 +18,15 @@ on:
jobs: jobs:
windows: windows:
name: 'Windows 32/64-bit' name: 'Windows 32/64-bit'
runs-on: [windows-latest] runs-on: [windows-2019]
if: contains(github.event.head_commit.message, '[skip ci]') != true if: contains(github.event.head_commit.message, '[skip ci]') != true
env: env:
QT_CACHE_VERSION: '2' # Change whenever updating OBS dependencies URL, in order to force a cache reset QT_CACHE_VERSION: '2' # Change whenever updating OBS dependencies URL, in order to force a cache reset
QT_VERSION: '5.15.2' 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_CACHE_VERSION: '1' # Change whenever updating Qt dependency URL, in order to force a cache reset
WINDOWS_DEPS_VERSION: '2019' WINDOWS_DEPS_VERSION: '2019'
CMAKE_GENERATOR: "Visual Studio 16 2019" CMAKE_GENERATOR: 'Visual Studio 16 2019'
CMAKE_SYSTEM_VERSION: "10.0" CMAKE_SYSTEM_VERSION: '10.0'
steps: steps:
- name: 'Add msbuild to PATH' - name: 'Add msbuild to PATH'
uses: microsoft/setup-msbuild@v1.0.2 uses: microsoft/setup-msbuild@v1.0.2
@ -35,20 +35,20 @@ jobs:
with: with:
path: ${{ github.workspace }}/obs-websocket path: ${{ github.workspace }}/obs-websocket
submodules: 'recursive' submodules: 'recursive'
- name: 'Checkout OBS-Studio' - name: 'Checkout OBS Studio'
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
repository: obsproject/obs-studio repository: obsproject/obs-studio
path: ${{ github.workspace }}/obs-studio path: ${{ github.workspace }}/obs-studio
submodules: 'recursive' submodules: 'recursive'
- name: 'Get OBS-Studio Git Info' - name: 'Get OBS Studio Git Info'
shell: bash shell: bash
working-directory: ${{ github.workspace }}/obs-studio working-directory: ${{ github.workspace }}/obs-studio
run: | run: |
git fetch --prune --unshallow git fetch --prune --unshallow
echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $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 shell: bash
working-directory: ${{ github.workspace }}/obs-studio working-directory: ${{ github.workspace }}/obs-studio
run: | run: |
@ -78,8 +78,6 @@ jobs:
with: with:
path: Qt_${{ env.QT_VERSION }}.7z path: Qt_${{ env.QT_VERSION }}.7z
key: 'qtdep-${{ env.QT_CACHE_VERSION }} | ${{ runner.os }}' key: 'qtdep-${{ env.QT_CACHE_VERSION }} | ${{ runner.os }}'
restore-keys: |
qtdep-${{ env.QT_CACHE_VERSION }} | ${{ runner.os }}
- name: 'Download Prerequisite: Qt' - name: 'Download Prerequisite: Qt'
if: steps.qtcache.outputs.cache-hit != 'true' if: steps.qtcache.outputs.cache-hit != 'true'
run: | run: |
@ -87,20 +85,18 @@ jobs:
- name: 'Extract Prerequisite: Qt' - name: 'Extract Prerequisite: Qt'
run: | run: |
7z x Qt_${{ env.QT_VERSION }}.7z -o"${{ github.workspace }}\cmbuild\QT" 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 id: obscache
uses: actions/cache@v2 uses: actions/cache@v2
with: with:
path: ${{ github.workspace }}\cmbuild\deps\** path: ${{ github.workspace }}\cmbuild\deps\**
key: 'obsdep-${{ env.WINDOWS_DEPS_CACHE_VERSION }} | ${{ runner.os }}' key: 'obsdep-${{ env.WINDOWS_DEPS_CACHE_VERSION }} | ${{ runner.os }}'
restore-keys: | - name: 'Install Prerequisite: Pre-built OBS Studio dependencies'
obsdep-${{ env.WINDOWS_DEPS_CACHE_VERSION }} | ${{ runner.os }}
- name: 'Install Prerequisite: Pre-built OBS-Studio dependencies'
if: steps.obscache.outputs.cache-hit != 'true' if: steps.obscache.outputs.cache-hit != 'true'
run: | run: |
curl -kLO https://cdn-fastly.obsproject.com/downloads/dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -f --retry 5 -C - 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" 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 id: build-cache-obs-32
uses: actions/cache@v2 uses: actions/cache@v2
env: env:
@ -108,22 +104,20 @@ jobs:
with: with:
path: ${{ github.workspace }}/obs-studio/build32 path: ${{ github.workspace }}/obs-studio/build32
key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }} key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }}
restore-keys: | - name: 'Configure OBS Studio 32-bit'
${{ runner.os }}-${{ env.CACHE_NAME }}-
- name: 'Configure OBS-Studio 32-bit'
if: steps.build-cache-obs-32.outputs.cache-hit != 'true' if: steps.build-cache-obs-32.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio working-directory: ${{ github.workspace }}/obs-studio
run: | run: |
if(!(Test-Path -Path ".\build32")){New-Item -ItemType directory -Path .\build32} if(!(Test-Path -Path ".\build32")){New-Item -ItemType directory -Path .\build32}
cd .\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 .. 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' if: steps.build-cache-obs-32.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio working-directory: ${{ github.workspace }}/obs-studio
run: | run: |
msbuild /m /p:Configuration=RelWithDebInfo .\build32\libobs\libobs.vcxproj msbuild /m /p:Configuration=RelWithDebInfo .\build32\libobs\libobs.vcxproj
msbuild /m /p:Configuration=RelWithDebInfo .\build32\UI\obs-frontend-api\obs-frontend-api.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 id: build-cache-obs-64
uses: actions/cache@v1 uses: actions/cache@v1
env: env:
@ -131,16 +125,14 @@ jobs:
with: with:
path: ${{ github.workspace }}/obs-studio/build64 path: ${{ github.workspace }}/obs-studio/build64
key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }} key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }}
restore-keys: | - name: 'Configure OBS Studio 64-bit'
${{ runner.os }}-${{ env.CACHE_NAME }}-
- name: 'Configure OBS-Studio 64-bit'
if: steps.build-cache-obs-64.outputs.cache-hit != 'true' if: steps.build-cache-obs-64.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio working-directory: ${{ github.workspace }}/obs-studio
run: | run: |
if(!(Test-Path -Path ".\build64")){New-Item -ItemType directory -Path .\build64} if(!(Test-Path -Path ".\build64")){New-Item -ItemType directory -Path .\build64}
cd .\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 .. 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' if: steps.build-cache-obs-64.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio working-directory: ${{ github.workspace }}/obs-studio
run: | run: |
@ -188,7 +180,7 @@ jobs:
name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-Windows-Installer' name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-Windows-Installer'
path: ${{ github.workspace }}/obs-websocket/package/*.exe path: ${{ github.workspace }}/obs-websocket/package/*.exe
ubuntu64: ubuntu64:
name: "Linux/Ubuntu 64-bit" name: 'Linux/Ubuntu 64-bit'
runs-on: [ubuntu-latest] runs-on: [ubuntu-latest]
if: contains(github.event.head_commit.message, '[skip ci]') != true if: contains(github.event.head_commit.message, '[skip ci]') != true
steps: steps:
@ -197,20 +189,20 @@ jobs:
with: with:
path: ${{ github.workspace }}/obs-websocket path: ${{ github.workspace }}/obs-websocket
submodules: 'recursive' submodules: 'recursive'
- name: 'Checkout OBS-Studio' - name: 'Checkout OBS Studio'
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
repository: obsproject/obs-studio repository: obsproject/obs-studio
path: ${{ github.workspace }}/obs-studio path: ${{ github.workspace }}/obs-studio
submodules: 'recursive' submodules: 'recursive'
- name: 'Get OBS-Studio Git Info' - name: 'Get OBS Studio Git Info'
shell: bash shell: bash
working-directory: ${{ github.workspace }}/obs-studio working-directory: ${{ github.workspace }}/obs-studio
run: | run: |
git fetch --prune --unshallow git fetch --prune --unshallow
echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $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 shell: bash
working-directory: ${{ github.workspace }}/obs-studio working-directory: ${{ github.workspace }}/obs-studio
run: | run: |
@ -284,21 +276,21 @@ jobs:
libx11-xcb-dev \ libx11-xcb-dev \
libxcb1-dev \ libxcb1-dev \
libxss-dev \ libxss-dev \
- name: 'Configure OBS-Studio' - name: 'Configure OBS Studio'
working-directory: ${{ github.workspace }}/obs-studio working-directory: ${{ github.workspace }}/obs-studio
shell: bash shell: bash
run: | run: |
mkdir ./build mkdir ./build
cd ./build cd ./build
cmake -DDISABLE_PLUGINS=YES -DENABLE_SCRIPTING=NO -DUNIX_STRUCTURE=YES -DCMAKE_INSTALL_PREFIX=/usr .. 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 working-directory: ${{ github.workspace }}/obs-studio
shell: bash shell: bash
run: | run: |
set -e set -e
cd ./build cd ./build
make -j4 libobs obs-frontend-api make -j4 libobs obs-frontend-api
- name: 'Install OBS-Studio' - name: 'Install OBS Studio'
working-directory: ${{ github.workspace }}/obs-studio working-directory: ${{ github.workspace }}/obs-studio
shell: bash shell: bash
run: | run: |
@ -316,7 +308,7 @@ jobs:
if [ "${{ env.GIT_TAG }}" ] ; then \ 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 .. ; \ 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 \ 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 fi
- name: 'Build obs-websocket' - name: 'Build obs-websocket'
working-directory: ${{ github.workspace }}/obs-websocket working-directory: ${{ github.workspace }}/obs-websocket
@ -355,3 +347,213 @@ jobs:
with: with:
name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-Ubuntu64' name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-Ubuntu64'
path: '${{ github.workspace }}/obs-websocket/package/*.deb' 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/ /docs/node_modules/
/src/plugin-macros.generated.h /src/plugin-macros.generated.h
/installer/installer-windows.generated.iss /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> <key>CONCLUSION_ACTION</key>
<integer>0</integer> <integer>0</integer>
<key>IDENTIFIER</key> <key>IDENTIFIER</key>
<string>fr.palakis.obs-websocket</string> <string>com.obsproject.obs-websocket</string>
<key>OVERWRITE_PERMISSIONS</key> <key>OVERWRITE_PERMISSIONS</key>
<false/> <false/>
<key>VERSION</key> <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_Outputs.cpp
src/eventhandler/EventHandler_SceneItems.cpp src/eventhandler/EventHandler_SceneItems.cpp
src/eventhandler/EventHandler_MediaInputs.cpp src/eventhandler/EventHandler_MediaInputs.cpp
src/eventhandler/EventHandler_Ui.cpp
src/requesthandler/RequestHandler.cpp src/requesthandler/RequestHandler.cpp
src/requesthandler/RequestBatchHandler.cpp src/requesthandler/RequestBatchHandler.cpp
src/requesthandler/RequestHandler_General.cpp src/requesthandler/RequestHandler_General.cpp
@ -110,10 +111,14 @@ set(obs-websocket_SOURCES
src/requesthandler/RequestHandler_Sources.cpp src/requesthandler/RequestHandler_Sources.cpp
src/requesthandler/RequestHandler_Scenes.cpp src/requesthandler/RequestHandler_Scenes.cpp
src/requesthandler/RequestHandler_Inputs.cpp src/requesthandler/RequestHandler_Inputs.cpp
src/requesthandler/RequestHandler_Transitions.cpp
src/requesthandler/RequestHandler_Filters.cpp
src/requesthandler/RequestHandler_SceneItems.cpp src/requesthandler/RequestHandler_SceneItems.cpp
src/requesthandler/RequestHandler_Outputs.cpp
src/requesthandler/RequestHandler_Stream.cpp src/requesthandler/RequestHandler_Stream.cpp
src/requesthandler/RequestHandler_Record.cpp src/requesthandler/RequestHandler_Record.cpp
src/requesthandler/RequestHandler_MediaInputs.cpp src/requesthandler/RequestHandler_MediaInputs.cpp
src/requesthandler/RequestHandler_Ui.cpp
src/requesthandler/rpc/Request.cpp src/requesthandler/rpc/Request.cpp
src/requesthandler/rpc/RequestBatchRequest.cpp src/requesthandler/rpc/RequestBatchRequest.cpp
src/requesthandler/rpc/RequestResult.cpp src/requesthandler/rpc/RequestResult.cpp
@ -123,7 +128,14 @@ set(obs-websocket_SOURCES
src/utils/Crypto.cpp src/utils/Crypto.cpp
src/utils/Json.cpp src/utils/Json.cpp
src/utils/Obs.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/Platform.cpp
src/utils/Compat.cpp src/utils/Compat.cpp
deps/qr/cpp/QrCode.cpp) deps/qr/cpp/QrCode.cpp)
@ -150,8 +162,8 @@ set(obs-websocket_HEADERS
src/utils/Crypto.h src/utils/Crypto.h
src/utils/Json.h src/utils/Json.h
src/utils/Obs.h src/utils/Obs.h
src/utils/ObsVolumeMeter.h src/utils/Obs_VolumeMeter.h
src/utils/ObsVolumeMeter_Helpers.h src/utils/Obs_VolumeMeter_Helpers.h
src/utils/Platform.h src/utils/Platform.h
src/utils/Compat.h src/utils/Compat.h
src/utils/Utils.h src/utils/Utils.h

View File

@ -8,7 +8,7 @@
WebSocket API for OBS Studio. 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) [![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) [![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 ### 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) ### 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 - 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 - 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). 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 ## Contributors

2
deps/asio vendored

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

View File

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

View File

@ -122,7 +122,7 @@ for comment in comments_raw:
enumValue = field_to_string(comment['enumValue']) enumValue = field_to_string(comment['enumValue'])
enum['enumValue'] = int(enumValue) if enumValue.isdigit() else enumValue enum['enumValue'] = int(enumValue) if enumValue.isdigit() else enumValue
else: else:
enum['enumValue'] = None enum['enumValue'] = enum['enumIdentifier']
if enumType not in enums_raw: if enumType not in enums_raw:
enums_raw[enumType] = {'enumIdentifiers': [enum]} 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> #include <obs.h>
#define OBS_WEBSOCKET_API_VERSION 1
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif

View File

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

View File

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

View File

@ -25,9 +25,11 @@ class WebSocketApi {
std::map<std::string, obs_websocket_request_callback> _requests; std::map<std::string, obs_websocket_request_callback> _requests;
}; };
WebSocketApi(EventCallback cb); WebSocketApi();
~WebSocketApi(); ~WebSocketApi();
void SetEventCallback(EventCallback cb);
enum RequestReturnCode PerformVendorRequest(std::string vendorName, std::string requestName, obs_data_t *requestData, obs_data_t *responseData); 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); 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, "hide", HandleInputShowStateChanged, this);
signal_handler_connect(sh, "mute", HandleInputMuteStateChanged, this); signal_handler_connect(sh, "mute", HandleInputMuteStateChanged, this);
signal_handler_connect(sh, "volume", HandleInputVolumeChanged, 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_sync", HandleInputAudioSyncOffsetChanged, this);
signal_handler_connect(sh, "audio_mixers", HandleInputAudioTracksChanged, 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) { if (sourceType == OBS_SOURCE_TYPE_INPUT) {
signal_handler_connect(sh, "media_started", HandleMediaInputPlaybackStarted, this); 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, "reorder", HandleSceneItemListReindexed, this);
signal_handler_connect(sh, "item_visible", HandleSceneItemEnableStateChanged, this); signal_handler_connect(sh, "item_visible", HandleSceneItemEnableStateChanged, this);
signal_handler_connect(sh, "item_locked", HandleSceneItemLockStateChanged, 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); 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, "hide", HandleInputShowStateChanged, this);
signal_handler_disconnect(sh, "mute", HandleInputMuteStateChanged, this); signal_handler_disconnect(sh, "mute", HandleInputMuteStateChanged, this);
signal_handler_disconnect(sh, "volume", HandleInputVolumeChanged, 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_sync", HandleInputAudioSyncOffsetChanged, this);
signal_handler_disconnect(sh, "audio_mixers", HandleInputAudioTracksChanged, 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_started", HandleMediaInputPlaybackStarted, this);
signal_handler_disconnect(sh, "media_ended", HandleMediaInputPlaybackEnded, this); signal_handler_disconnect(sh, "media_ended", HandleMediaInputPlaybackEnded, this);
signal_handler_disconnect(sh, "media_pause", SourceMediaPauseMultiHandler, 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_stopped", SourceMediaStopMultiHandler, this);
signal_handler_disconnect(sh, "media_next", SourceMediaNextMultiHandler, this); signal_handler_disconnect(sh, "media_next", SourceMediaNextMultiHandler, this);
signal_handler_disconnect(sh, "media_previous", SourceMediaPreviousMultiHandler, 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 // Scenes
signal_handler_disconnect(sh, "item_add", HandleSceneItemCreated, this); 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, "reorder", HandleSceneItemListReindexed, this);
signal_handler_disconnect(sh, "item_visible", HandleSceneItemEnableStateChanged, this); signal_handler_disconnect(sh, "item_visible", HandleSceneItemEnableStateChanged, this);
signal_handler_disconnect(sh, "item_locked", HandleSceneItemLockStateChanged, 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); 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) 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 (!eventHandler->_obsLoaded.load()) {
if (event == OBS_FRONTEND_EVENT_FINISHED_LOADING) { 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()` // In the case that plugins become hotloadable, this will have to go back into `EventHandler::EventHandler()`
// Enumerate inputs and connect each one // Enumerate inputs and connect each one
obs_enum_sources([](void* param, obs_source_t* source) { {
auto eventHandler = reinterpret_cast<EventHandler*>(param); auto enumInputs = [](void *param, obs_source_t *source) {
eventHandler->ConnectSourceSignals(source); auto eventHandler = static_cast<EventHandler*>(param);
return true; eventHandler->ConnectSourceSignals(source);
}, private_data);
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 // Enumerate scenes and connect each one
obs_enum_scenes([](void* param, obs_source_t* source) { {
auto eventHandler = reinterpret_cast<EventHandler*>(param); auto enumScenes = [](void *param, obs_source_t *source) {
eventHandler->ConnectSourceSignals(source); auto eventHandler = static_cast<EventHandler*>(param);
return true; eventHandler->ConnectSourceSignals(source);
}, private_data);
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."); 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()` // In the case that plugins become hotloadable, this will have to go back into `EventHandler::~EventHandler()`
// Enumerate inputs and disconnect each one // Enumerate inputs and disconnect each one
obs_enum_sources([](void* param, obs_source_t* source) { {
auto eventHandler = reinterpret_cast<EventHandler*>(param); auto enumInputs = [](void *param, obs_source_t *source) {
eventHandler->DisconnectSourceSignals(source); auto eventHandler = static_cast<EventHandler*>(param);
return true; eventHandler->DisconnectSourceSignals(source);
}, private_data);
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 // Enumerate scenes and disconnect each one
obs_enum_scenes([](void* param, obs_source_t* source) { {
auto eventHandler = reinterpret_cast<EventHandler*>(param); auto enumScenes = [](void *param, obs_source_t *source) {
eventHandler->DisconnectSourceSignals(source); auto eventHandler = static_cast<EventHandler*>(param);
return true; eventHandler->DisconnectSourceSignals(source);
}, private_data);
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."); blog_debug("[EventHandler::OnFrontendEvent] Finished.");
@ -264,18 +338,18 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
break; break;
// Config // Config
//case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING: case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING:
// eventHandler->HandleCurrentSceneCollectionChanging(); eventHandler->HandleCurrentSceneCollectionChanging();
// break; break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED: case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED:
eventHandler->HandleCurrentSceneCollectionChanged(); eventHandler->HandleCurrentSceneCollectionChanged();
break; break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED: case OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED:
eventHandler->HandleSceneCollectionListChanged(); eventHandler->HandleSceneCollectionListChanged();
break; break;
//case OBS_FRONTEND_EVENT_PROFILE_CHANGING: case OBS_FRONTEND_EVENT_PROFILE_CHANGING:
// eventHandler->HandleCurrentProfileChanging(); eventHandler->HandleCurrentProfileChanging();
// break; break;
case OBS_FRONTEND_EVENT_PROFILE_CHANGED: case OBS_FRONTEND_EVENT_PROFILE_CHANGED:
eventHandler->HandleCurrentProfileChanged(); eventHandler->HandleCurrentProfileChanged();
break; break;
@ -285,7 +359,7 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
// Scenes // Scenes
case OBS_FRONTEND_EVENT_SCENE_CHANGED: case OBS_FRONTEND_EVENT_SCENE_CHANGED:
eventHandler->HandleCurrentSceneChanged(); eventHandler->HandleCurrentProgramSceneChanged();
break; break;
case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED: case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED:
eventHandler->HandleCurrentPreviewSceneChanged(); eventHandler->HandleCurrentPreviewSceneChanged();
@ -296,10 +370,12 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
// Transitions // Transitions
case OBS_FRONTEND_EVENT_TRANSITION_CHANGED: case OBS_FRONTEND_EVENT_TRANSITION_CHANGED:
eventHandler->HandleCurrentSceneTransitionChanged();
break; break;
case OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED: case OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED:
break; break;
case OBS_FRONTEND_EVENT_TRANSITION_DURATION_CHANGED: case OBS_FRONTEND_EVENT_TRANSITION_DURATION_CHANGED:
eventHandler->HandleCurrentSceneTransitionDurationChanged();
break; break;
// Outputs // Outputs
@ -363,7 +439,7 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
// Only called for creation of a public source // Only called for creation of a public source
void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data) 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 // Don't react to signals until OBS has finished loading
if (!eventHandler->_obsLoaded.load()) if (!eventHandler->_obsLoaded.load())
@ -379,10 +455,6 @@ void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data)
case OBS_SOURCE_TYPE_INPUT: case OBS_SOURCE_TYPE_INPUT:
eventHandler->HandleInputCreated(source); eventHandler->HandleInputCreated(source);
break; break;
case OBS_SOURCE_TYPE_FILTER:
break;
case OBS_SOURCE_TYPE_TRANSITION:
break;
case OBS_SOURCE_TYPE_SCENE: case OBS_SOURCE_TYPE_SCENE:
eventHandler->HandleSceneCreated(source); eventHandler->HandleSceneCreated(source);
break; break;
@ -394,7 +466,7 @@ void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data)
// Only called for destruction of a public source // Only called for destruction of a public source
void EventHandler::SourceDestroyedMultiHandler(void *param, calldata_t *data) 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 // 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"); 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 // 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); eventHandler->HandleInputRemoved(source);
break; break;
case OBS_SOURCE_TYPE_FILTER:
break;
case OBS_SOURCE_TYPE_TRANSITION:
break;
case OBS_SOURCE_TYPE_SCENE: case OBS_SOURCE_TYPE_SCENE:
break; break;
default: default:
@ -426,7 +494,7 @@ void EventHandler::SourceDestroyedMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceRemovedMultiHandler(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()) if (!eventHandler->_obsLoaded.load())
return; return;
@ -438,10 +506,6 @@ void EventHandler::SourceRemovedMultiHandler(void *param, calldata_t *data)
switch (obs_source_get_type(source)) { switch (obs_source_get_type(source)) {
case OBS_SOURCE_TYPE_INPUT: case OBS_SOURCE_TYPE_INPUT:
break; break;
case OBS_SOURCE_TYPE_FILTER:
break;
case OBS_SOURCE_TYPE_TRANSITION:
break;
case OBS_SOURCE_TYPE_SCENE: case OBS_SOURCE_TYPE_SCENE:
// Scenes emit the `removed` signal when they are removed from OBS, as expected // Scenes emit the `removed` signal when they are removed from OBS, as expected
eventHandler->HandleSceneRemoved(source); eventHandler->HandleSceneRemoved(source);
@ -453,7 +517,7 @@ void EventHandler::SourceRemovedMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceRenamedMultiHandler(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()) if (!eventHandler->_obsLoaded.load())
return; return;
@ -471,8 +535,6 @@ void EventHandler::SourceRenamedMultiHandler(void *param, calldata_t *data)
case OBS_SOURCE_TYPE_INPUT: case OBS_SOURCE_TYPE_INPUT:
eventHandler->HandleInputNameChanged(source, oldSourceName, sourceName); eventHandler->HandleInputNameChanged(source, oldSourceName, sourceName);
break; break;
case OBS_SOURCE_TYPE_FILTER:
break;
case OBS_SOURCE_TYPE_TRANSITION: case OBS_SOURCE_TYPE_TRANSITION:
break; break;
case OBS_SOURCE_TYPE_SCENE: 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 <atomic>
#include <obs.hpp> #include <obs.hpp>
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
#include <util/platform.h>
#include "types/EventSubscription.h" #include "types/EventSubscription.h"
#include "../obs-websocket.h" #include "../obs-websocket.h"
#include "../utils/Obs.h" #include "../utils/Obs.h"
#include "../utils/ObsVolumeMeter.h" #include "../utils/Obs_VolumeMeter.h"
#include "../plugin-macros.generated.h" #include "../plugin-macros.generated.h"
class EventHandler class EventHandler
@ -59,6 +58,9 @@ class EventHandler
void ConnectSourceSignals(obs_source_t *source); void ConnectSourceSignals(obs_source_t *source);
void DisconnectSourceSignals(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); void BroadcastEvent(uint64_t requiredIntent, std::string eventType, json eventData = nullptr, uint8_t rpcVersion = 0);
// Signal handler: frontend // Signal handler: frontend
@ -95,7 +97,7 @@ class EventHandler
void HandleSceneCreated(obs_source_t *source); void HandleSceneCreated(obs_source_t *source);
void HandleSceneRemoved(obs_source_t *source); void HandleSceneRemoved(obs_source_t *source);
void HandleSceneNameChanged(obs_source_t *source, std::string oldSceneName, std::string sceneName); void HandleSceneNameChanged(obs_source_t *source, std::string oldSceneName, std::string sceneName);
void HandleCurrentSceneChanged(); void HandleCurrentProgramSceneChanged();
void HandleCurrentPreviewSceneChanged(); void HandleCurrentPreviewSceneChanged();
void HandleSceneListChanged(); void HandleSceneListChanged();
@ -108,14 +110,14 @@ class EventHandler
static void HandleInputShowStateChanged(void *param, calldata_t *data); // Direct callback static void HandleInputShowStateChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputMuteStateChanged(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 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 HandleInputAudioSyncOffsetChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputAudioTracksChanged(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 static void HandleInputAudioMonitorTypeChanged(void *param, calldata_t *data); // Direct callback
// Transitions // Transitions
void HandleTransitionCreated(obs_source_t *source); void HandleCurrentSceneTransitionChanged();
void HandleTransitionRemoved(obs_source_t *source); void HandleCurrentSceneTransitionDurationChanged();
void HandleTransitionNameChanged(obs_source_t *source, std::string oldTransitionName, std::string transitionName);
// Outputs // Outputs
void HandleStreamStateChanged(ObsOutputState state); void HandleStreamStateChanged(ObsOutputState state);
@ -130,10 +132,18 @@ class EventHandler
static void HandleSceneItemListReindexed(void *param, calldata_t *data); // Direct callback static void HandleSceneItemListReindexed(void *param, calldata_t *data); // Direct callback
static void HandleSceneItemEnableStateChanged(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 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 static void HandleSceneItemTransformChanged(void *param, calldata_t *data); // Direct callback
// Media Inputs // Media Inputs
static void HandleMediaInputPlaybackStarted(void *param, calldata_t *data); // Direct callback static void HandleMediaInputPlaybackStarted(void *param, calldata_t *data); // Direct callback
static void HandleMediaInputPlaybackEnded(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); 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() void EventHandler::HandleSceneCollectionListChanged()
{ {
json eventData; json eventData;
eventData["sceneCollections"] = Utils::Obs::ListHelper::GetSceneCollectionList(); eventData["sceneCollections"] = Utils::Obs::ArrayHelper::GetSceneCollectionList();
BroadcastEvent(EventSubscription::Config, "SceneCollectionListChanged", eventData); BroadcastEvent(EventSubscription::Config, "SceneCollectionListChanged", eventData);
} }
@ -140,6 +140,6 @@ void EventHandler::HandleCurrentProfileChanged()
void EventHandler::HandleProfileListChanged() void EventHandler::HandleProfileListChanged()
{ {
json eventData; json eventData;
eventData["profiles"] = Utils::Obs::ListHelper::GetProfileList(); eventData["profiles"] = Utils::Obs::ArrayHelper::GetProfileList();
BroadcastEvent(EventSubscription::Config, "ProfileListChanged", eventData); 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" #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"); 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" #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) void EventHandler::HandleInputCreated(obs_source_t *source)
{ {
std::string inputKind = obs_source_get_id(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); 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) void EventHandler::HandleInputRemoved(obs_source_t *source)
{ {
json eventData; json eventData;
@ -41,6 +71,20 @@ void EventHandler::HandleInputRemoved(obs_source_t *source)
BroadcastEvent(EventSubscription::Inputs, "InputRemoved", eventData); 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) void EventHandler::HandleInputNameChanged(obs_source_t *, std::string oldInputName, std::string inputName)
{ {
json eventData; json eventData;
@ -49,16 +93,25 @@ void EventHandler::HandleInputNameChanged(obs_source_t *, std::string oldInputNa
BroadcastEvent(EventSubscription::Inputs, "InputNameChanged", eventData); BroadcastEvent(EventSubscription::Inputs, "InputNameChanged", eventData);
} }
void EventHandler::HandleInputVolumeMeters(std::vector<json> inputs) /**
{ * An input's active state has changed.
json eventData; *
eventData["inputs"] = inputs; * When an input is active, it means it's being shown by the program feed.
BroadcastEvent(EventSubscription::InputVolumeMeters, "InputVolumeMeters", eventData); *
} * @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) void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data)
{ {
auto eventHandler = reinterpret_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler*>(param);
if (!eventHandler->_inputActiveStateChangedRef.load()) if (!eventHandler->_inputActiveStateChangedRef.load())
return; return;
@ -76,9 +129,25 @@ void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data)
eventHandler->BroadcastEvent(EventSubscription::InputActiveStateChanged, "InputActiveStateChanged", eventData); 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) void EventHandler::HandleInputShowStateChanged(void *param, calldata_t *data)
{ {
auto eventHandler = reinterpret_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler*>(param);
if (!eventHandler->_inputShowStateChangedRef.load()) if (!eventHandler->_inputShowStateChangedRef.load())
return; return;
@ -96,9 +165,23 @@ void EventHandler::HandleInputShowStateChanged(void *param, calldata_t *data)
eventHandler->BroadcastEvent(EventSubscription::InputShowStateChanged, "InputShowStateChanged", eventData); 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) 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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -113,9 +196,24 @@ void EventHandler::HandleInputMuteStateChanged(void *param, calldata_t *data)
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputMuteStateChanged", eventData); 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) 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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -138,9 +236,56 @@ void EventHandler::HandleInputVolumeChanged(void *param, calldata_t *data)
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputVolumeChanged", eventData); 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) 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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -157,9 +302,23 @@ void EventHandler::HandleInputAudioSyncOffsetChanged(void *param, calldata_t *da
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioSyncOffsetChanged", eventData); 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) 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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -181,9 +340,28 @@ void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data)
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioTracksChanged", eventData); 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) 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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!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"); enum obs_monitoring_type monitorType = (obs_monitoring_type)calldata_int(data, "type");
std::string monitorTypeString; std::string monitorTypeString = Utils::Obs::StringHelper::GetInputMonitorType(monitorType);
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;
}
json eventData; json eventData;
eventData["inputName"] = obs_source_get_name(source); eventData["inputName"] = obs_source_get_name(source);
eventData["monitorType"] = monitorTypeString; eventData["monitorType"] = monitorTypeString;
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioMonitorTypeChanged", eventData); 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) 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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -49,7 +49,7 @@ void EventHandler::SourceMediaPauseMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaPlayMultiHandler(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -63,7 +63,7 @@ void EventHandler::SourceMediaPlayMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaRestartMultiHandler(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -77,7 +77,7 @@ void EventHandler::SourceMediaRestartMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaStopMultiHandler(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -91,7 +91,7 @@ void EventHandler::SourceMediaStopMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaNextMultiHandler(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -105,7 +105,7 @@ void EventHandler::SourceMediaNextMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaPreviousMultiHandler(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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -117,9 +117,22 @@ void EventHandler::SourceMediaPreviousMultiHandler(void *param, calldata_t *data
eventHandler->HandleMediaInputActionTriggered(source, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS); 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) 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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -133,9 +146,22 @@ void EventHandler::HandleMediaInputPlaybackStarted(void *param, calldata_t *data
eventHandler->BroadcastEvent(EventSubscription::MediaInputs, "MediaInputPlaybackStarted", eventData); 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) 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"); obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source) if (!source)
@ -149,6 +175,20 @@ void EventHandler::HandleMediaInputPlaybackEnded(void *param, calldata_t *data)
eventHandler->BroadcastEvent(EventSubscription::MediaInputs, "MediaInputPlaybackEnded", eventData); 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) void EventHandler::HandleMediaInputActionTriggered(obs_source_t *source, ObsMediaInputAction action)
{ {
json eventData; json eventData;

View File

@ -19,21 +19,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "EventHandler.h" #include "EventHandler.h"
#define CASE(x) case x: return #x; static bool GetOutputStateActive(ObsOutputState state) {
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) {
switch(state) { switch(state) {
case OBS_WEBSOCKET_OUTPUT_STARTED: case OBS_WEBSOCKET_OUTPUT_STARTED:
case OBS_WEBSOCKET_OUTPUT_RESUMED: 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) void EventHandler::HandleStreamStateChanged(ObsOutputState state)
{ {
json eventData; json eventData;
eventData["outputActive"] = GetOutputStateActive(state); eventData["outputActive"] = GetOutputStateActive(state);
eventData["outputState"] = GetOutputStateString(state); eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state);
BroadcastEvent(EventSubscription::Outputs, "StreamStateChanged", eventData); 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) void EventHandler::HandleRecordStateChanged(ObsOutputState state)
{ {
json eventData; json eventData;
eventData["outputActive"] = GetOutputStateActive(state); eventData["outputActive"] = GetOutputStateActive(state);
eventData["outputState"] = GetOutputStateString(state); eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state);
BroadcastEvent(EventSubscription::Outputs, "RecordStateChanged", eventData); 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) void EventHandler::HandleReplayBufferStateChanged(ObsOutputState state)
{ {
json eventData; json eventData;
eventData["outputActive"] = GetOutputStateActive(state); eventData["outputActive"] = GetOutputStateActive(state);
eventData["outputState"] = GetOutputStateString(state); eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state);
BroadcastEvent(EventSubscription::Outputs, "ReplayBufferStateChanged", eventData); 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) void EventHandler::HandleVirtualcamStateChanged(ObsOutputState state)
{ {
json eventData; json eventData;
eventData["outputActive"] = GetOutputStateActive(state); eventData["outputActive"] = GetOutputStateActive(state);
eventData["outputState"] = GetOutputStateString(state); eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state);
BroadcastEvent(EventSubscription::Outputs, "VirtualcamStateChanged", eventData); 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() void EventHandler::HandleReplayBufferSaved()
{ {
json eventData; json eventData;

View File

@ -19,9 +19,25 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "EventHandler.h" #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) 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"); obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene) if (!scene)
@ -33,16 +49,32 @@ void EventHandler::HandleSceneItemCreated(void *param, calldata_t *data)
json eventData; json eventData;
eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene)); 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["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
eventData["sceneItemIndex"] = obs_sceneitem_get_order_position(sceneItem); eventData["sceneItemIndex"] = obs_sceneitem_get_order_position(sceneItem);
eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemCreated", eventData); 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) 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"); obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene) if (!scene)
@ -54,15 +86,28 @@ void EventHandler::HandleSceneItemRemoved(void *param, calldata_t *data)
json eventData; json eventData;
eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene)); 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["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
eventData["sceneItemIndex"] = obs_sceneitem_get_order_position(sceneItem);
eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemRemoved", eventData); 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) 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"); obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene) if (!scene)
@ -70,13 +115,28 @@ void EventHandler::HandleSceneItemListReindexed(void *param, calldata_t *data)
json eventData; json eventData;
eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene)); 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); 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) 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"); obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene) if (!scene)
@ -95,9 +155,24 @@ void EventHandler::HandleSceneItemEnableStateChanged(void *param, calldata_t *da
eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemEnableStateChanged", eventData); 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) 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"); obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene) if (!scene)
@ -116,9 +191,56 @@ void EventHandler::HandleSceneItemLockStateChanged(void *param, calldata_t *data
eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemLockStateChanged", eventData); 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) void EventHandler::HandleSceneItemTransformChanged(void *param, calldata_t *data)
{ {
auto eventHandler = reinterpret_cast<EventHandler*>(param); auto eventHandler = static_cast<EventHandler*>(param);
if (!eventHandler->_sceneItemTransformChangedRef.load()) if (!eventHandler->_sceneItemTransformChangedRef.load())
return; return;
@ -134,6 +256,6 @@ void EventHandler::HandleSceneItemTransformChanged(void *param, calldata_t *data
json eventData; json eventData;
eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene)); eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene));
eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem); 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); 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" #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) void EventHandler::HandleSceneCreated(obs_source_t *source)
{ {
json eventData; json eventData;
@ -27,6 +41,20 @@ void EventHandler::HandleSceneCreated(obs_source_t *source)
BroadcastEvent(EventSubscription::Scenes, "SceneCreated", eventData); 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) void EventHandler::HandleSceneRemoved(obs_source_t *source)
{ {
json eventData; json eventData;
@ -35,6 +63,20 @@ void EventHandler::HandleSceneRemoved(obs_source_t *source)
BroadcastEvent(EventSubscription::Scenes, "SceneRemoved", eventData); 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) void EventHandler::HandleSceneNameChanged(obs_source_t *, std::string oldSceneName, std::string sceneName)
{ {
json eventData; json eventData;
@ -43,15 +85,41 @@ void EventHandler::HandleSceneNameChanged(obs_source_t *, std::string oldSceneNa
BroadcastEvent(EventSubscription::Scenes, "SceneNameChanged", eventData); 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(); OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene();
json eventData; json eventData;
eventData["sceneName"] = obs_source_get_name(currentScene); 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() void EventHandler::HandleCurrentPreviewSceneChanged()
{ {
OBSSourceAutoRelease currentPreviewScene = obs_frontend_get_current_preview_scene(); OBSSourceAutoRelease currentPreviewScene = obs_frontend_get_current_preview_scene();
@ -65,9 +133,24 @@ void EventHandler::HandleCurrentPreviewSceneChanged()
BroadcastEvent(EventSubscription::Scenes, "CurrentPreviewSceneChanged", eventData); 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() void EventHandler::HandleSceneListChanged()
{ {
json eventData; json eventData;
eventData["scenes"] = Utils::Obs::ListHelper::GetSceneList(); eventData["scenes"] = Utils::Obs::ArrayHelper::GetSceneList();
BroadcastEvent(EventSubscription::Scenes, "SceneListChanged", eventData); 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" #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; json eventData;
eventData["transitionName"] = obs_source_get_name(source); eventData["transitionName"] = obs_source_get_name(transition);
eventData["transitionKind"] = obs_source_get_id(source); BroadcastEvent(EventSubscription::Transitions, "CurrentSceneTransitionChanged", eventData);
eventData["transitionFixed"] = obs_transition_fixed(source);
BroadcastEvent(EventSubscription::Transitions, "TransitionCreated", 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; json eventData;
eventData["transitionName"] = obs_source_get_name(source); eventData["transitionDuration"] = obs_frontend_get_transition_duration();
BroadcastEvent(EventSubscription::Transitions, "TransitionRemoved", eventData); BroadcastEvent(EventSubscription::Transitions, "CurrentSceneTransitionDurationChanged", 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);
} }

View File

@ -19,3 +19,22 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "EventHandler.h" #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 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
// Receive events in the `MediaInputs` category
MediaInputs = (1 << 8), MediaInputs = (1 << 8),
/** /**
* Subscription value to receive the `VendorEvent` event. * Subscription value to receive the `VendorEvent` event.
@ -142,9 +141,19 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
// Receive events from external OBS plugins and scripts
Vendors = (1 << 9), 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. * Helper to receive all non-high-volume events.
* *
* @enumIdentifier All * @enumIdentifier All
@ -154,8 +163,7 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
// Receive all event categories (exclude high-volume) All = (General | Config | Scenes | Inputs | Transitions | Filters | Outputs | SceneItems | MediaInputs | Ui | Vendors),
All = (General | Config | Scenes | Inputs | Transitions | Filters | Outputs | SceneItems | MediaInputs | Vendors),
/** /**
* Subscription value to receive the `InputVolumeMeters` high-volume event. * Subscription value to receive the `InputVolumeMeters` high-volume event.
* *
@ -166,7 +174,6 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
// InputVolumeMeters event (high-volume)
InputVolumeMeters = (1 << 16), InputVolumeMeters = (1 << 16),
/** /**
* Subscription value to receive the `InputActiveStateChanged` high-volume event. * Subscription value to receive the `InputActiveStateChanged` high-volume event.
@ -178,7 +185,6 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
// InputActiveStateChanged event (high-volume)
InputActiveStateChanged = (1 << 17), InputActiveStateChanged = (1 << 17),
/** /**
* Subscription value to receive the `InputShowStateChanged` high-volume event. * Subscription value to receive the `InputShowStateChanged` high-volume event.
@ -190,7 +196,6 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
// InputShowStateChanged event (high-volume)
InputShowStateChanged = (1 << 18), InputShowStateChanged = (1 << 18),
/** /**
* Subscription value to receive the `SceneItemTransformChanged` high-volume event. * Subscription value to receive the `SceneItemTransformChanged` high-volume event.
@ -202,7 +207,6 @@ namespace EventSubscription {
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @api enums * @api enums
*/ */
// SceneItemTransformChanged event (high-volume)
SceneItemTransformChanged = (1 << 19), SceneItemTransformChanged = (1 << 19),
}; };
} }

View File

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

View File

@ -35,12 +35,12 @@ OBS_MODULE_AUTHOR("OBSProject")
const char *obs_module_name(void) { return "obs-websocket"; } const char *obs_module_name(void) { return "obs-websocket"; }
const char *obs_module_description(void) { return obs_module_text("OBSWebSocket.Plugin.Description"); } const char *obs_module_description(void) { return obs_module_text("OBSWebSocket.Plugin.Description"); }
os_cpu_usage_info_t* _cpuUsageInfo;
ConfigPtr _config; ConfigPtr _config;
EventHandlerPtr _eventHandler;
WebSocketApiPtr _webSocketApi; WebSocketApiPtr _webSocketApi;
WebSocketServerPtr _webSocketServer; WebSocketServerPtr _webSocketServer;
EventHandlerPtr _eventHandler;
SettingsDialog *_settingsDialog = nullptr; SettingsDialog *_settingsDialog = nullptr;
os_cpu_usage_info_t* _cpuUsageInfo;
void WebSocketApiEventCallback(std::string vendorName, std::string eventType, obs_data_t *obsEventData); 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] 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] 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 = ConfigPtr(new Config());
_config->Load(); _config->Load();
// Initialize event handler before server, as the server configures the event handler. // Initialize the event handler
_eventHandler = EventHandlerPtr(new EventHandler()); _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()); _webSocketServer = WebSocketServerPtr(new WebSocketServer());
// Initialize the settings dialog
obs_frontend_push_ui_translation(obs_module_get_string); 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); _settingsDialog = new SettingsDialog(mainWindow);
obs_frontend_pop_ui_translation(); obs_frontend_pop_ui_translation();
// Add the settings dialog to the tools menu
const char* menuActionText = obs_module_text("OBSWebSocket.Settings.DialogTitle"); const char* menuActionText = obs_module_text("OBSWebSocket.Settings.DialogTitle");
QAction* menuAction = (QAction*)obs_frontend_add_tools_menu_qaction(menuActionText); QAction* menuAction = (QAction*)obs_frontend_add_tools_menu_qaction(menuActionText);
QObject::connect(menuAction, &QAction::triggered, [] { _settingsDialog->ToggleShowHide(); }); QObject::connect(menuAction, &QAction::triggered, [] { _settingsDialog->ToggleShowHide(); });
_cpuUsageInfo = os_cpu_usage_info_start();
// Loading finished
blog(LOG_INFO, "[obs_module_load] Module loaded."); blog(LOG_INFO, "[obs_module_load] Module loaded.");
return true; return true;
} }
@ -81,29 +86,46 @@ void obs_module_unload()
{ {
blog(LOG_INFO, "[obs_module_unload] Shutting down..."); blog(LOG_INFO, "[obs_module_unload] Shutting down...");
// Shutdown the WebSocket server if it is running
if (_webSocketServer->IsListening()) { if (_webSocketServer->IsListening()) {
blog_debug("[obs_module_unload] WebSocket server is running. Stopping..."); blog_debug("[obs_module_unload] WebSocket server is running. Stopping...");
_webSocketServer->Stop(); _webSocketServer->Stop();
} }
// Destroy the WebSocket server
_webSocketServer.reset(); _webSocketServer.reset();
_eventHandler.reset(); // Destroy the plugin/script api
_webSocketApi.reset(); _webSocketApi.reset();
// Destroy the event handler
_eventHandler.reset();
// Save and destroy the config manager
_config->Save(); _config->Save();
_config.reset(); _config.reset();
// Destroy the cpu stats
os_cpu_usage_info_destroy(_cpuUsageInfo); os_cpu_usage_info_destroy(_cpuUsageInfo);
blog(LOG_INFO, "[obs_module_unload] Finished shutting down."); blog(LOG_INFO, "[obs_module_unload] Finished shutting down.");
} }
os_cpu_usage_info_t* GetCpuUsageInfo()
{
return _cpuUsageInfo;
}
ConfigPtr GetConfig() ConfigPtr GetConfig()
{ {
return _config; return _config;
} }
EventHandlerPtr GetEventHandler()
{
return _eventHandler;
}
WebSocketApiPtr GetWebSocketApi() WebSocketApiPtr GetWebSocketApi()
{ {
return _webSocketApi; return _webSocketApi;
@ -114,32 +136,11 @@ WebSocketServerPtr GetWebSocketServer()
return _webSocketServer; return _webSocketServer;
} }
EventHandlerPtr GetEventHandler()
{
return _eventHandler;
}
os_cpu_usage_info_t* GetCpuUsageInfo()
{
return _cpuUsageInfo;
}
bool IsDebugEnabled() bool IsDebugEnabled()
{ {
return !_config || _config->DebugEnabled; 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. * 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 <memory>
#include <obs.hpp> #include <obs.hpp>
#ifdef _MSC_VER
#pragma push_macro("strtoll")
#endif
#include <util/platform.h> #include <util/platform.h>
#ifdef _MSC_VER
#pragma pop_macro("strtoll")
#endif
#include "utils/Obs.h"
#include "plugin-macros.generated.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; class Config;
typedef std::shared_ptr<Config> ConfigPtr; typedef std::shared_ptr<Config> ConfigPtr;
class EventHandler;
typedef std::shared_ptr<EventHandler> EventHandlerPtr;
class WebSocketApi; class WebSocketApi;
typedef std::shared_ptr<WebSocketApi> WebSocketApiPtr; typedef std::shared_ptr<WebSocketApi> WebSocketApiPtr;
class WebSocketServer; class WebSocketServer;
typedef std::shared_ptr<WebSocketServer> WebSocketServerPtr; typedef std::shared_ptr<WebSocketServer> WebSocketServerPtr;
class EventHandler; os_cpu_usage_info_t* GetCpuUsageInfo();
typedef std::shared_ptr<EventHandler> EventHandlerPtr;
ConfigPtr GetConfig(); ConfigPtr GetConfig();
EventHandlerPtr GetEventHandler();
WebSocketApiPtr GetWebSocketApi(); WebSocketApiPtr GetWebSocketApi();
WebSocketServerPtr GetWebSocketServer(); WebSocketServerPtr GetWebSocketServer();
EventHandlerPtr GetEventHandler();
os_cpu_usage_info_t* GetCpuUsageInfo();
bool IsDebugEnabled(); bool IsDebugEnabled();

View File

@ -109,7 +109,7 @@ static void ObsTickCallback(void *param, float)
{ {
ScopeProfiler prof{"obs_websocket_request_batch_frame_tick"}; ScopeProfiler prof{"obs_websocket_request_batch_frame_tick"};
auto serialFrameBatch = reinterpret_cast<SerialFrameBatch*>(param); auto serialFrameBatch = static_cast<SerialFrameBatch*>(param);
// Increment frame count // Increment frame count
serialFrameBatch->frameCount++; 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/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#ifdef PLUGIN_TESTS
#include <util/profiler.hpp>
#endif
#include "RequestHandler.h" #include "RequestHandler.h"
const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{ {
// General // General
{"GetVersion", &RequestHandler::GetVersion}, {"GetVersion", &RequestHandler::GetVersion},
@ -29,8 +33,6 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"GetHotkeyList", &RequestHandler::GetHotkeyList}, {"GetHotkeyList", &RequestHandler::GetHotkeyList},
{"TriggerHotkeyByName", &RequestHandler::TriggerHotkeyByName}, {"TriggerHotkeyByName", &RequestHandler::TriggerHotkeyByName},
{"TriggerHotkeyByKeySequence", &RequestHandler::TriggerHotkeyByKeySequence}, {"TriggerHotkeyByKeySequence", &RequestHandler::TriggerHotkeyByKeySequence},
{"GetStudioModeEnabled", &RequestHandler::GetStudioModeEnabled},
{"SetStudioModeEnabled", &RequestHandler::SetStudioModeEnabled},
{"Sleep", &RequestHandler::Sleep}, {"Sleep", &RequestHandler::Sleep},
// Config // Config
@ -49,14 +51,18 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"SetVideoSettings", &RequestHandler::SetVideoSettings}, {"SetVideoSettings", &RequestHandler::SetVideoSettings},
{"GetStreamServiceSettings", &RequestHandler::GetStreamServiceSettings}, {"GetStreamServiceSettings", &RequestHandler::GetStreamServiceSettings},
{"SetStreamServiceSettings", &RequestHandler::SetStreamServiceSettings}, {"SetStreamServiceSettings", &RequestHandler::SetStreamServiceSettings},
{"GetRecordDirectory", &RequestHandler::GetRecordDirectory},
// Sources // Sources
{"GetSourceActive", &RequestHandler::GetSourceActive}, {"GetSourceActive", &RequestHandler::GetSourceActive},
{"GetSourceScreenshot", &RequestHandler::GetSourceScreenshot}, {"GetSourceScreenshot", &RequestHandler::GetSourceScreenshot},
{"SaveSourceScreenshot", &RequestHandler::SaveSourceScreenshot}, {"SaveSourceScreenshot", &RequestHandler::SaveSourceScreenshot},
{"GetSourcePrivateSettings", &RequestHandler::GetSourcePrivateSettings},
{"SetSourcePrivateSettings", &RequestHandler::SetSourcePrivateSettings},
// Scenes // Scenes
{"GetSceneList", &RequestHandler::GetSceneList}, {"GetSceneList", &RequestHandler::GetSceneList},
{"GetGroupList", &RequestHandler::GetGroupList},
{"GetCurrentProgramScene", &RequestHandler::GetCurrentProgramScene}, {"GetCurrentProgramScene", &RequestHandler::GetCurrentProgramScene},
{"SetCurrentProgramScene", &RequestHandler::SetCurrentProgramScene}, {"SetCurrentProgramScene", &RequestHandler::SetCurrentProgramScene},
{"GetCurrentPreviewScene", &RequestHandler::GetCurrentPreviewScene}, {"GetCurrentPreviewScene", &RequestHandler::GetCurrentPreviewScene},
@ -64,12 +70,15 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"CreateScene", &RequestHandler::CreateScene}, {"CreateScene", &RequestHandler::CreateScene},
{"RemoveScene", &RequestHandler::RemoveScene}, {"RemoveScene", &RequestHandler::RemoveScene},
{"SetSceneName", &RequestHandler::SetSceneName}, {"SetSceneName", &RequestHandler::SetSceneName},
{"GetSceneSceneTransitionOverride", &RequestHandler::GetSceneSceneTransitionOverride},
{"SetSceneSceneTransitionOverride", &RequestHandler::SetSceneSceneTransitionOverride},
// Inputs // Inputs
{"GetInputList", &RequestHandler::GetInputList}, {"GetInputList", &RequestHandler::GetInputList},
{"GetInputKindList", &RequestHandler::GetInputKindList}, {"GetInputKindList", &RequestHandler::GetInputKindList},
{"GetSpecialInputs", &RequestHandler::GetSpecialInputs},
{"CreateInput", &RequestHandler::CreateInput}, {"CreateInput", &RequestHandler::CreateInput},
//{"RemoveInput", &RequestHandler::RemoveInput}, // Disabled for now. Pending obs-studio#5276 {"RemoveInput", &RequestHandler::RemoveInput},
{"SetInputName", &RequestHandler::SetInputName}, {"SetInputName", &RequestHandler::SetInputName},
{"GetInputDefaultSettings", &RequestHandler::GetInputDefaultSettings}, {"GetInputDefaultSettings", &RequestHandler::GetInputDefaultSettings},
{"GetInputSettings", &RequestHandler::GetInputSettings}, {"GetInputSettings", &RequestHandler::GetInputSettings},
@ -79,13 +88,39 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"ToggleInputMute", &RequestHandler::ToggleInputMute}, {"ToggleInputMute", &RequestHandler::ToggleInputMute},
{"GetInputVolume", &RequestHandler::GetInputVolume}, {"GetInputVolume", &RequestHandler::GetInputVolume},
{"SetInputVolume", &RequestHandler::SetInputVolume}, {"SetInputVolume", &RequestHandler::SetInputVolume},
{"GetInputAudioBalance", &RequestHandler::GetInputAudioBalance},
{"SetInputAudioBalance", &RequestHandler::SetInputAudioBalance},
{"GetInputAudioSyncOffset", &RequestHandler::GetInputAudioSyncOffset}, {"GetInputAudioSyncOffset", &RequestHandler::GetInputAudioSyncOffset},
{"SetInputAudioSyncOffset", &RequestHandler::SetInputAudioSyncOffset}, {"SetInputAudioSyncOffset", &RequestHandler::SetInputAudioSyncOffset},
{"GetInputAudioMonitorType", &RequestHandler::GetInputAudioMonitorType}, {"GetInputAudioMonitorType", &RequestHandler::GetInputAudioMonitorType},
{"SetInputAudioMonitorType", &RequestHandler::SetInputAudioMonitorType}, {"SetInputAudioMonitorType", &RequestHandler::SetInputAudioMonitorType},
{"GetInputAudioTracks", &RequestHandler::GetInputAudioTracks},
{"SetInputAudioTracks", &RequestHandler::SetInputAudioTracks},
{"GetInputPropertiesListPropertyItems", &RequestHandler::GetInputPropertiesListPropertyItems}, {"GetInputPropertiesListPropertyItems", &RequestHandler::GetInputPropertiesListPropertyItems},
{"PressInputPropertiesButton", &RequestHandler::PressInputPropertiesButton}, {"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 // Scene Items
{"GetSceneItemList", &RequestHandler::GetSceneItemList}, {"GetSceneItemList", &RequestHandler::GetSceneItemList},
{"GetGroupSceneItemList", &RequestHandler::GetGroupSceneItemList}, {"GetGroupSceneItemList", &RequestHandler::GetGroupSceneItemList},
@ -101,12 +136,27 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"SetSceneItemLocked", &RequestHandler::SetSceneItemLocked}, {"SetSceneItemLocked", &RequestHandler::SetSceneItemLocked},
{"GetSceneItemIndex", &RequestHandler::GetSceneItemIndex}, {"GetSceneItemIndex", &RequestHandler::GetSceneItemIndex},
{"SetSceneItemIndex", &RequestHandler::SetSceneItemIndex}, {"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 // Stream
{"GetStreamStatus", &RequestHandler::GetStreamStatus}, {"GetStreamStatus", &RequestHandler::GetStreamStatus},
{"ToggleStream", &RequestHandler::ToggleStream}, {"ToggleStream", &RequestHandler::ToggleStream},
{"StartStream", &RequestHandler::StartStream}, {"StartStream", &RequestHandler::StartStream},
{"StopStream", &RequestHandler::StopStream}, {"StopStream", &RequestHandler::StopStream},
{"SendStreamCaption", &RequestHandler::SendStreamCaption},
// Record // Record
{"GetRecordStatus", &RequestHandler::GetRecordStatus}, {"GetRecordStatus", &RequestHandler::GetRecordStatus},
@ -116,13 +166,19 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"ToggleRecordPause", &RequestHandler::ToggleRecordPause}, {"ToggleRecordPause", &RequestHandler::ToggleRecordPause},
{"PauseRecord", &RequestHandler::PauseRecord}, {"PauseRecord", &RequestHandler::PauseRecord},
{"ResumeRecord", &RequestHandler::ResumeRecord}, {"ResumeRecord", &RequestHandler::ResumeRecord},
//{"GetRecordDirectory", &RequestHandler::GetRecordDirectory},
// Media Inputs // Media Inputs
{"GetMediaInputStatus", &RequestHandler::GetMediaInputStatus}, {"GetMediaInputStatus", &RequestHandler::GetMediaInputStatus},
{"SetMediaInputCursor", &RequestHandler::SetMediaInputCursor}, {"SetMediaInputCursor", &RequestHandler::SetMediaInputCursor},
{"OffsetMediaInputCursor", &RequestHandler::OffsetMediaInputCursor}, {"OffsetMediaInputCursor", &RequestHandler::OffsetMediaInputCursor},
{"TriggerMediaInputAction", &RequestHandler::TriggerMediaInputAction}, {"TriggerMediaInputAction", &RequestHandler::TriggerMediaInputAction},
// Ui
{"GetStudioModeEnabled", &RequestHandler::GetStudioModeEnabled},
{"SetStudioModeEnabled", &RequestHandler::SetStudioModeEnabled},
{"OpenInputPropertiesDialog", &RequestHandler::OpenInputPropertiesDialog},
{"OpenInputFiltersDialog", &RequestHandler::OpenInputFiltersDialog},
{"OpenInputInteractDialog", &RequestHandler::OpenInputInteractDialog},
}; };
RequestHandler::RequestHandler(SessionPtr session) : RequestHandler::RequestHandler(SessionPtr session) :
@ -132,6 +188,10 @@ RequestHandler::RequestHandler(SessionPtr session) :
RequestResult RequestHandler::ProcessRequest(const Request& request) 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()) if (!request.RequestData.is_object() && !request.RequestData.is_null())
return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "Your request data is not an object."); 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 #pragma once
#include <map> #include <unordered_map>
#include <obs.hpp> #include <obs.hpp>
#include <obs-frontend-api.h> #include <obs-frontend-api.h>
@ -51,8 +51,6 @@ class RequestHandler {
RequestResult GetHotkeyList(const Request&); RequestResult GetHotkeyList(const Request&);
RequestResult TriggerHotkeyByName(const Request&); RequestResult TriggerHotkeyByName(const Request&);
RequestResult TriggerHotkeyByKeySequence(const Request&); RequestResult TriggerHotkeyByKeySequence(const Request&);
RequestResult GetStudioModeEnabled(const Request&);
RequestResult SetStudioModeEnabled(const Request&);
RequestResult Sleep(const Request&); RequestResult Sleep(const Request&);
// Config // Config
@ -71,14 +69,18 @@ class RequestHandler {
RequestResult SetVideoSettings(const Request&); RequestResult SetVideoSettings(const Request&);
RequestResult GetStreamServiceSettings(const Request&); RequestResult GetStreamServiceSettings(const Request&);
RequestResult SetStreamServiceSettings(const Request&); RequestResult SetStreamServiceSettings(const Request&);
RequestResult GetRecordDirectory(const Request&);
// Sources // Sources
RequestResult GetSourceActive(const Request&); RequestResult GetSourceActive(const Request&);
RequestResult GetSourceScreenshot(const Request&); RequestResult GetSourceScreenshot(const Request&);
RequestResult SaveSourceScreenshot(const Request&); RequestResult SaveSourceScreenshot(const Request&);
RequestResult GetSourcePrivateSettings(const Request&);
RequestResult SetSourcePrivateSettings(const Request&);
// Scenes // Scenes
RequestResult GetSceneList(const Request&); RequestResult GetSceneList(const Request&);
RequestResult GetGroupList(const Request&);
RequestResult GetCurrentProgramScene(const Request&); RequestResult GetCurrentProgramScene(const Request&);
RequestResult SetCurrentProgramScene(const Request&); RequestResult SetCurrentProgramScene(const Request&);
RequestResult GetCurrentPreviewScene(const Request&); RequestResult GetCurrentPreviewScene(const Request&);
@ -86,10 +88,13 @@ class RequestHandler {
RequestResult CreateScene(const Request&); RequestResult CreateScene(const Request&);
RequestResult RemoveScene(const Request&); RequestResult RemoveScene(const Request&);
RequestResult SetSceneName(const Request&); RequestResult SetSceneName(const Request&);
RequestResult GetSceneSceneTransitionOverride(const Request&);
RequestResult SetSceneSceneTransitionOverride(const Request&);
// Inputs // Inputs
RequestResult GetInputList(const Request&); RequestResult GetInputList(const Request&);
RequestResult GetInputKindList(const Request&); RequestResult GetInputKindList(const Request&);
RequestResult GetSpecialInputs(const Request&);
RequestResult CreateInput(const Request&); RequestResult CreateInput(const Request&);
RequestResult RemoveInput(const Request&); RequestResult RemoveInput(const Request&);
RequestResult SetInputName(const Request&); RequestResult SetInputName(const Request&);
@ -101,13 +106,39 @@ class RequestHandler {
RequestResult ToggleInputMute(const Request&); RequestResult ToggleInputMute(const Request&);
RequestResult GetInputVolume(const Request&); RequestResult GetInputVolume(const Request&);
RequestResult SetInputVolume(const Request&); RequestResult SetInputVolume(const Request&);
RequestResult GetInputAudioBalance(const Request&);
RequestResult SetInputAudioBalance(const Request&);
RequestResult GetInputAudioSyncOffset(const Request&); RequestResult GetInputAudioSyncOffset(const Request&);
RequestResult SetInputAudioSyncOffset(const Request&); RequestResult SetInputAudioSyncOffset(const Request&);
RequestResult GetInputAudioMonitorType(const Request&); RequestResult GetInputAudioMonitorType(const Request&);
RequestResult SetInputAudioMonitorType(const Request&); RequestResult SetInputAudioMonitorType(const Request&);
RequestResult GetInputAudioTracks(const Request&);
RequestResult SetInputAudioTracks(const Request&);
RequestResult GetInputPropertiesListPropertyItems(const Request&); RequestResult GetInputPropertiesListPropertyItems(const Request&);
RequestResult PressInputPropertiesButton(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 // Scene Items
RequestResult GetSceneItemList(const Request&); RequestResult GetSceneItemList(const Request&);
RequestResult GetGroupSceneItemList(const Request&); RequestResult GetGroupSceneItemList(const Request&);
@ -123,12 +154,27 @@ class RequestHandler {
RequestResult SetSceneItemLocked(const Request&); RequestResult SetSceneItemLocked(const Request&);
RequestResult GetSceneItemIndex(const Request&); RequestResult GetSceneItemIndex(const Request&);
RequestResult SetSceneItemIndex(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 // Stream
RequestResult GetStreamStatus(const Request&); RequestResult GetStreamStatus(const Request&);
RequestResult ToggleStream(const Request&); RequestResult ToggleStream(const Request&);
RequestResult StartStream(const Request&); RequestResult StartStream(const Request&);
RequestResult StopStream(const Request&); RequestResult StopStream(const Request&);
RequestResult SendStreamCaption(const Request&);
// Record // Record
RequestResult GetRecordStatus(const Request&); RequestResult GetRecordStatus(const Request&);
@ -138,7 +184,6 @@ class RequestHandler {
RequestResult ToggleRecordPause(const Request&); RequestResult ToggleRecordPause(const Request&);
RequestResult PauseRecord(const Request&); RequestResult PauseRecord(const Request&);
RequestResult ResumeRecord(const Request&); RequestResult ResumeRecord(const Request&);
RequestResult GetRecordDirectory(const Request&);
// Media Inputs // Media Inputs
RequestResult GetMediaInputStatus(const Request&); RequestResult GetMediaInputStatus(const Request&);
@ -146,6 +191,13 @@ class RequestHandler {
RequestResult OffsetMediaInputCursor(const Request&); RequestResult OffsetMediaInputCursor(const Request&);
RequestResult TriggerMediaInputAction(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; 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; json responseData;
responseData["currentSceneCollectionName"] = Utils::Obs::StringHelper::GetCurrentSceneCollection(); responseData["currentSceneCollectionName"] = Utils::Obs::StringHelper::GetCurrentSceneCollection();
responseData["sceneCollections"] = Utils::Obs::ListHelper::GetSceneCollectionList(); responseData["sceneCollections"] = Utils::Obs::ArrayHelper::GetSceneCollectionList();
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -151,7 +151,7 @@ RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request)
std::string sceneCollectionName = request.RequestData["sceneCollectionName"]; 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()) if (std::find(sceneCollections.begin(), sceneCollections.end(), sceneCollectionName) == sceneCollections.end())
return RequestResult::Error(RequestStatus::ResourceNotFound); return RequestResult::Error(RequestStatus::ResourceNotFound);
@ -159,7 +159,7 @@ RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request)
// Avoid queueing tasks if nothing will change // Avoid queueing tasks if nothing will change
if (currentSceneCollectionName != sceneCollectionName) { if (currentSceneCollectionName != sceneCollectionName) {
obs_queue_task(OBS_TASK_UI, [](void* param) { 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); }, (void*)sceneCollectionName.c_str(), true);
} }
@ -189,11 +189,11 @@ RequestResult RequestHandler::CreateSceneCollection(const Request& request)
std::string sceneCollectionName = request.RequestData["sceneCollectionName"]; 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()) if (std::find(sceneCollections.begin(), sceneCollections.end(), sceneCollectionName) != sceneCollections.end())
return RequestResult::Error(RequestStatus::ResourceAlreadyExists); 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; bool success = false;
QMetaObject::invokeMethod(mainWindow, "AddSceneCollection", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, success), Q_ARG(bool, true), Q_ARG(QString, QString::fromStdString(sceneCollectionName))); QMetaObject::invokeMethod(mainWindow, "AddSceneCollection", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, success), Q_ARG(bool, true), Q_ARG(QString, QString::fromStdString(sceneCollectionName)));
if (!success) if (!success)
@ -219,7 +219,7 @@ RequestResult RequestHandler::GetProfileList(const Request&)
{ {
json responseData; json responseData;
responseData["currentProfileName"] = Utils::Obs::StringHelper::GetCurrentProfile(); responseData["currentProfileName"] = Utils::Obs::StringHelper::GetCurrentProfile();
responseData["profiles"] = Utils::Obs::ListHelper::GetProfileList(); responseData["profiles"] = Utils::Obs::ArrayHelper::GetProfileList();
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -244,7 +244,7 @@ RequestResult RequestHandler::SetCurrentProfile(const Request& request)
std::string profileName = request.RequestData["profileName"]; 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()) if (std::find(profiles.begin(), profiles.end(), profileName) == profiles.end())
return RequestResult::Error(RequestStatus::ResourceNotFound); return RequestResult::Error(RequestStatus::ResourceNotFound);
@ -252,7 +252,7 @@ RequestResult RequestHandler::SetCurrentProfile(const Request& request)
// Avoid queueing tasks if nothing will change // Avoid queueing tasks if nothing will change
if (currentProfileName != profileName) { if (currentProfileName != profileName) {
obs_queue_task(OBS_TASK_UI, [](void* param) { 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); }, (void*)profileName.c_str(), true);
} }
@ -280,11 +280,11 @@ RequestResult RequestHandler::CreateProfile(const Request& request)
std::string profileName = request.RequestData["profileName"]; 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()) if (std::find(profiles.begin(), profiles.end(), profileName) != profiles.end())
return RequestResult::Error(RequestStatus::ResourceAlreadyExists); 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))); QMetaObject::invokeMethod(mainWindow, "NewProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName)));
return RequestResult::Success(); return RequestResult::Success();
@ -311,14 +311,14 @@ RequestResult RequestHandler::RemoveProfile(const Request& request)
std::string profileName = request.RequestData["profileName"]; 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()) if (std::find(profiles.begin(), profiles.end(), profileName) == profiles.end())
return RequestResult::Error(RequestStatus::ResourceNotFound); return RequestResult::Error(RequestStatus::ResourceNotFound);
if (profiles.size() < 2) if (profiles.size() < 2)
return RequestResult::Error(RequestStatus::NotEnoughResources); 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))); QMetaObject::invokeMethod(mainWindow, "DeleteProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName)));
return RequestResult::Success(); 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 * @responseField defaultParameterValue | String | Default value associated with the parameter. `null` if no default
* *
* @requestType GetProfileParameter * @requestType GetProfileParameter
* @complexity 3 * @complexity 4
* @rpcVersion -1 * @rpcVersion -1
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @category config * @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 * @requestField parameterValue | String | Value of the parameter to set. Use `null` to delete
* *
* @requestType SetProfileParameter * @requestType SetProfileParameter
* @complexity 3 * @complexity 4
* @rpcVersion -1 * @rpcVersion -1
* @initialVersion 5.0.0 * @initialVersion 5.0.0
* @category config * @category config
@ -408,6 +408,8 @@ RequestResult RequestHandler::SetProfileParameter(const Request& request)
return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "The field `parameterValue` must be a string."); return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "The field `parameterValue` must be a string.");
} }
config_save(profile);
return RequestResult::Success(); return RequestResult::Success();
} }
@ -592,3 +594,23 @@ RequestResult RequestHandler::SetStreamServiceSettings(const Request& request)
return RequestResult::Success(); 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&) RequestResult RequestHandler::GetStats(const Request&)
{ {
json responseData = Utils::Obs::DataHelper::GetStats(); json responseData = Utils::Obs::ObjectHelper::GetStats();
responseData["webSocketSessionIncomingMessages"] = _session->IncomingMessages(); responseData["webSocketSessionIncomingMessages"] = _session->IncomingMessages();
responseData["webSocketSessionOutgoingMessages"] = _session->OutgoingMessages(); responseData["webSocketSessionOutgoingMessages"] = _session->OutgoingMessages();
@ -195,7 +195,7 @@ RequestResult RequestHandler::CallVendorRequest(const Request& request)
RequestResult RequestHandler::GetHotkeyList(const Request&) RequestResult RequestHandler::GetHotkeyList(const Request&)
{ {
json responseData; json responseData;
responseData["hotkeys"] = Utils::Obs::ListHelper::GetHotkeyNameList(); responseData["hotkeys"] = Utils::Obs::ArrayHelper::GetHotkeyNameList();
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -288,58 +288,6 @@ RequestResult RequestHandler::TriggerHotkeyByKeySequence(const Request& request)
return RequestResult::Success(); 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`. * 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; json responseData;
responseData["inputs"] = Utils::Obs::ListHelper::GetInputList(inputKind); responseData["inputs"] = Utils::Obs::ArrayHelper::GetInputList(inputKind);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
@ -79,7 +79,44 @@ RequestResult RequestHandler::GetInputKindList(const Request& request)
} }
json responseData; 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); 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."); return RequestResult::Error(RequestStatus::ResourceAlreadyExists, "A source already exists by that input name.");
std::string inputKind = request.RequestData["inputKind"]; 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()) 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."); 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); return RequestResult::Error(statusCode, comment);
std::string inputKind = request.RequestData["inputKind"]; 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()); OBSDataAutoRelease defaultSettings = obs_get_source_defaults(inputKind.c_str());
if (!defaultSettings) if (!defaultSettings)
@ -278,8 +318,9 @@ RequestResult RequestHandler::GetInputSettings(const Request& request)
/** /**
* Sets the settings of an input. * Sets the settings of an input.
* *
* @requestField inputName | String | Name of the input to set the settings of * @requestField inputName | String | Name of the input to set the settings of
* @requestField inputSettings | Object | Object of settings to apply * @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 * @requestType SetInputSettings
* @complexity 3 * @complexity 3
@ -345,6 +386,9 @@ RequestResult RequestHandler::GetInputMute(const Request& request)
if (!input) if (!input)
return RequestResult::Error(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 responseData; json responseData;
responseData["inputMuted"] = obs_source_muted(input); responseData["inputMuted"] = obs_source_muted(input);
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
@ -371,6 +415,9 @@ RequestResult RequestHandler::SetInputMute(const Request& request)
if (!(input && request.ValidateBoolean("inputMuted", statusCode, comment))) if (!(input && request.ValidateBoolean("inputMuted", statusCode, comment)))
return RequestResult::Error(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"]); obs_source_set_muted(input, request.RequestData["inputMuted"]);
return RequestResult::Success(); return RequestResult::Success();
@ -398,6 +445,9 @@ RequestResult RequestHandler::ToggleInputMute(const Request& request)
if (!input) if (!input)
return RequestResult::Error(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.");
bool inputMuted = !obs_source_muted(input); bool inputMuted = !obs_source_muted(input);
obs_source_set_muted(input, inputMuted); obs_source_set_muted(input, inputMuted);
@ -429,6 +479,9 @@ RequestResult RequestHandler::GetInputVolume(const Request& request)
if (!input) if (!input)
return RequestResult::Error(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.");
float inputVolumeMul = obs_source_get_volume(input); float inputVolumeMul = obs_source_get_volume(input);
float inputVolumeDb = obs_mul_to_db(inputVolumeMul); float inputVolumeDb = obs_mul_to_db(inputVolumeMul);
if (inputVolumeDb == -INFINITY) 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 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 ?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 * @requestType SetInputVolume
* @complexity 3 * @complexity 3
@ -462,6 +515,9 @@ RequestResult RequestHandler::SetInputVolume(const Request& request)
if (!input) if (!input)
return RequestResult::Error(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.");
bool hasMul = request.Contains("inputVolumeMul"); bool hasMul = request.Contains("inputVolumeMul");
if (hasMul && !request.ValidateOptionalNumber("inputVolumeMul", statusCode, comment, 0, 20)) if (hasMul && !request.ValidateOptionalNumber("inputVolumeMul", statusCode, comment, 0, 20))
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
@ -487,6 +543,67 @@ RequestResult RequestHandler::SetInputVolume(const Request& request)
return RequestResult::Success(); 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. * Gets the audio sync offset of an input.
* *
@ -511,6 +628,9 @@ RequestResult RequestHandler::GetInputAudioSyncOffset(const Request& request)
if (!input) if (!input)
return RequestResult::Error(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 responseData; json responseData;
// Offset is stored in nanoseconds in OBS. // Offset is stored in nanoseconds in OBS.
responseData["inputAudioSyncOffset"] = obs_source_get_sync_offset(input) / 1000000; 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))) if (!(input && request.ValidateNumber("inputAudioSyncOffset", statusCode, comment, -950, 20000)))
return RequestResult::Error(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.");
int64_t syncOffset = request.RequestData["inputAudioSyncOffset"]; int64_t syncOffset = request.RequestData["inputAudioSyncOffset"];
obs_source_set_sync_offset(input, syncOffset * 1000000); obs_source_set_sync_offset(input, syncOffset * 1000000);
@ -573,6 +696,9 @@ RequestResult RequestHandler::GetInputAudioMonitorType(const Request& request)
if (!input) if (!input)
return RequestResult::Error(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 responseData; json responseData;
responseData["monitorType"] = Utils::Obs::StringHelper::GetInputMonitorType(input); responseData["monitorType"] = Utils::Obs::StringHelper::GetInputMonitorType(input);
@ -600,6 +726,12 @@ RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request)
if (!(input && request.ValidateString("monitorType", statusCode, comment))) if (!(input && request.ValidateString("monitorType", statusCode, comment)))
return RequestResult::Error(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; enum obs_monitoring_type monitorType;
std::string monitorTypeString = request.RequestData["monitorType"]; std::string monitorTypeString = request.RequestData["monitorType"];
if (monitorTypeString == "OBS_MONITORING_TYPE_NONE") if (monitorTypeString == "OBS_MONITORING_TYPE_NONE")
@ -616,30 +748,93 @@ RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request)
return RequestResult::Success(); 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); if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
size_t itemCount = obs_property_list_item_count(property); return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
for (size_t i = 0; i < itemCount; i++) { long long tracks = obs_source_get_audio_mixers(input);
json itemData;
itemData["itemName"] = obs_property_list_item_name(property, i); json inputAudioTracks;
itemData["itemEnabled"] = !obs_property_list_item_disabled(property, i); for (long long i = 0; i < MAX_AUDIO_MIXES; i++) {
if (itemFormat == OBS_COMBO_FORMAT_INT) { inputAudioTracks[std::to_string(i + 1)] = (bool)((tracks >> i) & 1);
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; 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."); return RequestResult::Error(RequestStatus::InvalidResourceType, "The property found is not a list.");
json responseData; json responseData;
responseData["propertyItems"] = GetListPropertyItems(property); responseData["propertyItems"] = Utils::Obs::ArrayHelper::GetListPropertyItems(property);
return RequestResult::Success(responseData); 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; 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) RequestResult RequestHandler::GetMediaInputStatus(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
@ -47,6 +73,21 @@ RequestResult RequestHandler::GetMediaInputStatus(const Request& request)
return RequestResult::Success(responseData); 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) RequestResult RequestHandler::SetMediaInputCursor(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
@ -66,6 +107,21 @@ RequestResult RequestHandler::SetMediaInputCursor(const Request& request)
return RequestResult::Success(); 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) RequestResult RequestHandler::OffsetMediaInputCursor(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
@ -88,6 +144,19 @@ RequestResult RequestHandler::OffsetMediaInputCursor(const Request& request)
return RequestResult::Success(); 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) RequestResult RequestHandler::TriggerMediaInputAction(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; 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" #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&) 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); uint64_t outputDuration = Utils::Obs::NumberHelper::GetOutputDuration(recordOutput);
@ -35,6 +51,16 @@ RequestResult RequestHandler::GetRecordStatus(const Request&)
return RequestResult::Success(responseData); 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&) RequestResult RequestHandler::ToggleRecord(const Request&)
{ {
json responseData; json responseData;
@ -49,6 +75,16 @@ RequestResult RequestHandler::ToggleRecord(const Request&)
return RequestResult::Success(responseData); 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&) RequestResult RequestHandler::StartRecord(const Request&)
{ {
if (obs_frontend_recording_active()) if (obs_frontend_recording_active())
@ -60,6 +96,16 @@ RequestResult RequestHandler::StartRecord(const Request&)
return RequestResult::Success(); 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&) RequestResult RequestHandler::StopRecord(const Request&)
{ {
if (!obs_frontend_recording_active()) if (!obs_frontend_recording_active())
@ -71,6 +117,16 @@ RequestResult RequestHandler::StopRecord(const Request&)
return RequestResult::Success(); 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&) RequestResult RequestHandler::ToggleRecordPause(const Request&)
{ {
json responseData; json responseData;
@ -85,6 +141,16 @@ RequestResult RequestHandler::ToggleRecordPause(const Request&)
return RequestResult::Success(responseData); 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&) RequestResult RequestHandler::PauseRecord(const Request&)
{ {
if (obs_frontend_recording_paused()) if (obs_frontend_recording_paused())
@ -96,6 +162,16 @@ RequestResult RequestHandler::PauseRecord(const Request&)
return RequestResult::Success(); 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&) RequestResult RequestHandler::ResumeRecord(const Request&)
{ {
if (!obs_frontend_recording_paused()) if (!obs_frontend_recording_paused())
@ -106,11 +182,3 @@ RequestResult RequestHandler::ResumeRecord(const Request&)
return RequestResult::Success(); 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" #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) RequestResult RequestHandler::GetSceneItemList(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
@ -28,11 +44,29 @@ RequestResult RequestHandler::GetSceneItemList(const Request& request)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
json responseData; 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); 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) RequestResult RequestHandler::GetGroupSceneItemList(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
@ -42,26 +76,36 @@ RequestResult RequestHandler::GetGroupSceneItemList(const Request& request)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
json responseData; 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); 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) RequestResult RequestHandler::GetSceneItemId(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
OBSSourceAutoRelease sceneSource = request.ValidateScene("sceneName", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); OBSSceneAutoRelease scene = request.ValidateScene2("sceneName", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!(sceneSource && request.ValidateString("sourceName", statusCode, comment))) if (!(scene && request.ValidateString("sourceName", statusCode, comment)))
return RequestResult::Error(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"]; std::string sourceName = request.RequestData["sourceName"];
OBSSceneItemAutoRelease item = Utils::Obs::SearchHelper::GetSceneItemByName(scene, sourceName); OBSSceneItemAutoRelease item = Utils::Obs::SearchHelper::GetSceneItemByName(scene, sourceName);
@ -74,6 +118,24 @@ RequestResult RequestHandler::GetSceneItemId(const Request& request)
return RequestResult::Success(responseData); 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) RequestResult RequestHandler::CreateSceneItem(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
@ -108,6 +170,21 @@ RequestResult RequestHandler::CreateSceneItem(const Request& request)
return RequestResult::Success(responseData); 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) RequestResult RequestHandler::RemoveSceneItem(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
@ -116,11 +193,30 @@ RequestResult RequestHandler::RemoveSceneItem(const Request& request)
if (!sceneItem) if (!sceneItem)
return RequestResult::Error(statusCode, comment); 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); obs_sceneitem_remove(sceneItem);
return RequestResult::Success(); 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) RequestResult RequestHandler::DuplicateSceneItem(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
@ -136,8 +232,9 @@ RequestResult RequestHandler::DuplicateSceneItem(const Request& request)
if (!destinationScene) if (!destinationScene)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
} else { } else {
destinationScene = obs_sceneitem_get_scene(sceneItem); destinationScene = obs_scene_get_ref(obs_sceneitem_get_scene(sceneItem));
obs_scene_addref(destinationScene); 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) { 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); 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) RequestResult RequestHandler::GetSceneItemTransform(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
@ -174,11 +288,25 @@ RequestResult RequestHandler::GetSceneItemTransform(const Request& request)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
json responseData; json responseData;
responseData["sceneItemTransform"] = Utils::Obs::DataHelper::GetSceneItemTransform(sceneItem); responseData["sceneItemTransform"] = Utils::Obs::ObjectHelper::GetSceneItemTransform(sceneItem);
return RequestResult::Success(responseData); 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) RequestResult RequestHandler::SetSceneItemTransform(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
@ -317,6 +445,23 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request& request)
return RequestResult::Success(); 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) RequestResult RequestHandler::GetSceneItemEnabled(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
@ -331,6 +476,22 @@ RequestResult RequestHandler::GetSceneItemEnabled(const Request& request)
return RequestResult::Success(responseData); 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) RequestResult RequestHandler::SetSceneItemEnabled(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
@ -346,6 +507,23 @@ RequestResult RequestHandler::SetSceneItemEnabled(const Request& request)
return RequestResult::Success(); 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) RequestResult RequestHandler::GetSceneItemLocked(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
@ -360,6 +538,22 @@ RequestResult RequestHandler::GetSceneItemLocked(const Request& request)
return RequestResult::Success(responseData); 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) RequestResult RequestHandler::SetSceneItemLocked(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
@ -375,6 +569,25 @@ RequestResult RequestHandler::SetSceneItemLocked(const Request& request)
return RequestResult::Success(); 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) RequestResult RequestHandler::GetSceneItemIndex(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
@ -389,6 +602,22 @@ RequestResult RequestHandler::GetSceneItemIndex(const Request& request)
return RequestResult::Success(responseData); 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) RequestResult RequestHandler::SetSceneItemIndex(const Request& request)
{ {
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
@ -403,3 +632,81 @@ RequestResult RequestHandler::SetSceneItemIndex(const Request& request)
return RequestResult::Success(); 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. * Gets an array of all scenes in OBS.
* *
* @responseField scenes | Array<String> | Array of scenes in OBS
* @responseField currentProgramSceneName | String | Current program scene * @responseField currentProgramSceneName | String | Current program scene
* @responseField currentPreviewSceneName | String | Current preview scene. `null` if not in studio mode * @responseField currentPreviewSceneName | String | Current preview scene. `null` if not in studio mode
* @responseField scenes | Array<Object> | Array of scenes
* *
* @requestType GetSceneList * @requestType GetSceneList
* @complexity 2 * @complexity 2
@ -49,7 +49,30 @@ RequestResult RequestHandler::GetSceneList(const Request&)
else else
responseData["currentPreviewSceneName"] = nullptr; 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); return RequestResult::Success(responseData);
} }
@ -250,3 +273,105 @@ RequestResult RequestHandler::SetSceneName(const Request& request)
return RequestResult::Success(); 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(); 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" #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&) RequestResult RequestHandler::GetStreamStatus(const Request&)
{ {
OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output(); OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output();
@ -37,6 +55,18 @@ RequestResult RequestHandler::GetStreamStatus(const Request&)
return RequestResult::Success(responseData); 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&) RequestResult RequestHandler::ToggleStream(const Request&)
{ {
json responseData; json responseData;
@ -51,6 +81,16 @@ RequestResult RequestHandler::ToggleStream(const Request&)
return RequestResult::Success(responseData); 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&) RequestResult RequestHandler::StartStream(const Request&)
{ {
if (obs_frontend_streaming_active()) if (obs_frontend_streaming_active())
@ -62,6 +102,16 @@ RequestResult RequestHandler::StartStream(const Request&)
return RequestResult::Success(); 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&) RequestResult RequestHandler::StopStream(const Request&)
{ {
if (!obs_frontend_streaming_active()) if (!obs_frontend_streaming_active())
@ -72,3 +122,35 @@ RequestResult RequestHandler::StopStream(const Request&)
return RequestResult::Success(); 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)"; comment = "The specified source is not a scene. (Is group)";
return nullptr; return nullptr;
} }
OBSScene ret = obs_group_from_source(sceneSource); return obs_scene_get_ref(obs_group_from_source(sceneSource));
obs_scene_addref(ret);
return ret;
} else { } else {
if (filter == OBS_WEBSOCKET_SCENE_FILTER_GROUP_ONLY) { if (filter == OBS_WEBSOCKET_SCENE_FILTER_GROUP_ONLY) {
statusCode = RequestStatus::InvalidResourceType; statusCode = RequestStatus::InvalidResourceType;
comment = "The specified source is not a group. (Is scene)"; comment = "The specified source is not a group. (Is scene)";
return nullptr; return nullptr;
} }
OBSScene ret = obs_scene_from_source(sceneSource); return obs_scene_get_ref(obs_scene_from_source(sceneSource));
obs_scene_addref(ret);
return ret;
} }
} }
@ -295,24 +291,35 @@ obs_source_t *Request::ValidateInput(const std::string &keyName, RequestStatus::
return ret; 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 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); OBSSceneAutoRelease scene = ValidateScene2(sceneKeyName, statusCode, comment, filter);
if (!sceneSource) if (!scene)
return nullptr; return nullptr;
if (!ValidateNumber(sceneItemIdKeyName, statusCode, comment, 0)) if (!ValidateNumber(sceneItemIdKeyName, statusCode, comment, 0))
return nullptr; 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]; int64_t sceneItemId = RequestData[sceneItemIdKeyName];

View File

@ -29,6 +29,12 @@ enum ObsWebSocketSceneFilter {
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP, 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 struct Request
{ {
Request(const std::string &requestType, const json &requestData = nullptr, const RequestBatchExecutionType::RequestBatchExecutionType executionType = RequestBatchExecutionType::None); 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_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_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; 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; 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; std::string RequestType;

View File

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

View File

@ -332,6 +332,30 @@ namespace RequestStatus {
* @api enums * @api enums
*/ */
InvalidInputKind = 605, 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. * 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 "Platform.h"
#include "../plugin-macros.generated.h" #include "../plugin-macros.generated.h"
bool Utils::Json::JsonArrayIsValidObsArray(json j) bool Utils::Json::JsonArrayIsValidObsArray(const json &j)
{ {
for (auto it : j) { for (auto it : j) {
if (!it.is_object()) if (!it.is_object())
@ -191,7 +191,7 @@ bool Utils::Json::GetJsonFileContent(std::string fileName, json &content)
return true; 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); std::string textContent = content.dump(2);
return Utils::Platform::SetTextFileContent(fileName, textContent, createNew); return Utils::Platform::SetTextFileContent(fileName, textContent, createNew);

View File

@ -27,10 +27,11 @@ using json = nlohmann::json;
namespace Utils { namespace Utils {
namespace Json { namespace Json {
bool JsonArrayIsValidObsArray(json j); bool JsonArrayIsValidObsArray(const json &j);
obs_data_t *JsonToObsData(json j); obs_data_t *JsonToObsData(json j);
json ObsDataToJson(obs_data_t *d, bool includeDefault = false); json ObsDataToJson(obs_data_t *d, bool includeDefault = false);
bool GetJsonFileContent(std::string fileName, json &content); 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/> 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.h"
#include "../obs-websocket.h"
#include "../plugin-macros.generated.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 <string>
#include <obs.hpp> #include <obs.hpp>
#include <obs-frontend-api.h>
#include "Json.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) { template <typename T> T* GetCalldataPointer(const calldata_t *data, const char* name) {
void *ptr = nullptr; void *ptr = nullptr;
calldata_get_ptr(data, name, &ptr); calldata_get_ptr(data, name, &ptr);
return reinterpret_cast<T*>(ptr); return static_cast<T*>(ptr);
} }
enum ObsOutputState { enum ObsOutputState {
OBS_WEBSOCKET_OUTPUT_UNKNOWN,
OBS_WEBSOCKET_OUTPUT_STARTING, OBS_WEBSOCKET_OUTPUT_STARTING,
OBS_WEBSOCKET_OUTPUT_STARTED, OBS_WEBSOCKET_OUTPUT_STARTED,
OBS_WEBSOCKET_OUTPUT_STOPPING, OBS_WEBSOCKET_OUTPUT_STOPPING,
OBS_WEBSOCKET_OUTPUT_STOPPED, OBS_WEBSOCKET_OUTPUT_STOPPED,
OBS_WEBSOCKET_OUTPUT_RECONNECTING, OBS_WEBSOCKET_OUTPUT_RECONNECTING,
OBS_WEBSOCKET_OUTPUT_PAUSED, OBS_WEBSOCKET_OUTPUT_PAUSED,
OBS_WEBSOCKET_OUTPUT_RESUMED OBS_WEBSOCKET_OUTPUT_RESUMED,
}; };
enum ObsMediaInputAction { 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, 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, 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, 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, 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, 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_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 { namespace Utils {
@ -59,48 +158,61 @@ namespace Utils {
std::string GetCurrentProfilePath(); std::string GetCurrentProfilePath();
std::string GetCurrentRecordOutputPath(); std::string GetCurrentRecordOutputPath();
std::string GetSourceType(obs_source_t *source); std::string GetSourceType(obs_source_t *source);
std::string GetInputMonitorType(enum obs_monitoring_type monitorType);
std::string GetInputMonitorType(obs_source_t *input); std::string GetInputMonitorType(obs_source_t *input);
std::string GetMediaInputState(obs_source_t *input); std::string GetMediaInputState(obs_source_t *input);
std::string GetLastReplayBufferFilePath(); std::string GetLastReplayBufferFilePath();
std::string GetSceneItemBoundsType(enum obs_bounds_type type); std::string GetSceneItemBoundsType(enum obs_bounds_type type);
std::string GetSceneItemBlendMode(enum obs_blending_type mode);
std::string DurationToTimecode(uint64_t); std::string DurationToTimecode(uint64_t);
std::string GetOutputState(ObsOutputState state);
} }
namespace EnumHelper { namespace EnumHelper {
enum obs_bounds_type GetSceneItemBoundsType(std::string boundsType); enum obs_bounds_type GetSceneItemBoundsType(std::string boundsType);
enum ObsMediaInputAction GetMediaInputAction(std::string mediaAction); enum ObsMediaInputAction GetMediaInputAction(std::string mediaAction);
enum obs_blending_type GetSceneItemBlendMode(std::string mode);
} }
namespace NumberHelper { namespace NumberHelper {
uint64_t GetOutputDuration(obs_output_t *output); uint64_t GetOutputDuration(obs_output_t *output);
size_t GetSceneCount(); 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> GetSceneCollectionList();
std::vector<std::string> GetProfileList(); std::vector<std::string> GetProfileList();
std::vector<obs_hotkey_t *> GetHotkeyList(); std::vector<obs_hotkey_t *> GetHotkeyList();
std::vector<std::string> GetHotkeyNameList(); std::vector<std::string> GetHotkeyNameList();
std::vector<json> GetSceneList(); std::vector<json> GetSceneList();
std::vector<std::string> GetGroupList();
std::vector<json> GetSceneItemList(obs_scene_t *scene, bool basic = false); std::vector<json> GetSceneItemList(obs_scene_t *scene, bool basic = false);
std::vector<json> GetTransitionList();
std::vector<json> GetInputList(std::string inputKind = ""); std::vector<json> GetInputList(std::string inputKind = "");
std::vector<std::string> GetInputKindList(bool unversioned = false, bool includeDisabled = false); 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 GetStats();
json GetSceneItemTransform(obs_sceneitem_t *item); json GetSceneItemTransform(obs_sceneitem_t *item);
} }
namespace SearchHelper { namespace SearchHelper {
obs_hotkey_t *GetHotkeyByName(std::string name); 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 obs_sceneitem_t *GetSceneItemByName(obs_scene_t *scene, std::string name); // Increments ref. Use OBSSceneItemAutoRelease
} }
namespace ActionHelper { 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 *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_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 obs-websocket
Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de> Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de>
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com> Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com> Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or the Free Software Foundation; either version 2 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License along You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#include <cmath> #include <cmath>
#include <algorithm> #include <algorithm>
#include "Obs.h" #include "Obs.h"
#include "ObsVolumeMeter.h" #include "Obs_VolumeMeter.h"
#include "ObsVolumeMeter_Helpers.h" #include "Obs_VolumeMeter_Helpers.h"
#include "../obs-websocket.h"
Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input) :
PeakMeterType(SAMPLE_PEAK_METER), Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input) :
_input(obs_source_get_weak_source(input)), PeakMeterType(SAMPLE_PEAK_METER),
_channels(0), _input(obs_source_get_weak_source(input)),
_lastUpdate(0), _channels(0),
_volume(obs_source_get_volume(input)) _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); 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);
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));
} blog_debug("[Utils::Obs::VolumeMeter::Meter::Meter] Meter created for input: %s", obs_source_get_name(input));
}
Utils::Obs::VolumeMeter::Meter::~Meter()
{ Utils::Obs::VolumeMeter::Meter::~Meter()
OBSSourceAutoRelease input = obs_weak_source_get_source(_input); {
if (!input) { OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::~Meter] Failed to get strong reference to input. Has it been destroyed?"); if (!input) {
return; 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); 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);
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));
} blog_debug("[Utils::Obs::VolumeMeter::Meter::~Meter] Meter destroyed for input: %s", obs_source_get_name(input));
}
bool Utils::Obs::VolumeMeter::Meter::InputValid()
{ bool Utils::Obs::VolumeMeter::Meter::InputValid()
// return !obs_weak_source_expired(_input); {
return true; return !obs_weak_source_expired(_input);
} }
json Utils::Obs::VolumeMeter::Meter::GetMeterData() json Utils::Obs::VolumeMeter::Meter::GetMeterData()
{ {
json ret; json ret;
OBSSourceAutoRelease input = obs_weak_source_get_source(_input); OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
if (!input) { if (!input) {
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::GetMeterData] Failed to get strong reference to input. Has it been destroyed?"); blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::GetMeterData] Failed to get strong reference to input. Has it been destroyed?");
return ret; return ret;
} }
std::vector<std::vector<float>> levels; std::vector<std::vector<float>> levels;
const float volume = _muted ? 0.0f : _volume.load(); const float volume = _muted ? 0.0f : _volume.load();
std::unique_lock<std::mutex> l(_mutex); std::unique_lock<std::mutex> l(_mutex);
if (_lastUpdate != 0 && (os_gettime_ns() - _lastUpdate) * 0.000000001 > 0.3) if (_lastUpdate != 0 && (os_gettime_ns() - _lastUpdate) * 0.000000001 > 0.3)
ResetAudioLevels(); ResetAudioLevels();
for (int channel = 0; channel < _channels; channel++) { for (int channel = 0; channel < _channels; channel++) {
std::vector<float> level; std::vector<float> level;
level.push_back(_magnitude[channel] * volume); level.push_back(_magnitude[channel] * volume);
level.push_back(_peak[channel] * volume); level.push_back(_peak[channel] * volume);
level.push_back(_peak[channel]); level.push_back(_peak[channel]);
levels.push_back(level); levels.push_back(level);
} }
l.unlock(); l.unlock();
ret["inputName"] = obs_source_get_name(input); ret["inputName"] = obs_source_get_name(input);
ret["inputLevelsMul"] = levels; ret["inputLevelsMul"] = levels;
return ret; return ret;
} }
// MUST HOLD LOCK // MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ResetAudioLevels() void Utils::Obs::VolumeMeter::Meter::ResetAudioLevels()
{ {
_lastUpdate = 0; _lastUpdate = 0;
for (int channelNumber = 0; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++) { for (int channelNumber = 0; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++) {
_magnitude[channelNumber] = 0; _magnitude[channelNumber] = 0;
_peak[channelNumber] = 0; _peak[channelNumber] = 0;
} }
} }
// MUST HOLD LOCK // MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ProcessAudioChannels(const struct audio_data *data) void Utils::Obs::VolumeMeter::Meter::ProcessAudioChannels(const struct audio_data *data)
{ {
int channels = 0; int channels = 0;
for (int i = 0; i < MAX_AV_PLANES; i++) { for (int i = 0; i < MAX_AV_PLANES; i++) {
if (data->data[i]) if (data->data[i])
channels++; channels++;
} }
bool channelsChanged = _channels != channels; bool channelsChanged = _channels != channels;
_channels = std::clamp(channels, 0, MAX_AUDIO_CHANNELS); _channels = std::clamp(channels, 0, MAX_AUDIO_CHANNELS);
if (channelsChanged) if (channelsChanged)
ResetAudioLevels(); ResetAudioLevels();
} }
// MUST HOLD LOCK // MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data) void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data)
{ {
size_t sampleCount = data->frames; size_t sampleCount = data->frames;
int channelNumber = 0; int channelNumber = 0;
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) { for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
float *samples = (float*)data->data[planeNumber]; float *samples = (float*)data->data[planeNumber];
if (!samples) if (!samples)
continue; continue;
if (((uintptr_t)samples & 0xf) > 0) { if (((uintptr_t)samples & 0xf) > 0) {
_peak[channelNumber] = 1.0f; _peak[channelNumber] = 1.0f;
channelNumber++; channelNumber++;
continue; continue;
} }
__m128 previousSamples = _mm_loadu_ps(_previousSamples[channelNumber]); __m128 previousSamples = _mm_loadu_ps(_previousSamples[channelNumber]);
float peak; float peak;
switch (PeakMeterType) { switch (PeakMeterType) {
default: default:
case SAMPLE_PEAK_METER: case SAMPLE_PEAK_METER:
peak = GetSamplePeak(previousSamples, samples, sampleCount); peak = GetSamplePeak(previousSamples, samples, sampleCount);
break; break;
case TRUE_PEAK_METER: case TRUE_PEAK_METER:
peak = GetTruePeak(previousSamples, samples, sampleCount); peak = GetTruePeak(previousSamples, samples, sampleCount);
break; break;
} }
switch (sampleCount) { switch (sampleCount) {
case 0: case 0:
break; break;
case 1: case 1:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][1]; _previousSamples[channelNumber][0] = _previousSamples[channelNumber][1];
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][2]; _previousSamples[channelNumber][1] = _previousSamples[channelNumber][2];
_previousSamples[channelNumber][2] = _previousSamples[channelNumber][3]; _previousSamples[channelNumber][2] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; _previousSamples[channelNumber][3] = samples[sampleCount - 1];
break; break;
case 2: case 2:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][2]; _previousSamples[channelNumber][0] = _previousSamples[channelNumber][2];
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][3]; _previousSamples[channelNumber][1] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2]; _previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; _previousSamples[channelNumber][3] = samples[sampleCount - 1];
break; break;
case 3: case 3:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][3]; _previousSamples[channelNumber][0] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][1] = samples[sampleCount - 3]; _previousSamples[channelNumber][1] = samples[sampleCount - 3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2]; _previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; _previousSamples[channelNumber][3] = samples[sampleCount - 1];
break; break;
default: default:
_previousSamples[channelNumber][0] = samples[sampleCount - 4]; _previousSamples[channelNumber][0] = samples[sampleCount - 4];
_previousSamples[channelNumber][1] = samples[sampleCount - 3]; _previousSamples[channelNumber][1] = samples[sampleCount - 3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2]; _previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1]; _previousSamples[channelNumber][3] = samples[sampleCount - 1];
} }
_peak[channelNumber] = peak; _peak[channelNumber] = peak;
channelNumber++; channelNumber++;
} }
for (; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++) for (; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++)
_peak[channelNumber] = 0.0; _peak[channelNumber] = 0.0;
} }
// MUST HOLD LOCK // MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(const struct audio_data *data) void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(const struct audio_data *data)
{ {
size_t sampleCount = data->frames; size_t sampleCount = data->frames;
int channelNumber = 0; int channelNumber = 0;
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) { for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
float *samples = (float*)data->data[planeNumber]; float *samples = (float*)data->data[planeNumber];
if (!samples) if (!samples)
continue; continue;
float sum = 0.0; float sum = 0.0;
for (size_t i = 0; i < sampleCount; i++) { for (size_t i = 0; i < sampleCount; i++) {
float sample = samples[i]; float sample = samples[i];
sum += sample * sample; sum += sample * sample;
} }
_magnitude[channelNumber] = std::sqrt(sum / sampleCount); _magnitude[channelNumber] = std::sqrt(sum / sampleCount);
channelNumber++; channelNumber++;
} }
} }
void Utils::Obs::VolumeMeter::Meter::InputAudioCaptureCallback(void *priv_data, obs_source_t *, const struct audio_data *data, bool muted) 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); auto c = static_cast<Meter*>(priv_data);
std::unique_lock<std::mutex> l(c->_mutex); std::unique_lock<std::mutex> l(c->_mutex);
c->_muted = muted; c->_muted = muted;
c->ProcessAudioChannels(data); c->ProcessAudioChannels(data);
c->ProcessPeak(data); c->ProcessPeak(data);
c->ProcessMagnitude(data); c->ProcessMagnitude(data);
c->_lastUpdate = os_gettime_ns(); c->_lastUpdate = os_gettime_ns();
} }
void Utils::Obs::VolumeMeter::Meter::InputVolumeCallback(void *priv_data, calldata_t *cd) void Utils::Obs::VolumeMeter::Meter::InputVolumeCallback(void *priv_data, calldata_t *cd)
{ {
auto c = static_cast<Meter*>(priv_data); auto c = static_cast<Meter*>(priv_data);
c->_volume = (float)calldata_float(cd, "volume"); c->_volume = (float)calldata_float(cd, "volume");
} }
Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod) : Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod) :
_updateCallback(cb), _updateCallback(cb),
_updatePeriod(updatePeriod), _updatePeriod(updatePeriod),
_running(false) _running(false)
{ {
signal_handler_t *sh = obs_get_signal_handler(); signal_handler_t *sh = obs_get_signal_handler();
if (!sh) if (!sh)
return; return;
auto enumProc = [](void *priv_data, obs_source_t *input) { auto enumProc = [](void *priv_data, obs_source_t *input) {
auto c = static_cast<Handler*>(priv_data); auto c = static_cast<Handler*>(priv_data);
if (!obs_source_active(input)) if (!obs_source_active(input))
return true; return true;
uint32_t flags = obs_source_get_output_flags(input); uint32_t flags = obs_source_get_output_flags(input);
if ((flags & OBS_SOURCE_AUDIO) == 0) if ((flags & OBS_SOURCE_AUDIO) == 0)
return true; return true;
c->_meters.emplace_back(std::move(new Meter(input))); c->_meters.emplace_back(std::move(new Meter(input)));
return true; return true;
}; };
obs_enum_sources(enumProc, this); obs_enum_sources(enumProc, this);
signal_handler_connect(sh, "source_activate", Handler::InputActivateCallback, this); signal_handler_connect(sh, "source_activate", Handler::InputActivateCallback, this);
signal_handler_connect(sh, "source_deactivate", Handler::InputDeactivateCallback, this); signal_handler_connect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
_running = true; _running = true;
_updateThread = std::thread(&Handler::UpdateThread, this); _updateThread = std::thread(&Handler::UpdateThread, this);
blog_debug("[Utils::Obs::VolumeMeter::Handler::Handler] Handler created."); blog_debug("[Utils::Obs::VolumeMeter::Handler::Handler] Handler created.");
} }
Utils::Obs::VolumeMeter::Handler::~Handler() Utils::Obs::VolumeMeter::Handler::~Handler()
{ {
signal_handler_t *sh = obs_get_signal_handler(); signal_handler_t *sh = obs_get_signal_handler();
if (!sh) if (!sh)
return; return;
signal_handler_disconnect(sh, "source_activate", Handler::InputActivateCallback, this); signal_handler_disconnect(sh, "source_activate", Handler::InputActivateCallback, this);
signal_handler_disconnect(sh, "source_deactivate", Handler::InputDeactivateCallback, this); signal_handler_disconnect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
if (_running) { if (_running) {
_mutex.lock(); _running = false;
_running = false; _cond.notify_all();
_mutex.unlock(); }
_cond.notify_all();
} if (_updateThread.joinable())
_updateThread.join();
if (_updateThread.joinable())
_updateThread.join(); blog_debug("[Utils::Obs::VolumeMeter::Handler::~Handler] Handler destroyed.");
}
blog_debug("[Utils::Obs::VolumeMeter::Handler::~Handler] Handler destroyed.");
} void Utils::Obs::VolumeMeter::Handler::UpdateThread()
{
void Utils::Obs::VolumeMeter::Handler::UpdateThread() blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread started.");
{ while (_running) {
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; }))
std::unique_lock<std::mutex> l(_mutex); break;
if (_cond.wait_for(l, std::chrono::milliseconds(_updatePeriod), [this]{ return !_running; })) }
break;
} std::vector<json> inputs;
std::unique_lock<std::mutex> l(_meterMutex);
std::vector<json> inputs; for (auto &meter : _meters) {
std::unique_lock<std::mutex> l(_meterMutex); if (meter->InputValid())
for (auto &meter : _meters) { inputs.push_back(meter->GetMeterData());
if (meter->InputValid()) }
inputs.push_back(meter->GetMeterData()); l.unlock();
}
l.unlock(); if (_updateCallback)
_updateCallback(inputs);
if (_updateCallback) }
_updateCallback(inputs); blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread stopped.");
} }
blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread stopped.");
} void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data, calldata_t *cd)
{
void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data, calldata_t *cd) auto c = static_cast<Handler*>(priv_data);
{
auto c = static_cast<Handler*>(priv_data); obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
if (!input)
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source"); return;
if (!input)
return; if (obs_source_get_type(input) != OBS_SOURCE_TYPE_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)
uint32_t flags = obs_source_get_output_flags(input); return;
if ((flags & OBS_SOURCE_AUDIO) == 0)
return; std::unique_lock<std::mutex> l(c->_meterMutex);
c->_meters.emplace_back(std::move(new Meter(input)));
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)
{
void Utils::Obs::VolumeMeter::Handler::InputDeactivateCallback(void *priv_data, calldata_t *cd) auto c = static_cast<Handler*>(priv_data);
{
auto c = static_cast<Handler*>(priv_data); obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
if (!input)
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source"); return;
if (!input)
return; if (obs_source_get_type(input) != OBS_SOURCE_TYPE_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);
// Don't ask me why, but using std::remove_if segfaults trying this. std::vector<MeterPtr>::iterator iter;
std::unique_lock<std::mutex> l(c->_meterMutex); for (iter = c->_meters.begin(); iter != c->_meters.end();) {
std::vector<MeterPtr>::iterator iter; if (obs_weak_source_references_source(iter->get()->GetWeakInput(), input))
for (iter = c->_meters.begin(); iter != c->_meters.end();) { iter = c->_meters.erase(iter);
if (obs_weak_source_references_source(iter->get()->GetWeakInput(), input)) else
iter = c->_meters.erase(iter); ++iter;
else }
++iter; }
}
}

View File

@ -1,99 +1,99 @@
/* /*
obs-websocket obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com> Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com> Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.com>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or the Free Software Foundation; either version 2 of the License, or
(at your option) any later version. (at your option) any later version.
This program is distributed in the hope that it will be useful, This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
You should have received a copy of the GNU General Public License along You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/> with this program. If not, see <https://www.gnu.org/licenses/>
*/ */
#pragma once #pragma once
#include <string> #include <string>
#include <atomic> #include <atomic>
#include <mutex> #include <mutex>
#include <condition_variable> #include <condition_variable>
#include <memory> #include <memory>
#include <thread> #include <thread>
#include <obs.hpp> #include <obs.hpp>
#include "../obs-websocket.h" #include "Obs.h"
#include "Json.h" #include "Json.h"
namespace Utils { namespace Utils {
namespace Obs { namespace Obs {
namespace VolumeMeter { namespace VolumeMeter {
// Some code copied from https://github.com/obsproject/obs-studio/blob/master/libobs/obs-audio-controls.c // 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 // Keeps a running tally of the current audio levels, for a specific input
class Meter { class Meter {
public: public:
Meter(obs_source_t *input); Meter(obs_source_t *input);
~Meter(); ~Meter();
bool InputValid(); bool InputValid();
obs_weak_source_t *GetWeakInput() { return _input; } obs_weak_source_t *GetWeakInput() { return _input; }
json GetMeterData(); json GetMeterData();
std::atomic<enum obs_peak_meter_type> PeakMeterType; std::atomic<enum obs_peak_meter_type> PeakMeterType;
private: private:
OBSWeakSourceAutoRelease _input; OBSWeakSourceAutoRelease _input;
// All values in mul // All values in mul
std::mutex _mutex; std::mutex _mutex;
bool _muted; bool _muted;
int _channels; int _channels;
float _magnitude[MAX_AUDIO_CHANNELS]; float _magnitude[MAX_AUDIO_CHANNELS];
float _peak[MAX_AUDIO_CHANNELS]; float _peak[MAX_AUDIO_CHANNELS];
float _previousSamples[MAX_AUDIO_CHANNELS][4]; float _previousSamples[MAX_AUDIO_CHANNELS][4];
std::atomic<uint64_t> _lastUpdate; std::atomic<uint64_t> _lastUpdate;
std::atomic<float> _volume; std::atomic<float> _volume;
void ResetAudioLevels(); void ResetAudioLevels();
void ProcessAudioChannels(const struct audio_data *data); void ProcessAudioChannels(const struct audio_data *data);
void ProcessPeak(const struct audio_data *data); void ProcessPeak(const struct audio_data *data);
void ProcessMagnitude(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 InputAudioCaptureCallback(void *priv_data, obs_source_t *source, const struct audio_data *data, bool muted);
static void InputVolumeCallback(void *priv_data, calldata_t *cd); static void InputVolumeCallback(void *priv_data, calldata_t *cd);
}; };
// Maintains an array of active inputs // Maintains an array of active inputs
class Handler { class Handler {
typedef std::function<void(std::vector<json>)> UpdateCallback; typedef std::function<void(std::vector<json>)> UpdateCallback;
typedef std::unique_ptr<Meter> MeterPtr; typedef std::unique_ptr<Meter> MeterPtr;
public: public:
Handler(UpdateCallback cb, uint64_t updatePeriod = 50); Handler(UpdateCallback cb, uint64_t updatePeriod = 50);
~Handler(); ~Handler();
private: private:
UpdateCallback _updateCallback; UpdateCallback _updateCallback;
std::mutex _meterMutex; std::mutex _meterMutex;
std::vector<MeterPtr> _meters; std::vector<MeterPtr> _meters;
uint64_t _updatePeriod; uint64_t _updatePeriod;
std::mutex _mutex; std::mutex _mutex;
std::condition_variable _cond; std::condition_variable _cond;
bool _running; std::atomic<bool> _running;
std::thread _updateThread; std::thread _updateThread;
void UpdateThread(); void UpdateThread();
static void InputActivateCallback(void *priv_data, calldata_t *cd); static void InputActivateCallback(void *priv_data, calldata_t *cd);
static void InputDeactivateCallback(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]); \ r = fmaxf(r, x4_mem[3]); \
} while (false) } 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; __m128 peak = previousSamples;
for (size_t i = 0; (i + 3) < sampleCount; i += 4) { 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; 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 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); 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) { obs_queue_task(OBS_TASK_UI, [](void* param) {
void *systemTrayPtr = obs_frontend_get_system_tray(); 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); systemTray->showMessage(notification->title, notification->body, notification->icon);
delete notification; delete notification;
}, (void*)notification, false); }, (void*)notification, false);

View File

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

View File

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

View File

@ -233,7 +233,7 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
return; return;
} }
uint8_t requestedExecutionType = payloadData["executionType"]; int8_t requestedExecutionType = payloadData["executionType"];
if (!RequestBatchExecutionType::IsValid(requestedExecutionType) || requestedExecutionType == RequestBatchExecutionType::None) { if (!RequestBatchExecutionType::IsValid(requestedExecutionType) || requestedExecutionType == RequestBatchExecutionType::None) {
ret.closeCode = WebSocketCloseCode::InvalidDataFieldValue; ret.closeCode = WebSocketCloseCode::InvalidDataFieldValue;
ret.closeReason = "Your `executionType` has an invalid value."; ret.closeReason = "Your `executionType` has an invalid value.";
@ -296,7 +296,7 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces
} return; } return;
default: default:
ret.closeCode = WebSocketCloseCode::UnknownOpCode; 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; return;
} }
} }