diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index 839c9545..f4067645 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -69,8 +69,8 @@ body: label: obs-websocket Version description: What version of obs-websocket are you using? options: + - 5.0.0-alpha3 - 5.0.0-alpha2 - - 5.0.0-alpha1 - 4.9.1 - 4.9.0 - Git diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 50e63703..93b7dad7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: "CI Multiplatform Build" +name: 'CI Multiplatform Build' on: push: @@ -18,15 +18,15 @@ on: jobs: windows: name: 'Windows 32/64-bit' - runs-on: [windows-latest] + runs-on: [windows-2019] if: contains(github.event.head_commit.message, '[skip ci]') != true env: QT_CACHE_VERSION: '2' # Change whenever updating OBS dependencies URL, in order to force a cache reset QT_VERSION: '5.15.2' WINDOWS_DEPS_CACHE_VERSION: '1' # Change whenever updating Qt dependency URL, in order to force a cache reset WINDOWS_DEPS_VERSION: '2019' - CMAKE_GENERATOR: "Visual Studio 16 2019" - CMAKE_SYSTEM_VERSION: "10.0" + CMAKE_GENERATOR: 'Visual Studio 16 2019' + CMAKE_SYSTEM_VERSION: '10.0' steps: - name: 'Add msbuild to PATH' uses: microsoft/setup-msbuild@v1.0.2 @@ -35,20 +35,20 @@ jobs: with: path: ${{ github.workspace }}/obs-websocket submodules: 'recursive' - - name: 'Checkout OBS-Studio' + - name: 'Checkout OBS Studio' uses: actions/checkout@v2 with: repository: obsproject/obs-studio path: ${{ github.workspace }}/obs-studio submodules: 'recursive' - - name: 'Get OBS-Studio Git Info' + - name: 'Get OBS Studio Git Info' shell: bash working-directory: ${{ github.workspace }}/obs-studio run: | git fetch --prune --unshallow echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV - - name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})' + - name: 'Checkout last OBS Studio release (${{ env.OBS_GIT_TAG }})' shell: bash working-directory: ${{ github.workspace }}/obs-studio run: | @@ -78,8 +78,6 @@ jobs: with: path: Qt_${{ env.QT_VERSION }}.7z key: 'qtdep-${{ env.QT_CACHE_VERSION }} | ${{ runner.os }}' - restore-keys: | - qtdep-${{ env.QT_CACHE_VERSION }} | ${{ runner.os }} - name: 'Download Prerequisite: Qt' if: steps.qtcache.outputs.cache-hit != 'true' run: | @@ -87,20 +85,18 @@ jobs: - name: 'Extract Prerequisite: Qt' run: | 7z x Qt_${{ env.QT_VERSION }}.7z -o"${{ github.workspace }}\cmbuild\QT" - - name: 'Restore Cached OBS-Studio Dependencies' + - name: 'Restore Cached OBS Studio Dependencies' id: obscache uses: actions/cache@v2 with: path: ${{ github.workspace }}\cmbuild\deps\** key: 'obsdep-${{ env.WINDOWS_DEPS_CACHE_VERSION }} | ${{ runner.os }}' - restore-keys: | - obsdep-${{ env.WINDOWS_DEPS_CACHE_VERSION }} | ${{ runner.os }} - - name: 'Install Prerequisite: Pre-built OBS-Studio dependencies' + - name: 'Install Prerequisite: Pre-built OBS Studio dependencies' if: steps.obscache.outputs.cache-hit != 'true' run: | curl -kLO https://cdn-fastly.obsproject.com/downloads/dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -f --retry 5 -C - 7z x dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -o"${{ github.workspace }}\cmbuild\deps" - - name: 'Restore OBS-Studio 32-bit Build v${{ env.OBS_GIT_TAG }} from Cache' + - name: 'Restore OBS Studio 32-bit Build v${{ env.OBS_GIT_TAG }} from Cache' id: build-cache-obs-32 uses: actions/cache@v2 env: @@ -108,22 +104,20 @@ jobs: with: path: ${{ github.workspace }}/obs-studio/build32 key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }} - restore-keys: | - ${{ runner.os }}-${{ env.CACHE_NAME }}- - - name: 'Configure OBS-Studio 32-bit' + - name: 'Configure OBS Studio 32-bit' if: steps.build-cache-obs-32.outputs.cache-hit != 'true' working-directory: ${{ github.workspace }}/obs-studio run: | if(!(Test-Path -Path ".\build32")){New-Item -ItemType directory -Path .\build32} cd .\build32 cmake -G "${{ env.CMAKE_GENERATOR }}" -A Win32 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win32" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES -DBUILD_BROWSER=OFF .. - - name: 'Build OBS-Studio 32-bit' + - name: 'Build OBS Studio 32-bit' if: steps.build-cache-obs-32.outputs.cache-hit != 'true' working-directory: ${{ github.workspace }}/obs-studio run: | msbuild /m /p:Configuration=RelWithDebInfo .\build32\libobs\libobs.vcxproj msbuild /m /p:Configuration=RelWithDebInfo .\build32\UI\obs-frontend-api\obs-frontend-api.vcxproj - - name: 'Restore OBS-Studio 64-bit Build v${{ env.OBS_GIT_TAG }} from Cache' + - name: 'Restore OBS Studio 64-bit Build v${{ env.OBS_GIT_TAG }} from Cache' id: build-cache-obs-64 uses: actions/cache@v1 env: @@ -131,16 +125,14 @@ jobs: with: path: ${{ github.workspace }}/obs-studio/build64 key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }} - restore-keys: | - ${{ runner.os }}-${{ env.CACHE_NAME }}- - - name: 'Configure OBS-Studio 64-bit' + - name: 'Configure OBS Studio 64-bit' if: steps.build-cache-obs-64.outputs.cache-hit != 'true' working-directory: ${{ github.workspace }}/obs-studio run: | if(!(Test-Path -Path ".\build64")){New-Item -ItemType directory -Path .\build64} cd .\build64 cmake -G "${{ env.CMAKE_GENERATOR }}" -A x64 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019_64" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win64" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES -DBUILD_BROWSER=OFF .. - - name: 'Build OBS-Studio 64-bit' + - name: 'Build OBS Studio 64-bit' if: steps.build-cache-obs-64.outputs.cache-hit != 'true' working-directory: ${{ github.workspace }}/obs-studio run: | @@ -188,7 +180,7 @@ jobs: name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-Windows-Installer' path: ${{ github.workspace }}/obs-websocket/package/*.exe ubuntu64: - name: "Linux/Ubuntu 64-bit" + name: 'Linux/Ubuntu 64-bit' runs-on: [ubuntu-latest] if: contains(github.event.head_commit.message, '[skip ci]') != true steps: @@ -197,20 +189,20 @@ jobs: with: path: ${{ github.workspace }}/obs-websocket submodules: 'recursive' - - name: 'Checkout OBS-Studio' + - name: 'Checkout OBS Studio' uses: actions/checkout@v2 with: repository: obsproject/obs-studio path: ${{ github.workspace }}/obs-studio submodules: 'recursive' - - name: 'Get OBS-Studio Git Info' + - name: 'Get OBS Studio Git Info' shell: bash working-directory: ${{ github.workspace }}/obs-studio run: | git fetch --prune --unshallow echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV - - name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})' + - name: 'Checkout last OBS Studio release (${{ env.OBS_GIT_TAG }})' shell: bash working-directory: ${{ github.workspace }}/obs-studio run: | @@ -284,21 +276,21 @@ jobs: libx11-xcb-dev \ libxcb1-dev \ libxss-dev \ - - name: 'Configure OBS-Studio' + - name: 'Configure OBS Studio' working-directory: ${{ github.workspace }}/obs-studio shell: bash run: | mkdir ./build cd ./build cmake -DDISABLE_PLUGINS=YES -DENABLE_SCRIPTING=NO -DUNIX_STRUCTURE=YES -DCMAKE_INSTALL_PREFIX=/usr .. - - name: 'Build OBS-Studio' + - name: 'Build OBS Studio' working-directory: ${{ github.workspace }}/obs-studio shell: bash run: | set -e cd ./build make -j4 libobs obs-frontend-api - - name: 'Install OBS-Studio' + - name: 'Install OBS Studio' working-directory: ${{ github.workspace }}/obs-studio shell: bash run: | @@ -316,7 +308,7 @@ jobs: if [ "${{ env.GIT_TAG }}" ] ; then \ cmake -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=TRUE -DOBS_WEBSOCKET_VERSION_SUFFIX="${{ env.CMAKE_VERSION_SUFFIX }}" -DCMAKE_BUILD_TYPE=Release .. ; \ else \ - cmake -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=TRUE -DOBS_WEBSOCKET_VERSION_SUFFIX="${{ env.CMAKE_VERSION_SUFFIX }}" .. ; \ + cmake -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=TRUE -DOBS_WEBSOCKET_VERSION_SUFFIX="${{ env.CMAKE_VERSION_SUFFIX }}" -DPLUGIN_TESTS=TRUE .. ; \ fi - name: 'Build obs-websocket' working-directory: ${{ github.workspace }}/obs-websocket @@ -355,3 +347,213 @@ jobs: with: name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-Ubuntu64' path: '${{ github.workspace }}/obs-websocket/package/*.deb' + macOS: + name: 'macOS 64-bit' + runs-on: [macos-latest] + if: contains(github.event.head_commit.message, '[skip ci]') != true + env: + MACOS_DEPS_VERSION: '2022-01-01' + MACOS_DEPS_CACHE_VERSION: '2' # Change whenever updating dependencies version, in order to force a cache reset + steps: + - name: 'Checkout obs-websocket' + uses: actions/checkout@v2 + with: + path: ${{ github.workspace }}/obs-websocket + submodules: 'recursive' + - name: 'Checkout OBS Studio' + uses: actions/checkout@v2 + with: + repository: obsproject/obs-studio + path: ${{ github.workspace }}/obs-studio + submodules: 'recursive' + - name: 'Install Prerequisite: Binary Signing Certificate' + if: github.event_name != 'pull_request' + uses: apple-actions/import-codesign-certs@v1 + with: + p12-file-base64: ${{ secrets.MACOS_SIGNING_CERT }} + p12-password: ${{ secrets.MACOS_SIGNING_CERT_PASSWORD }} + create-keychain: true + keychain-password: ${{ secrets.MACOS_TEMP_CI_KEYCHAIN_PASSWORD }} + - name: 'Install Prerequisite: Installer Signing Certificate' + if: github.event_name != 'pull_request' + uses: apple-actions/import-codesign-certs@v1 + with: + p12-file-base64: ${{ secrets.MACOS_INSTALLER_CERT }} + p12-password: ${{ secrets.MACOS_INSTALLER_CERT_PASSWORD }} + create-keychain: false + keychain-password: ${{ secrets.MACOS_TEMP_CI_KEYCHAIN_PASSWORD }} + - name: 'Get OBS Studio Git Info' + shell: bash + working-directory: ${{ github.workspace }}/obs-studio + run: | + git fetch --prune --unshallow + echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV + - name: 'Checkout last OBS Studio release (${{ env.OBS_GIT_TAG }})' + shell: bash + working-directory: ${{ github.workspace }}/obs-studio + run: | + git checkout ${{ env.OBS_GIT_TAG }} + git submodule update + - name: 'Get obs-websocket git info' + shell: bash + working-directory: ${{ github.workspace }}/obs-websocket + run: | + git fetch --prune --unshallow + GIT_HASH=$(git rev-parse --short HEAD) + echo "GIT_HASH=$GIT_HASH" >> $GITHUB_ENV + GIT_TAG=$(git describe --exact-match --tags --abbrev=0) || GIT_TAG="" + echo "GIT_TAG=$GIT_TAG" >> $GITHUB_ENV + if [ "$GIT_TAG" ] ; then \ + VERSION="$GIT_TAG" \ + VERSION_SUFFIX=$(echo "$GIT_TAG" | cut -c6-20) ; \ + else \ + VERSION="$GIT_HASH-git" \ + VERSION_SUFFIX="-$GIT_HASH-git" ; \ + fi + echo "PACKAGE_VERSION=$VERSION" >> $GITHUB_ENV + echo "CMAKE_VERSION_SUFFIX=$VERSION_SUFFIX" >> $GITHUB_ENV + - name: 'Install Packages' + shell: bash + run: | + curl -L -O http://s.sudre.free.fr/Software/files/Packages.dmg + sudo hdiutil attach ./Packages.dmg + sudo installer -pkg /Volumes/Packages\ 1.2.10/Install\ Packages.pkg -target / + - name: 'Restore Cached Qt & OBS Studio dependencies' + id: deps-cache + uses: actions/cache@v2 + with: + path: ${{ github.workspace }}/obsdeps/** + key: 'deps-cache-${{ env.MACOS_DEPS_CACHE_VERSION }} | ${{ runner.os }}' + - name: 'Install Prerequisite: Qt + OBS Studio dependencies' + if: steps.deps-cache.outputs.cache-hit != 'true' + shell: bash + run: | + mkdir -p obsdeps + curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.MACOS_DEPS_VERSION }}/macos-deps-qt-${{ env.MACOS_DEPS_VERSION }}-universal.tar.xz + tar -xf macos-deps-qt-${{ env.MACOS_DEPS_VERSION }}-universal.tar.xz -C "./obsdeps" + curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.MACOS_DEPS_VERSION }}/macos-deps-${{ env.MACOS_DEPS_VERSION }}-universal.tar.xz + tar -xf macos-deps-${{ env.MACOS_DEPS_VERSION }}-universal.tar.xz -C "./obsdeps" + - run: xattr -r -d com.apple.quarantine ./obsdeps + shell: bash + - name: 'Configue OBS Studio' + if: steps.cache-obs-build.outputs.cache-hit != 'true' + working-directory: ${{ github.workspace }}/obs-studio + shell: bash + run: | + mkdir -p ./build + cd ./build + cmake .. \ + -DQTDIR=${{ github.workspace }}/obsdeps \ + -DDepsPath=${{ github.workspace }}/obsdeps \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 \ + -DDISABLE_PLUGINS=true \ + -DENABLE_SCRIPTING=0 \ + -DCMAKE_PREFIX_PATH=${{ github.workspace }}/obsdeps/lib/cmake + - name: 'Build OBS Studio' + if: steps.cache-obs-build.outputs.cache-hit != 'true' + working-directory: ${{ github.workspace }}/obs-studio/build + shell: bash + run: | + set -e + make -j4 libobs obs-frontend-api + - name: 'Configure obs-websocket' + working-directory: ${{ github.workspace }}/obs-websocket + shell: bash + run: | + mkdir -p build + cd build + cmake .. \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 \ + -DQTDIR=${{ github.workspace }}/obsdeps \ + -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs \ + -DLIBOBS_LIB=${{ github.workspace }}/obs-studio/libobs \ + -DOBS_FRONTEND_LIB="${{ github.workspace }}/obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_INSTALL_PREFIX=/usr + - name: 'Build obs-websocket' + working-directory: ${{ github.workspace }}/obs-websocket/build + shell: bash + run: | + set -e + make -j4 + - name: 'Relink Qt' + shell: bash + working-directory: ${{ github.workspace }}/obs-websocket/build + run: | + install_name_tool \ + -change /tmp/obsdeps/lib/QtWidgets.framework/Versions/5/QtWidgets \ + @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets \ + -change /tmp/obsdeps/lib/QtGui.framework/Versions/5/QtGui \ + @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui \ + -change /tmp/obsdeps/lib/QtCore.framework/Versions/5/QtCore \ + @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore \ + -change /tmp/obsdeps/lib/QtNetwork.framework/Versions/5/QtNetwork \ + @executable_path/../Frameworks/QtNetwork.framework/Versions/5/QtNetwork \ + -change /tmp/obsdeps/lib/QtSvg.framework/Versions/5/QtSvg \ + @executable_path/../Frameworks/QtSvg.framework/Versions/5/QtSvg \ + ./obs-websocket.so + - name: 'Sign plugin binary' + if: github.event_name != 'pull_request' + shell: bash + working-directory: ${{ github.workspace }}/obs-websocket/build + run: | + codesign --sign "${{ secrets.MACOS_SIGNING_IDENTITY }}" ./obs-websocket.so + - name: 'Set PR Artifact Filename' + shell: bash + run: | + echo "MACOS_FILENAME=obs-websocket-${{ env.PACKAGE_VERSION }}-macOS.pkg" >> $GITHUB_ENV + echo "MACOS_FILENAME_UNSIGNED=obs-websocket-${{ env.PACKAGE_VERSION }}-macOS-Unsigned.pkg" >> $GITHUB_ENV + - name: 'Package ${{ env.MACOS_FILENAME_UNSIGNED }}' + if: success() + working-directory: ${{ github.workspace }}/obs-websocket + shell: bash + run: | + packagesbuild ./CI/macos/obs-websocket.pkgproj + mv ./release/obs-websocket.pkg ./release/${{ env.MACOS_FILENAME_UNSIGNED }} + - name: 'Sign plugin package' + if: ${{ env.GIT_TAG != '' }} + shell: bash + working-directory: ${{ github.workspace }}/obs-websocket + run: | + productsign \ + --sign "${{ secrets.MACOS_INSTALLER_IDENTITY }}" \ + ./release/${{ env.MACOS_FILENAME_UNSIGNED }} \ + ./release/${{ env.MACOS_FILENAME }} + rm ./release/${{ env.MACOS_FILENAME_UNSIGNED }} + - name: 'Notarize package' + if: ${{ env.GIT_TAG != '' }} + shell: bash + working-directory: ${{ github.workspace }}/obs-websocket + run: | + zip -r ./release/${{ env.MACOS_FILENAME }}.zip ./release/${{ env.MACOS_FILENAME }} + UPLOAD_RESULT=$(xcrun altool --notarize-app \ + --primary-bundle-id "com.obsproject.obs-websocket" \ + --username "${{ secrets.MACOS_NOTARIZATION_USERNAME }}" \ + --password "${{ secrets.MACOS_NOTARIZATION_PASSWORD }}" \ + --asc-provider "${{ secrets.ASC_PROVIDER_SHORTNAME }}" \ + --file "./release/${{ env.MACOS_FILENAME }}.zip") + + rm ./release/${{ env.MACOS_FILENAME }}.zip + + REQUEST_UUID=$(echo $UPLOAD_RESULT | awk -F ' = ' '/RequestUUID/ {print $2}') + + # Pieces of code borrowed from rednoah/notarized-app + while sleep 30 && date; do + CHECK_RESULT=$(xcrun altool \ + --notarization-info "$REQUEST_UUID" \ + --username "${{ secrets.MACOS_NOTARIZATION_USERNAME }}" \ + --password "${{ secrets.MACOS_NOTARIZATION_PASSWORD }}" \ + --asc-provider "${{ secrets.ASC_PROVIDER_SHORTNAME }}") + + if ! grep -q "Status: in progress" <<< "$CHECK_RESULT"; then + xcrun stapler staple ./release/${{ env.MACOS_FILENAME }} + break + fi + done + - name: 'Publish Packages' + if: success() + uses: actions/upload-artifact@v2-preview + with: + name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-macOS' + path: '${{ github.workspace }}/obs-websocket/release/*.pkg' diff --git a/.gitignore b/.gitignore index 11f19da9..b234aa97 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ /docs/node_modules/ /src/plugin-macros.generated.h /installer/installer-windows.generated.iss +/cmake-build-debug/ diff --git a/CI/macos/Brewfile b/CI/macos/Brewfile deleted file mode 100644 index 923af906..00000000 --- a/CI/macos/Brewfile +++ /dev/null @@ -1,5 +0,0 @@ -brew "jack" -brew "speexdsp" -brew "cmake" -brew "freetype" -brew "fdk-aac" diff --git a/CI/macos/build-plugin-macos.sh b/CI/macos/build-plugin-macos.sh deleted file mode 100755 index 13c15806..00000000 --- a/CI/macos/build-plugin-macos.sh +++ /dev/null @@ -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 diff --git a/CI/macos/install-build-obs-macos.sh b/CI/macos/install-build-obs-macos.sh deleted file mode 100755 index 580c1dfd..00000000 --- a/CI/macos/install-build-obs-macos.sh +++ /dev/null @@ -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 diff --git a/CI/macos/install-dependencies-macos.sh b/CI/macos/install-dependencies-macos.sh deleted file mode 100755 index 8c734b8a..00000000 --- a/CI/macos/install-dependencies-macos.sh +++ /dev/null @@ -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 \ No newline at end of file diff --git a/CI/macos/obs-websocket.pkgproj b/CI/macos/obs-websocket.pkgproj index 328b60ff..d38b3a2e 100644 --- a/CI/macos/obs-websocket.pkgproj +++ b/CI/macos/obs-websocket.pkgproj @@ -514,7 +514,7 @@ CONCLUSION_ACTION 0 IDENTIFIER - fr.palakis.obs-websocket + com.obsproject.obs-websocket OVERWRITE_PERMISSIONS VERSION diff --git a/CI/macos/package-plugin-macos.sh b/CI/macos/package-plugin-macos.sh deleted file mode 100755 index 917585d1..00000000 --- a/CI/macos/package-plugin-macos.sh +++ /dev/null @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index f65f0ea0..83b00ea6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,7 @@ set(obs-websocket_SOURCES src/eventhandler/EventHandler_Outputs.cpp src/eventhandler/EventHandler_SceneItems.cpp src/eventhandler/EventHandler_MediaInputs.cpp + src/eventhandler/EventHandler_Ui.cpp src/requesthandler/RequestHandler.cpp src/requesthandler/RequestBatchHandler.cpp src/requesthandler/RequestHandler_General.cpp @@ -110,10 +111,14 @@ set(obs-websocket_SOURCES src/requesthandler/RequestHandler_Sources.cpp src/requesthandler/RequestHandler_Scenes.cpp src/requesthandler/RequestHandler_Inputs.cpp + src/requesthandler/RequestHandler_Transitions.cpp + src/requesthandler/RequestHandler_Filters.cpp src/requesthandler/RequestHandler_SceneItems.cpp + src/requesthandler/RequestHandler_Outputs.cpp src/requesthandler/RequestHandler_Stream.cpp src/requesthandler/RequestHandler_Record.cpp src/requesthandler/RequestHandler_MediaInputs.cpp + src/requesthandler/RequestHandler_Ui.cpp src/requesthandler/rpc/Request.cpp src/requesthandler/rpc/RequestBatchRequest.cpp src/requesthandler/rpc/RequestResult.cpp @@ -123,7 +128,14 @@ set(obs-websocket_SOURCES src/utils/Crypto.cpp src/utils/Json.cpp src/utils/Obs.cpp - src/utils/ObsVolumeMeter.cpp + src/utils/Obs_StringHelper.cpp + src/utils/Obs_EnumHelper.cpp + src/utils/Obs_NumberHelper.cpp + src/utils/Obs_ArrayHelper.cpp + src/utils/Obs_ObjectHelper.cpp + src/utils/Obs_SearchHelper.cpp + src/utils/Obs_ActionHelper.cpp + src/utils/Obs_VolumeMeter.cpp src/utils/Platform.cpp src/utils/Compat.cpp deps/qr/cpp/QrCode.cpp) @@ -150,8 +162,8 @@ set(obs-websocket_HEADERS src/utils/Crypto.h src/utils/Json.h src/utils/Obs.h - src/utils/ObsVolumeMeter.h - src/utils/ObsVolumeMeter_Helpers.h + src/utils/Obs_VolumeMeter.h + src/utils/Obs_VolumeMeter_Helpers.h src/utils/Platform.h src/utils/Compat.h src/utils/Utils.h diff --git a/README.md b/README.md index 85843f8e..86056256 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ WebSocket API for OBS Studio. -[![CI Multiplatform Build](https://github.com/obsproject/obs-websocket/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/obs-websocket/obs-websocket/actions/workflows/main.yml) +[![CI Multiplatform Build](https://github.com/obsproject/obs-websocket/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/obsproject/obs-websocket/actions/workflows/main.yml) [![Discord](https://img.shields.io/discord/715691013825364120.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/WBaSQ3A) [![Financial Contributors on Open Collective](https://opencollective.com/obs-websocket-dev/all/badge.svg?label=financial+contributors)](https://opencollective.com/obs-websocket-dev) @@ -30,7 +30,7 @@ It is **highly recommended** to protect obs-websocket with a password against un ### Client software -- (No known clients supporting 5.0.0 at the moment. Send a message in Discord if you have one!) +- (No known clients supporting 5.0.0 at the moment. Ping us in the Discord if you have one!) ### Client libraries (for developers) @@ -39,10 +39,10 @@ Here's a list of available language APIs for obs-websocket: - Python 3.7+ (Asyncio): [simpleobsws](https://github.com/IRLToolkit/simpleobsws/tree/master) by IRLToolkit - Rust: [obws](https://github.com/dnaka91/obws/tree/v5-api) by dnaka91 -The server is a typical Websockets server running by default on port 4444 (the port number can be changed in the Settings dialog under `Tools`). +The 5.x server is a typical WebSocket server running by default on port 4455 (the port number can be changed in the Settings dialog under `Tools`). The protocol we use is documented in [PROTOCOL.md](docs/generated/protocol.md). -We'd like to know what you're building with or for obs-websocket. If you do something in this fashion, feel free to drop a message in `#project-showoff` in the [discord server!](https://discord.gg/WBaSQ3A) +We'd like to know what you're building with obs-websocket! If you do something in this fashion, feel free to drop a message in `#project-showoff` in the [discord server!](https://discord.gg/WBaSQ3A) ## Contributors diff --git a/deps/asio b/deps/asio index b84e6c16..b73dc1d2 160000 --- a/deps/asio +++ b/deps/asio @@ -1 +1 @@ -Subproject commit b84e6c16b2ea907dbad94206b7510d85aafc0b42 +Subproject commit b73dc1d2c0ecb9452a87c26544d7f71e24342df6 diff --git a/docs/docs/generate_md.py b/docs/docs/generate_md.py index bc25f5fb..b52383e7 100644 --- a/docs/docs/generate_md.py +++ b/docs/docs/generate_md.py @@ -25,6 +25,7 @@ categoryOrder = [ 'Stream', 'Record', 'Media Inputs', + 'Ui', 'High-Volume' ] diff --git a/docs/docs/process_comments.py b/docs/docs/process_comments.py index db52914c..3868eb5e 100644 --- a/docs/docs/process_comments.py +++ b/docs/docs/process_comments.py @@ -122,7 +122,7 @@ for comment in comments_raw: enumValue = field_to_string(comment['enumValue']) enum['enumValue'] = int(enumValue) if enumValue.isdigit() else enumValue else: - enum['enumValue'] = None + enum['enumValue'] = enum['enumIdentifier'] if enumType not in enums_raw: enums_raw[enumType] = {'enumIdentifiers': [enum]} diff --git a/docs/generated/protocol.json b/docs/generated/protocol.json index bd906beb..16ac5dfa 100644 --- a/docs/generated/protocol.json +++ b/docs/generated/protocol.json @@ -91,6 +91,14 @@ "initialVersion": "5.0.0", "enumValue": "(1 << 9)" }, + { + "description": "Subscription value to receive events in the `Ui` category.", + "enumIdentifier": "Ui", + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "enumValue": "(1 << 10)" + }, { "description": "Helper to receive all non-high-volume events.", "enumIdentifier": "All", @@ -389,6 +397,22 @@ "initialVersion": "5.0.0", "enumValue": 605 }, + { + "description": "The resource does not support being configured.\n\nThis is particularly relevant to transitions, where they do not always have changeable settings.", + "enumIdentifier": "ResourceNotConfigurable", + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "enumValue": 606 + }, + { + "description": "The specified filter (obs_source_t-OBS_SOURCE_TYPE_FILTER) had the wrong kind.", + "enumIdentifier": "InvalidFilterKind", + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "enumValue": 607 + }, { "description": "Creating the resource failed.", "enumIdentifier": "ResourceCreationFailed", @@ -423,6 +447,67 @@ } ] }, + { + "enumType": "ObsMediaInputAction", + "enumIdentifiers": [ + { + "description": "No action.", + "enumIdentifier": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE", + "rpcVersion": 1, + "deprecated": true, + "initialVersion": "5.0.0", + "enumValue": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE" + }, + { + "description": "Play the media input.", + "enumIdentifier": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY", + "rpcVersion": 1, + "deprecated": true, + "initialVersion": "5.0.0", + "enumValue": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY" + }, + { + "description": "Pause the media input.", + "enumIdentifier": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE", + "rpcVersion": 1, + "deprecated": true, + "initialVersion": "5.0.0", + "enumValue": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE" + }, + { + "description": "Stop the media input.", + "enumIdentifier": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP", + "rpcVersion": 1, + "deprecated": true, + "initialVersion": "5.0.0", + "enumValue": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP" + }, + { + "description": "Restart the media input.", + "enumIdentifier": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART", + "rpcVersion": 1, + "deprecated": true, + "initialVersion": "5.0.0", + "enumValue": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART" + }, + { + "description": "Go to the next playlist item.", + "enumIdentifier": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT", + "rpcVersion": 1, + "deprecated": true, + "initialVersion": "5.0.0", + "enumValue": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT" + }, + { + "description": "Go to the previous playlist item.", + "enumIdentifier": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS", + "rpcVersion": 1, + "deprecated": true, + "initialVersion": "5.0.0", + "enumValue": "OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS" + } + ] + }, { "enumType": "WebSocketCloseCode", "enumIdentifiers": [ @@ -828,7 +913,7 @@ { "description": "Gets a parameter from the current profile's configuration.", "requestType": "GetProfileParameter", - "complexity": 3, + "complexity": 4, "rpcVersion": "1", "deprecated": false, "initialVersion": "5.0.0", @@ -867,7 +952,7 @@ { "description": "Sets the value of a parameter in the current profile's configuration.", "requestType": "SetProfileParameter", - "complexity": 3, + "complexity": 4, "rpcVersion": "1", "deprecated": false, "initialVersion": "5.0.0", @@ -1052,6 +1137,312 @@ ], "responseFields": [] }, + { + "description": "Gets the current directory that the record output is set to.", + "requestType": "GetRecordDirectory", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "rconfig", + "requestFields": [], + "responseFields": [ + { + "valueName": "recordDirectory", + "valueType": "String", + "valueDescription": "Output directory" + } + ] + }, + { + "description": "Gets an array of all of a source's filters.", + "requestType": "GetSourceFilterList", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "filters", + "requestFields": [ + { + "valueName": "sourceName", + "valueType": "String", + "valueDescription": "Name of the source", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [ + { + "valueName": "filters", + "valueType": "Array", + "valueDescription": "Array of filters" + } + ] + }, + { + "description": "Gets the default settings for a filter kind.", + "requestType": "GetSourceFilterDefaultSettings", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "filters", + "requestFields": [ + { + "valueName": "filterKind", + "valueType": "String", + "valueDescription": "Filter kind to get the default settings for", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [ + { + "valueName": "defaultFilterSettings", + "valueType": "Object", + "valueDescription": "Object of default settings for the filter kind" + } + ] + }, + { + "description": "Creates a new filter, adding it to the specified source.", + "requestType": "CreateSourceFilter", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "filters", + "requestFields": [ + { + "valueName": "sourceName", + "valueType": "String", + "valueDescription": "Name of the source to add the filter to", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "filterName", + "valueType": "String", + "valueDescription": "Name of the new filter to be created", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "filterKind", + "valueType": "String", + "valueDescription": "The kind of filter to be created", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "filterSettings", + "valueType": "Object", + "valueDescription": "Settings object to initialize the filter with", + "valueRestrictions": null, + "valueOptional": true, + "valueOptionalBehavior": "Default settings used" + } + ], + "responseFields": [] + }, + { + "description": "Removes a filter from a source.", + "requestType": "RemoveSourceFilter", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "filters", + "requestFields": [ + { + "valueName": "sourceName", + "valueType": "String", + "valueDescription": "Name of the source the filter is on", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "filterName", + "valueType": "String", + "valueDescription": "Name of the filter to remove", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, + { + "description": "Sets the name of a source filter (rename).", + "requestType": "SetSourceFilterName", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "filters", + "requestFields": [ + { + "valueName": "sourceName", + "valueType": "String", + "valueDescription": "Name of the source the filter is on", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "filterName", + "valueType": "String", + "valueDescription": "Current name of the filter", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "newFilterName", + "valueType": "String", + "valueDescription": "New name for the filter", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, + { + "description": "Gets the info for a specific source filter.", + "requestType": "GetSourceFilter", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "filters", + "requestFields": [ + { + "valueName": "sourceName", + "valueType": "String", + "valueDescription": "Name of the source", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "filterName", + "valueType": "String", + "valueDescription": "Name of the filter", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [ + { + "valueName": "filterEnabled", + "valueType": "Boolean", + "valueDescription": "Whether the filter is enabled" + }, + { + "valueName": "filterIndex", + "valueType": "Number", + "valueDescription": "Index of the filter in the list, beginning at 0" + }, + { + "valueName": "filterKind", + "valueType": "String", + "valueDescription": "The kind of filter" + }, + { + "valueName": "filterSettings", + "valueType": "Object", + "valueDescription": "Settings object associated with the filter" + } + ] + }, + { + "description": "Sets the index position of a filter on a source.", + "requestType": "SetSourceFilterIndex", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "filters", + "requestFields": [ + { + "valueName": "sourceName", + "valueType": "String", + "valueDescription": "Name of the source the filter is on", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "filterName", + "valueType": "String", + "valueDescription": "Name of the filter", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "filterIndex", + "valueType": "Number", + "valueDescription": "New index position of the filter", + "valueRestrictions": ">= 0", + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, + { + "description": "Sets the settings of a source filter.", + "requestType": "SetSourceFilterSettings", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "filters", + "requestFields": [ + { + "valueName": "sourceName", + "valueType": "String", + "valueDescription": "Name of the source the filter is on", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "filterName", + "valueType": "String", + "valueDescription": "Name of the filter to set the settings of", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "filterSettings", + "valueType": "Object", + "valueDescription": "Object of settings to apply", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "overlay", + "valueType": "Boolean", + "valueDescription": "True == apply the settings on top of existing ones, False == reset the input to its defaults, then apply settings.", + "valueRestrictions": null, + "valueOptional": true, + "valueOptionalBehavior": "true" + } + ], + "responseFields": [] + }, { "description": "Gets data about the current plugin and RPC version.", "requestType": "GetVersion", @@ -1315,43 +1706,6 @@ ], "responseFields": [] }, - { - "description": "Gets whether studio is enabled.", - "requestType": "GetStudioModeEnabled", - "complexity": 1, - "rpcVersion": "1", - "deprecated": false, - "initialVersion": "5.0.0", - "category": "general", - "requestFields": [], - "responseFields": [ - { - "valueName": "studioModeEnabled", - "valueType": "Boolean", - "valueDescription": "Whether studio mode is enabled" - } - ] - }, - { - "description": "Enables or disables studio mode", - "requestType": "SetStudioModeEnabled", - "complexity": 1, - "rpcVersion": "1", - "deprecated": false, - "initialVersion": "5.0.0", - "category": "general", - "requestFields": [ - { - "valueName": "studioModeEnabled", - "valueType": "Boolean", - "valueDescription": "True == Enabled, False == Disabled", - "valueRestrictions": null, - "valueOptional": false, - "valueOptionalBehavior": null - } - ], - "responseFields": [] - }, { "description": "Sleeps for a time duration or number of frames. Only available in request batches with types `SERIAL_REALTIME` or `SERIAL_FRAME`.", "requestType": "Sleep", @@ -1432,6 +1786,48 @@ } ] }, + { + "description": "Gets the names of all special inputs.", + "requestType": "GetSpecialInputs", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "inputs", + "requestFields": [], + "responseFields": [ + { + "valueName": "desktop1", + "valueType": "String", + "valueDescription": "Name of the Desktop Audio input" + }, + { + "valueName": "desktop2", + "valueType": "String", + "valueDescription": "Name of the Desktop Audio 2 input" + }, + { + "valueName": "mic1", + "valueType": "String", + "valueDescription": "Name of the Mic/Auxiliary Audio input" + }, + { + "valueName": "mic2", + "valueType": "String", + "valueDescription": "Name of the Mic/Auxiliary Audio 2 input" + }, + { + "valueName": "mic3", + "valueType": "String", + "valueDescription": "Name of the Mic/Auxiliary Audio 3 input" + }, + { + "valueName": "mic4", + "valueType": "String", + "valueDescription": "Name of the Mic/Auxiliary Audio 4 input" + } + ] + }, { "description": "Creates a new input, adding it as a scene item to the specified scene.", "requestType": "CreateInput", @@ -1619,6 +2015,14 @@ "valueRestrictions": null, "valueOptional": false, "valueOptionalBehavior": null + }, + { + "valueName": "overlay", + "valueType": "Boolean", + "valueDescription": "True == apply the settings on top of existing ones, False == reset the input to its defaults, then apply settings.", + "valueRestrictions": null, + "valueOptional": true, + "valueOptionalBehavior": "true" } ], "responseFields": [] @@ -1763,13 +2167,67 @@ "valueName": "inputVolumeDb", "valueType": "Number", "valueDescription": "Volume setting in dB", - "valueRestrictions": ">= -100, <= -26", + "valueRestrictions": ">= -100, <= 26", "valueOptional": true, "valueOptionalBehavior": "`inputVolumeMul` should be specified" } ], "responseFields": [] }, + { + "description": "Gets the audio balance of an input.", + "requestType": "GetInputAudioBalance", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "inputs", + "requestFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input to get the audio balance of", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [ + { + "valueName": "inputAudioBalance", + "valueType": "Number", + "valueDescription": "Audio balance value from 0.0-1.0" + } + ] + }, + { + "description": "Sets the audio balance of an input.", + "requestType": "SetInputAudioBalance", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "inputs", + "requestFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input to set the audio balance of", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "inputAudioBalance", + "valueType": "Number", + "valueDescription": "New audio balance value", + "valueRestrictions": ">= 0.0, <= 1.0", + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, { "description": "Gets the audio sync offset of an input.\n\nNote: The audio sync offset can be negative too!", "requestType": "GetInputAudioSyncOffset", @@ -1878,6 +2336,60 @@ ], "responseFields": [] }, + { + "description": "Gets the enable state of all audio tracks of an input.", + "requestType": "GetInputAudioTracks", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "inputs", + "requestFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [ + { + "valueName": "inputAudioTracks", + "valueType": "Object", + "valueDescription": "Object of audio tracks and associated enable states" + } + ] + }, + { + "description": "Sets the enable state of audio tracks of an input.", + "requestType": "SetInputAudioTracks", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "inputs", + "requestFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "inputAudioTracks", + "valueType": "Object", + "valueDescription": "Track settings to apply", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, { "description": "Gets the items of a list property from an input's properties.\n\nNote: Use this in cases where an input provides a dynamic, selectable list of items. For example, display capture, where it provides a list of available displays.", "requestType": "GetInputPropertiesListPropertyItems", @@ -1940,6 +2452,917 @@ ], "responseFields": [] }, + { + "description": "Gets the status of a media input.\n\nMedia States:\n- `OBS_MEDIA_STATE_NONE`\n- `OBS_MEDIA_STATE_PLAYING`\n- `OBS_MEDIA_STATE_OPENING`\n- `OBS_MEDIA_STATE_BUFFERING`\n- `OBS_MEDIA_STATE_PAUSED`\n- `OBS_MEDIA_STATE_STOPPED`\n- `OBS_MEDIA_STATE_ENDED`\n- `OBS_MEDIA_STATE_ERROR`", + "requestType": "GetMediaInputStatus", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "media inputs", + "requestFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the media input", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [ + { + "valueName": "mediaState", + "valueType": "String", + "valueDescription": "State of the media input" + }, + { + "valueName": "mediaDuration", + "valueType": "Number", + "valueDescription": "Total duration of the playing media in milliseconds. `null` if not playing" + }, + { + "valueName": "mediaCursor", + "valueType": "Number", + "valueDescription": "Position of the cursor in milliseconds. `null` if not playing" + } + ] + }, + { + "description": "Sets the cursor position of a media input.\n\nThis request does not perform bounds checking of the cursor position.", + "requestType": "SetMediaInputCursor", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "media inputs", + "requestFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the media input", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "mediaCursor", + "valueType": "Number", + "valueDescription": "New cursor position to set", + "valueRestrictions": ">= 0", + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, + { + "description": "Offsets the current cursor position of a media input by the specified value.\n\nThis request does not perform bounds checking of the cursor position.", + "requestType": "OffsetMediaInputCursor", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "media inputs", + "requestFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the media input", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "mediaCursorOffset", + "valueType": "Number", + "valueDescription": "Value to offset the current cursor position by", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, + { + "description": "Triggers an action on a media input.", + "requestType": "TriggerMediaInputAction", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "media inputs", + "requestFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the media input", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "mediaAction", + "valueType": "String", + "valueDescription": "Identifier of the `ObsMediaInputAction` enum", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, + { + "description": "Gets the status of the virtualcam output.", + "requestType": "GetVirtualCamStatus", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "outputs", + "requestFields": [], + "responseFields": [ + { + "valueName": "outputActive", + "valueType": "Boolean", + "valueDescription": "Whether the output is active" + } + ] + }, + { + "description": "Toggles the state of the virtualcam output.", + "requestType": "ToggleVirtualCam", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "outputs", + "requestFields": [], + "responseFields": [ + { + "valueName": "outputActive", + "valueType": "Boolean", + "valueDescription": "Whether the output is active" + } + ] + }, + { + "description": "Starts the virtualcam output.", + "requestType": "StartVirtualCam", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "outputs", + "requestFields": [], + "responseFields": [] + }, + { + "description": "Stops the virtualcam output.", + "requestType": "StopVirtualCam", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "outputs", + "requestFields": [], + "responseFields": [] + }, + { + "description": "Gets the status of the replay buffer output.", + "requestType": "GetReplayBufferStatus", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "outputs", + "requestFields": [], + "responseFields": [ + { + "valueName": "outputActive", + "valueType": "Boolean", + "valueDescription": "Whether the output is active" + } + ] + }, + { + "description": "Toggles the state of the replay buffer output.", + "requestType": "ToggleReplayBuffer", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "outputs", + "requestFields": [], + "responseFields": [ + { + "valueName": "outputActive", + "valueType": "Boolean", + "valueDescription": "Whether the output is active" + } + ] + }, + { + "description": "Starts the replay buffer output.", + "requestType": "StartReplayBuffer", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "outputs", + "requestFields": [], + "responseFields": [] + }, + { + "description": "Stops the replay buffer output.", + "requestType": "StopReplayBuffer", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "outputs", + "requestFields": [], + "responseFields": [] + }, + { + "description": "Saves the contents of the replay buffer output.", + "requestType": "SaveReplayBuffer", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "outputs", + "requestFields": [], + "responseFields": [] + }, + { + "description": "Gets the filename of the last replay buffer save file.", + "requestType": "GetLastReplayBufferReplay", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "outputs", + "requestFields": [], + "responseFields": [ + { + "valueName": "savedReplayPath", + "valueType": "String", + "valueDescription": "File path" + } + ] + }, + { + "description": "Gets the status of the record output.", + "requestType": "GetRecordStatus", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "record", + "requestFields": [], + "responseFields": [ + { + "valueName": "outputActive", + "valueType": "Boolean", + "valueDescription": "Whether the output is active" + }, + { + "valueName": "ouputPaused", + "valueType": "Boolean", + "valueDescription": "Whether the output is paused" + }, + { + "valueName": "outputTimecode", + "valueType": "String", + "valueDescription": "Current formatted timecode string for the output" + }, + { + "valueName": "outputDuration", + "valueType": "Number", + "valueDescription": "Current duration in milliseconds for the output" + }, + { + "valueName": "outputBytes", + "valueType": "Number", + "valueDescription": "Number of bytes sent by the output" + } + ] + }, + { + "description": "Toggles the status of the record output.", + "requestType": "ToggleRecord", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "record", + "requestFields": [], + "responseFields": [] + }, + { + "description": "Starts the record output.", + "requestType": "StartRecord", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "record", + "requestFields": [], + "responseFields": [] + }, + { + "description": "Stops the record output.", + "requestType": "StopRecord", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "record", + "requestFields": [], + "responseFields": [] + }, + { + "description": "Toggles pause on the record output.", + "requestType": "ToggleRecordPause", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "record", + "requestFields": [], + "responseFields": [] + }, + { + "description": "Pauses the record output.", + "requestType": "PauseRecord", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "record", + "requestFields": [], + "responseFields": [] + }, + { + "description": "Resumes the record output.", + "requestType": "ResumeRecord", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "record", + "requestFields": [], + "responseFields": [] + }, + { + "description": "Gets a list of all scene items in a scene.\n\nScenes only", + "requestType": "GetSceneItemList", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene to get the items of", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [ + { + "valueName": "sceneItems", + "valueType": "Array", + "valueDescription": "Array of scene items in the scene" + } + ] + }, + { + "description": "Basically GetSceneItemList, but for groups.\n\nUsing groups at all in OBS is discouraged, as they are very broken under the hood.\n\nGroups only", + "requestType": "GetGroupItemList", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the group to get the items of", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [ + { + "valueName": "sceneItems", + "valueType": "Array", + "valueDescription": "Array of scene items in the group" + } + ] + }, + { + "description": "Searches a scene for a source, and returns its id.\n\nScenes and Groups", + "requestType": "GetSceneItemId", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene or group to search in", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sourceName", + "valueType": "String", + "valueDescription": "Name of the source to find", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [ + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item" + } + ] + }, + { + "description": "Creates a new scene item using a source.\n\nScenes only", + "requestType": "CreateSceneItem", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene to create the new item in", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sourceName", + "valueType": "String", + "valueDescription": "Name of the source to add to the scene", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemEnabled", + "valueType": "Boolean", + "valueDescription": "Enable state to apply to the scene item on creation", + "valueRestrictions": null, + "valueOptional": true, + "valueOptionalBehavior": "True" + } + ], + "responseFields": [ + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item" + } + ] + }, + { + "description": "Removes a scene item from a scene.\n\nScenes only", + "requestType": "RemoveSceneItem", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene the item is in", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item", + "valueRestrictions": ">= 0", + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, + { + "description": "Duplicates a scene item, copying all transform and crop info.\n\nScenes only", + "requestType": "DuplicateSceneItem", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene the item is in", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item", + "valueRestrictions": ">= 0", + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "destinationSceneName", + "valueType": "String", + "valueDescription": "Name of the scene to create the duplicated item in", + "valueRestrictions": null, + "valueOptional": true, + "valueOptionalBehavior": "`sceneName` is assumed" + } + ], + "responseFields": [ + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the duplicated scene item" + } + ] + }, + { + "description": "Gets the transform and crop info of a scene item.\n\nScenes and Groups", + "requestType": "GetSceneItemTransform", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene the item is in", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item", + "valueRestrictions": ">= 0", + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [ + { + "valueName": "sceneItemTransform", + "valueType": "Object", + "valueDescription": "Object containing scene item transform info" + } + ] + }, + { + "description": "Sets the transform and crop info of a scene item.", + "requestType": "SetSceneItemTransform", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene the item is in", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item", + "valueRestrictions": ">= 0", + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemTransform", + "valueType": "Object", + "valueDescription": "Object containing scene item transform info to update", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, + { + "description": "Gets the enable state of a scene item.\n\nScenes and Groups", + "requestType": "GetSceneItemEnabled", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene the item is in", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item", + "valueRestrictions": ">= 0", + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [ + { + "valueName": "sceneItemEnabled", + "valueType": "Boolean", + "valueDescription": "Whether the scene item is enabled. `true` for enabled, `false` for disabled" + } + ] + }, + { + "description": "Sets the enable state of a scene item.\n\nScenes and Groups", + "requestType": "SetSceneItemEnabled", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene the item is in", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item", + "valueRestrictions": ">= 0", + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemEnabled", + "valueType": "Boolean", + "valueDescription": "New enable state of the scene item", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, + { + "description": "Gets the lock state of a scene item.\n\nScenes and Groups", + "requestType": "GetSceneItemLocked", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene the item is in", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item", + "valueRestrictions": ">= 0", + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [ + { + "valueName": "sceneItemLocked", + "valueType": "Boolean", + "valueDescription": "Whether the scene item is locked. `true` for locked, `false` for unlocked" + } + ] + }, + { + "description": "Sets the lock state of a scene item.\n\nScenes and Group", + "requestType": "SetSceneItemLocked", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene the item is in", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item", + "valueRestrictions": ">= 0", + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemLocked", + "valueType": "Boolean", + "valueDescription": "New lock state of the scene item", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, + { + "description": "Gets the index position of a scene item in a scene.\n\nAn index of 0 is at the bottom of the source list in the UI.\n\nScenes and Groups", + "requestType": "GetSceneItemIndex", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene the item is in", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item", + "valueRestrictions": ">= 0", + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [ + { + "valueName": "sceneItemIndex", + "valueType": "Number", + "valueDescription": "Index position of the scene item" + } + ] + }, + { + "description": "Sets the index position of a scene item in a scene.\n\nScenes and Groups", + "requestType": "SetSceneItemIndex", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene the item is in", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item", + "valueRestrictions": ">= 0", + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemIndex", + "valueType": "Number", + "valueDescription": "New index position of the scene item", + "valueRestrictions": ">= 0", + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, + { + "description": "Gets the blend mode of a scene item.\n\nBlend modes:\n\n- `OBS_BLEND_NORMAL`\n- `OBS_BLEND_ADDITIVE`\n- `OBS_BLEND_SUBTRACT`\n- `OBS_BLEND_SCREEN`\n- `OBS_BLEND_MULTIPLY`\n- `OBS_BLEND_LIGHTEN`\n- `OBS_BLEND_DARKEN`\n\nScenes and Groups", + "requestType": "GetSceneItemBlendMode", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene the item is in", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item", + "valueRestrictions": ">= 0", + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [ + { + "valueName": "sceneItemBlendMode", + "valueType": "String", + "valueDescription": "Current blend mode" + } + ] + }, + { + "description": "Sets the blend mode of a scene item.\n\nScenes and Groups", + "requestType": "SetSceneItemBlendMode", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene the item is in", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item", + "valueRestrictions": ">= 0", + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "sceneItemBlendMode", + "valueType": "String", + "valueDescription": "New blend mode", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, { "description": "Gets an array of all scenes in OBS.", "requestType": "GetSceneList", @@ -1950,11 +3373,6 @@ "category": "scenes", "requestFields": [], "responseFields": [ - { - "valueName": "scenes", - "valueType": "Array", - "valueDescription": "Array of scenes in OBS" - }, { "valueName": "currentProgramSceneName", "valueType": "String", @@ -1964,6 +3382,28 @@ "valueName": "currentPreviewSceneName", "valueType": "String", "valueDescription": "Current preview scene. `null` if not in studio mode" + }, + { + "valueName": "scenes", + "valueType": "Array", + "valueDescription": "Array of scenes" + } + ] + }, + { + "description": "Gets an array of all groups in OBS.\n\nGroups in OBS are actually scenes, but renamed and modified. In obs-websocket, we treat them as scenes where we can.", + "requestType": "GetGroupList", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scenes", + "requestFields": [], + "responseFields": [ + { + "valueName": "groups", + "valueType": "Array", + "valueDescription": "Array of group names" } ] }, @@ -2109,6 +3549,73 @@ ], "responseFields": [] }, + { + "description": "Gets the scene transition overridden for a scene.", + "requestType": "GetSceneSceneTransitionOverride", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scenes", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [ + { + "valueName": "transitionName", + "valueType": "String", + "valueDescription": "Name of the overridden scene transition, else `null`" + }, + { + "valueName": "transitionDuration", + "valueType": "Number", + "valueDescription": "Duration of the overridden scene transition, else `null`" + } + ] + }, + { + "description": "Gets the scene transition overridden for a scene.", + "requestType": "SetSceneSceneTransitionOverride", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scenes", + "requestFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "transitionName", + "valueType": "String", + "valueDescription": "Name of the scene transition to use as override. Specify `null` to remove", + "valueRestrictions": null, + "valueOptional": true, + "valueOptionalBehavior": "Unchanged" + }, + { + "valueName": "transitionDuration", + "valueType": "Number", + "valueDescription": "Duration to use for any overridden transition. Specify `null` to remove", + "valueRestrictions": ">= 50, <= 20000", + "valueOptional": true, + "valueOptionalBehavior": "Unchanged" + } + ], + "responseFields": [] + }, { "description": "Gets the active and show state of a source.\n\n**Compatible with inputs and scenes.**", "requestType": "GetSourceActive", @@ -2263,6 +3770,419 @@ "valueDescription": "Base64-encoded screenshot" } ] + }, + { + "description": "Gets the status of the stream output.", + "requestType": "GetStreamStatus", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "stream", + "requestFields": [], + "responseFields": [ + { + "valueName": "outputActive", + "valueType": "Boolean", + "valueDescription": "Whether the output is active" + }, + { + "valueName": "outputReconnecting", + "valueType": "Boolean", + "valueDescription": "Whether the output is currently reconnecting" + }, + { + "valueName": "outputTimecode", + "valueType": "String", + "valueDescription": "Current formatted timecode string for the output" + }, + { + "valueName": "outputDuration", + "valueType": "Number", + "valueDescription": "Current duration in milliseconds for the output" + }, + { + "valueName": "outputBytes", + "valueType": "Number", + "valueDescription": "Number of bytes sent by the output" + }, + { + "valueName": "outputSkippedFrames", + "valueType": "Number", + "valueDescription": "Number of frames skipped by the output's process" + }, + { + "valueName": "outputTotalFrames", + "valueType": "Number", + "valueDescription": "Total number of frames delivered by the output's process" + } + ] + }, + { + "description": "Toggles the status of the stream output.", + "requestType": "ToggleStream", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "stream", + "requestFields": [], + "responseFields": [ + { + "valueName": "outputActive", + "valueType": "Boolean", + "valueDescription": "New state of the stream output" + } + ] + }, + { + "description": "Starts the stream output.", + "requestType": "StartStream", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "stream", + "requestFields": [], + "responseFields": [] + }, + { + "description": "Stops the stream output.", + "requestType": "StopStream", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "stream", + "requestFields": [], + "responseFields": [] + }, + { + "description": "Sends CEA-608 caption text over the stream output.", + "requestType": "SendStreamCaption", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "stream", + "requestFields": [ + { + "valueName": "captionText", + "valueType": "String", + "valueDescription": "Caption text", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, + { + "description": "Gets an array of all available transition kinds.\n\nSimilar to `GetInputKindList`", + "requestType": "GetTransitionKindList", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "transitions", + "requestFields": [], + "responseFields": [ + { + "valueName": "transitionKinds", + "valueType": "Array", + "valueDescription": "Array of transition kinds" + } + ] + }, + { + "description": "Gets an array of all scene transitions in OBS.", + "requestType": "GetSceneTransitionList", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "transitions", + "requestFields": [], + "responseFields": [ + { + "valueName": "currentSceneTransitionName", + "valueType": "String", + "valueDescription": "Name of the current scene transition. Can be null" + }, + { + "valueName": "currentSceneTransitionKind", + "valueType": "String", + "valueDescription": "Kind of the current scene transition. Can be null" + }, + { + "valueName": "transitions", + "valueType": "Array", + "valueDescription": "Array of transitions" + } + ] + }, + { + "description": "Gets information about the current scene transition.", + "requestType": "GetCurrentSceneTransition", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "transitions", + "requestFields": [], + "responseFields": [ + { + "valueName": "transitionName", + "valueType": "String", + "valueDescription": "Name of the transition" + }, + { + "valueName": "transitionKind", + "valueType": "String", + "valueDescription": "Kind of the transition" + }, + { + "valueName": "transitionFixed", + "valueType": "Boolean", + "valueDescription": "Whether the transition uses a fixed (unconfigurable) duration" + }, + { + "valueName": "transitionDuration", + "valueType": "Number", + "valueDescription": "Configured transition duration in milliseconds. `null` if transition is fixed" + }, + { + "valueName": "transitionConfigurable", + "valueType": "Boolean", + "valueDescription": "Whether the transition supports being configured" + }, + { + "valueName": "transitionSettings", + "valueType": "Object", + "valueDescription": "Object of settings for the transition. `null` if transition is not configurable" + } + ] + }, + { + "description": "Sets the current scene transition.\n\nSmall note: While the namespace of scene transitions is generally unique, that uniqueness is not a guarantee as it is with other resources like inputs.", + "requestType": "SetCurrentSceneTransition", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "transitions", + "requestFields": [ + { + "valueName": "transitionName", + "valueType": "String", + "valueDescription": "Name of the transition to make active", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, + { + "description": "Sets the duration of the current scene transition, if it is not fixed.", + "requestType": "SetCurrentSceneTransitionDuration", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "transitions", + "requestFields": [ + { + "valueName": "transitionDuration", + "valueType": "Number", + "valueDescription": "Duration in milliseconds", + "valueRestrictions": ">= 50, <= 20000", + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, + { + "description": "Sets the settings of the current scene transition.", + "requestType": "SetCurrentSceneTransitionSettings", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "transitions", + "requestFields": [ + { + "valueName": "transitionSettings", + "valueType": "Object", + "valueDescription": "Settings object to apply to the transition. Can be `{}`", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "overlay", + "valueType": "Boolean", + "valueDescription": "Whether to overlay over the current settings or replace them", + "valueRestrictions": null, + "valueOptional": true, + "valueOptionalBehavior": "true" + } + ], + "responseFields": [] + }, + { + "description": "Gets the cursor position of the current scene transition.\n\nNote: `transitionCursor` will return 1.0 when the transition is inactive.", + "requestType": "GetCurrentSceneTransitionCursor", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "transitions", + "requestFields": [], + "responseFields": [ + { + "valueName": "transitionCursor", + "valueType": "Number", + "valueDescription": "Cursor position, between 0.0 and 1.0" + } + ] + }, + { + "description": "Triggers the current scene transition. Same functionality as the `Transition` button in studio mode.", + "requestType": "TriggerStudioModeTransition", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "transitions", + "requestFields": [], + "responseFields": [] + }, + { + "description": "Sets the position of the TBar.\n\n**Very important note**: This will be deprecated and replaced in a future version of obs-websocket.", + "requestType": "SetTBarPosition", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "transitions", + "requestFields": [ + { + "valueName": "position", + "valueType": "Number", + "valueDescription": "New position", + "valueRestrictions": ">= 0.0, <= 1.0", + "valueOptional": false, + "valueOptionalBehavior": null + }, + { + "valueName": "release", + "valueType": "Boolean", + "valueDescription": "Whether to release the TBar. Only set `false` if you know that you will be sending another position update", + "valueRestrictions": null, + "valueOptional": true, + "valueOptionalBehavior": "`true`" + } + ], + "responseFields": [] + }, + { + "description": "Gets whether studio is enabled.", + "requestType": "GetStudioModeEnabled", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "ui", + "requestFields": [], + "responseFields": [ + { + "valueName": "studioModeEnabled", + "valueType": "Boolean", + "valueDescription": "Whether studio mode is enabled" + } + ] + }, + { + "description": "Enables or disables studio mode", + "requestType": "SetStudioModeEnabled", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "ui", + "requestFields": [ + { + "valueName": "studioModeEnabled", + "valueType": "Boolean", + "valueDescription": "True == Enabled, False == Disabled", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, + { + "description": "Opens the properties dialog of an input.", + "requestType": "OpenInputPropertiesDialog", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "ui", + "requestFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input to open the dialog of", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, + { + "description": "Opens the filters dialog of an input.", + "requestType": "OpenInputFiltersDialog", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "ui", + "requestFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input to open the dialog of", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] + }, + { + "description": "Opens the interact dialog of an input.", + "requestType": "OpenInputInteractDialog", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "ui", + "requestFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input to open the dialog of", + "valueRestrictions": null, + "valueOptional": false, + "valueOptionalBehavior": null + } + ], + "responseFields": [] } ], "events": [ @@ -2368,6 +4288,146 @@ } ] }, + { + "description": "A filter has been added to a source.", + "eventType": "SourceFilterCreated", + "eventSubscription": "Filters", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "filters", + "dataFields": [ + { + "valueName": "sourceName", + "valueType": "String", + "valueDescription": "Name of the source the filter was added to" + }, + { + "valueName": "filterName", + "valueType": "String", + "valueDescription": "Name of the filter" + }, + { + "valueName": "filterKind", + "valueType": "String", + "valueDescription": "The kind of the filter" + }, + { + "valueName": "filterIndex", + "valueType": "Number", + "valueDescription": "Index position of the filter" + }, + { + "valueName": "filterSettings", + "valueType": "Object", + "valueDescription": "The settings configured to the filter when it was created" + }, + { + "valueName": "defaultFilterSettings", + "valueType": "Object", + "valueDescription": "The default settings for the filter" + } + ] + }, + { + "description": "A filter has been removed from a source.", + "eventType": "SourceFilterRemoved", + "eventSubscription": "Filters", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "filters", + "dataFields": [ + { + "valueName": "sourceName", + "valueType": "String", + "valueDescription": "Name of the source the filter was on" + }, + { + "valueName": "filterName", + "valueType": "String", + "valueDescription": "Name of the filter" + } + ] + }, + { + "description": "A source's filter list has been reindexed.", + "eventType": "SourceFilterListReindexed", + "eventSubscription": "Filters", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "filters", + "dataFields": [ + { + "valueName": "sourceName", + "valueType": "String", + "valueDescription": "Name of the source" + }, + { + "valueName": "filters", + "valueType": "Array", + "valueDescription": "Array of filter objects" + } + ] + }, + { + "description": "A source filter's enable state has changed.", + "eventType": "SourceFilterEnableStateChanged", + "eventSubscription": "Filters", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "filters", + "dataFields": [ + { + "valueName": "sourceName", + "valueType": "String", + "valueDescription": "Name of the source the filter is on" + }, + { + "valueName": "filterName", + "valueType": "String", + "valueDescription": "Name of the filter" + }, + { + "valueName": "filterEnabled", + "valueType": "Boolean", + "valueDescription": "Whether the filter is enabled" + } + ] + }, + { + "description": "The name of a source filter has changed.", + "eventType": "SourceFilterNameChanged", + "eventSubscription": "Filters", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "filters", + "dataFields": [ + { + "valueName": "sourceName", + "valueType": "String", + "valueDescription": "The source the filter is on" + }, + { + "valueName": "oldFilterName", + "valueType": "String", + "valueDescription": "Old name of the filter" + }, + { + "valueName": "filterName", + "valueType": "String", + "valueDescription": "New name of the filter" + } + ] + }, { "description": "OBS has begun the shutdown process.", "eventType": "ExitStarted", @@ -2380,14 +4440,784 @@ "dataFields": [] }, { - "description": "Studio mode has been enabled or disabled.", - "eventType": "StudioModeStateChanged", - "eventSubscription": "General", + "description": "An input has been created.", + "eventType": "InputCreated", + "eventSubscription": "Inputs", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "inputs", + "dataFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input" + }, + { + "valueName": "inputKind", + "valueType": "String", + "valueDescription": "The kind of the input" + }, + { + "valueName": "unversionedInputKind", + "valueType": "String", + "valueDescription": "The unversioned kind of input (aka no `_v2` stuff)" + }, + { + "valueName": "inputSettings", + "valueType": "Object", + "valueDescription": "The settings configured to the input when it was created" + }, + { + "valueName": "defaultInputSettings", + "valueType": "Object", + "valueDescription": "The default settings for the input" + } + ] + }, + { + "description": "An input has been removed.", + "eventType": "InputRemoved", + "eventSubscription": "Inputs", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "inputs", + "dataFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input" + } + ] + }, + { + "description": "The name of an input has changed.", + "eventType": "InputNameChanged", + "eventSubscription": "Inputs", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "inputs", + "dataFields": [ + { + "valueName": "oldInputName", + "valueType": "String", + "valueDescription": "Old name of the input" + }, + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "New name of the input" + } + ] + }, + { + "description": "An input's active state has changed.\n\nWhen an input is active, it means it's being shown by the program feed.", + "eventType": "InputActiveStateChanged", + "eventSubscription": "InputActiveStateChanged", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "inputs", + "dataFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input" + }, + { + "valueName": "videoActive", + "valueType": "Boolean", + "valueDescription": "Whether the input is active" + } + ] + }, + { + "description": "An input's show state has changed.\n\nWhen an input is showing, it means it's being shown by the preview or a dialog.", + "eventType": "InputShowStateChanged", + "eventSubscription": "InputShowStateChanged", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "inputs", + "dataFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input" + }, + { + "valueName": "videoShowing", + "valueType": "Boolean", + "valueDescription": "Whether the input is showing" + } + ] + }, + { + "description": "An input's mute state has changed.", + "eventType": "InputMuteStateChanged", + "eventSubscription": "Inputs", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "inputs", + "dataFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input" + }, + { + "valueName": "inputMuted", + "valueType": "Boolean", + "valueDescription": "Whether the input is muted" + } + ] + }, + { + "description": "An input's volume level has changed.", + "eventType": "InputVolumeChanged", + "eventSubscription": "Inputs", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "inputs", + "dataFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input" + }, + { + "valueName": "inputVolumeMul", + "valueType": "Number", + "valueDescription": "New volume level in multimap" + }, + { + "valueName": "inputVolumeDb", + "valueType": "Number", + "valueDescription": "New volume level in dB" + } + ] + }, + { + "description": "The audio balance value of an input has changed.", + "eventType": "InputAudioBalanceChanged", + "eventSubscription": "Inputs", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "inputs", + "dataFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the affected input" + }, + { + "valueName": "inputAudioBalance", + "valueType": "Number", + "valueDescription": "New audio balance value of the input" + } + ] + }, + { + "description": "The sync offset of an input has changed.", + "eventType": "InputAudioSyncOffsetChanged", + "eventSubscription": "Inputs", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "inputs", + "dataFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input" + }, + { + "valueName": "inputAudioSyncOffset", + "valueType": "Number", + "valueDescription": "New sync offset in milliseconds" + } + ] + }, + { + "description": "The audio tracks of an input have changed.", + "eventType": "InputAudioTracksChanged", + "eventSubscription": "Inputs", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "inputs", + "dataFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input" + }, + { + "valueName": "inputAudioTracks", + "valueType": "Object", + "valueDescription": "Object of audio tracks along with their associated enable states" + } + ] + }, + { + "description": "The monitor type of an input has changed.\n\nAvailable types are:\n- `OBS_MONITORING_TYPE_NONE`\n- `OBS_MONITORING_TYPE_MONITOR_ONLY`\n- `OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT`", + "eventType": "InputAudioMonitorTypeChanged", + "eventSubscription": "Inputs", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "inputs", + "dataFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input" + }, + { + "valueName": "monitorType", + "valueType": "String", + "valueDescription": "New monitor type of the input" + } + ] + }, + { + "description": "A high-volume event providing volume levels of all active inputs every 50 milliseconds.", + "eventType": "InputVolumeMeters", + "eventSubscription": "InputVolumeMeters", + "complexity": 4, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "inputs", + "dataFields": [ + { + "valueName": "inputs", + "valueType": "Array", + "valueDescription": "Array of active inputs with their associated volume levels" + } + ] + }, + { + "description": "A media input has started playing.", + "eventType": "MediaInputPlaybackStarted", + "eventSubscription": "MediaInputs", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "media inputs", + "dataFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input" + } + ] + }, + { + "description": "A media input has finished playing.", + "eventType": "MediaInputPlaybackEnded", + "eventSubscription": "MediaInputs", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "media inputs", + "dataFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input" + } + ] + }, + { + "description": "An action has been performed on an input.", + "eventType": "MediaInputActionTriggered", + "eventSubscription": "MediaInputs", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "media inputs", + "dataFields": [ + { + "valueName": "inputName", + "valueType": "String", + "valueDescription": "Name of the input" + }, + { + "valueName": "mediaAction", + "valueType": "String", + "valueDescription": "Action performed on the input. See `ObsMediaInputAction` enum" + } + ] + }, + { + "description": "The state of the stream output has changed.", + "eventType": "StreamStateChanged", + "eventSubscription": "Outputs", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "outputs", + "dataFields": [ + { + "valueName": "outputActive", + "valueType": "Boolean", + "valueDescription": "Whether the output is active" + }, + { + "valueName": "outputState", + "valueType": "String", + "valueDescription": "The specific state of the output" + } + ] + }, + { + "description": "The state of the record output has changed.", + "eventType": "RecordStateChanged", + "eventSubscription": "Outputs", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "outputs", + "dataFields": [ + { + "valueName": "outputActive", + "valueType": "Boolean", + "valueDescription": "Whether the output is active" + }, + { + "valueName": "outputState", + "valueType": "String", + "valueDescription": "The specific state of the output" + } + ] + }, + { + "description": "The state of the replay buffer output has changed.", + "eventType": "ReplayBufferStateChanged", + "eventSubscription": "Outputs", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "outputs", + "dataFields": [ + { + "valueName": "outputActive", + "valueType": "Boolean", + "valueDescription": "Whether the output is active" + }, + { + "valueName": "outputState", + "valueType": "String", + "valueDescription": "The specific state of the output" + } + ] + }, + { + "description": "The state of the virtualcam output has changed.", + "eventType": "VirtualcamStateChanged", + "eventSubscription": "Outputs", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "outputs", + "dataFields": [ + { + "valueName": "outputActive", + "valueType": "Boolean", + "valueDescription": "Whether the output is active" + }, + { + "valueName": "outputState", + "valueType": "String", + "valueDescription": "The specific state of the output" + } + ] + }, + { + "description": "The replay buffer has been saved.", + "eventType": "ReplayBufferSaved", + "eventSubscription": "Outputs", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "outputs", + "dataFields": [ + { + "valueName": "savedReplayPath", + "valueType": "String", + "valueDescription": "Path of the saved replay file" + } + ] + }, + { + "description": "A scene item has been created.", + "eventType": "SceneItemCreated", + "eventSubscription": "SceneItems", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "dataFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene the item was added to" + }, + { + "valueName": "sourceName", + "valueType": "String", + "valueDescription": "Name of the underlying source (input/scene)" + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item" + }, + { + "valueName": "sceneItemIndex", + "valueType": "Number", + "valueDescription": "Index position of the item" + } + ] + }, + { + "description": "A scene item has been removed.\n\nThis event is not emitted when the scene the item is in is removed.", + "eventType": "SceneItemRemoved", + "eventSubscription": "SceneItems", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "dataFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene the item was removed from" + }, + { + "valueName": "sourceName", + "valueType": "String", + "valueDescription": "Name of the underlying source (input/scene)" + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item" + } + ] + }, + { + "description": "A scene's item list has been reindexed.", + "eventType": "SceneItemListReindexed", + "eventSubscription": "SceneItems", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "dataFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene" + }, + { + "valueName": "sceneItems", + "valueType": "Array", + "valueDescription": "Array of scene item objects" + } + ] + }, + { + "description": "A scene item's enable state has changed.", + "eventType": "SceneItemEnableStateChanged", + "eventSubscription": "SceneItems", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "dataFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene the item is in" + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item" + }, + { + "valueName": "sceneItemEnabled", + "valueType": "Boolean", + "valueDescription": "Whether the scene item is enabled (visible)" + } + ] + }, + { + "description": "A scene item's lock state has changed.", + "eventType": "SceneItemLockStateChanged", + "eventSubscription": "SceneItems", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "dataFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene the item is in" + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item" + }, + { + "valueName": "sceneItemLocked", + "valueType": "Boolean", + "valueDescription": "Whether the scene item is locked" + } + ] + }, + { + "description": "A scene item has been selected in the Ui.", + "eventType": "SceneItemSelected", + "eventSubscription": "SceneItems", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "dataFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene the item is in" + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item" + } + ] + }, + { + "description": "The transform/crop of a scene item has changed.", + "eventType": "SceneItemTransformChanged", + "eventSubscription": "SceneItemTransformChanged", + "complexity": 4, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scene items", + "dataFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "The name of the scene the item is in" + }, + { + "valueName": "sceneItemId", + "valueType": "Number", + "valueDescription": "Numeric ID of the scene item" + }, + { + "valueName": "sceneItemTransform", + "valueType": "Object", + "valueDescription": "New transform/crop info of the scene item" + } + ] + }, + { + "description": "A new scene has been created.", + "eventType": "SceneCreated", + "eventSubscription": "Scenes", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scenes", + "dataFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the new scene" + }, + { + "valueName": "isGroup", + "valueType": "Boolean", + "valueDescription": "Whether the new scene is a group" + } + ] + }, + { + "description": "A scene has been removed.", + "eventType": "SceneRemoved", + "eventSubscription": "Scenes", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scenes", + "dataFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the removed scene" + }, + { + "valueName": "isGroup", + "valueType": "Boolean", + "valueDescription": "Whether the scene was a group" + } + ] + }, + { + "description": "The name of a scene has changed.", + "eventType": "SceneNameChanged", + "eventSubscription": "Scenes", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scenes", + "dataFields": [ + { + "valueName": "oldSceneName", + "valueType": "String", + "valueDescription": "Old name of the scene" + }, + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "New name of the scene" + } + ] + }, + { + "description": "The current program scene has changed.", + "eventType": "CurrentProgramSceneChanged", + "eventSubscription": "Scenes", "complexity": 1, "rpcVersion": "1", "deprecated": false, "initialVersion": "5.0.0", - "category": "general", + "category": "scenes", + "dataFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene that was switched to" + } + ] + }, + { + "description": "The current preview scene has changed.", + "eventType": "CurrentPreviewSceneChanged", + "eventSubscription": "Scenes", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scenes", + "dataFields": [ + { + "valueName": "sceneName", + "valueType": "String", + "valueDescription": "Name of the scene that was switched to" + } + ] + }, + { + "description": "The list of scenes has changed.\n\nTODO: Make OBS fire this event when scenes are reordered.", + "eventType": "SceneListChanged", + "eventSubscription": "Scenes", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "scenes", + "dataFields": [ + { + "valueName": "scenes", + "valueType": "Array", + "valueDescription": "Updated array of scenes" + } + ] + }, + { + "description": "The current scene transition has changed.", + "eventType": "CurrentSceneTransitionChanged", + "eventSubscription": "Transitions", + "complexity": 3, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "transitions", + "dataFields": [ + { + "valueName": "transitionName", + "valueType": "String", + "valueDescription": "Name of the new transition" + } + ] + }, + { + "description": "The current scene transition duration has changed.", + "eventType": "CurrentSceneTransitionDurationChanged", + "eventSubscription": "Transitions", + "complexity": 2, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "transitions", + "dataFields": [ + { + "valueName": "transitionDuration", + "valueType": "Number", + "valueDescription": "Transition duration in milliseconds" + } + ] + }, + { + "description": "Studio mode has been enabled or disabled.", + "eventType": "StudioModeStateChanged", + "eventSubscription": "Ui", + "complexity": 1, + "rpcVersion": "1", + "deprecated": false, + "initialVersion": "5.0.0", + "category": "ui", "dataFields": [ { "valueName": "studioModeEnabled", diff --git a/docs/generated/protocol.md b/docs/generated/protocol.md index 8a88fe14..54a07b73 100644 --- a/docs/generated/protocol.md +++ b/docs/generated/protocol.md @@ -442,6 +442,8 @@ These are enumeration declarations, which are referenced throughout obs-websocke - [RequestStatus::NotEnoughResources](#requeststatusnotenoughresources) - [RequestStatus::InvalidResourceState](#requeststatusinvalidresourcestate) - [RequestStatus::InvalidInputKind](#requeststatusinvalidinputkind) + - [RequestStatus::ResourceNotConfigurable](#requeststatusresourcenotconfigurable) + - [RequestStatus::InvalidFilterKind](#requeststatusinvalidfilterkind) - [RequestStatus::ResourceCreationFailed](#requeststatusresourcecreationfailed) - [RequestStatus::ResourceActionFailed](#requeststatusresourceactionfailed) - [RequestStatus::RequestProcessingFailed](#requeststatusrequestprocessingfailed) @@ -458,6 +460,7 @@ These are enumeration declarations, which are referenced throughout obs-websocke - [EventSubscription::SceneItems](#eventsubscriptionsceneitems) - [EventSubscription::MediaInputs](#eventsubscriptionmediainputs) - [EventSubscription::Vendors](#eventsubscriptionvendors) + - [EventSubscription::Ui](#eventsubscriptionui) - [EventSubscription::All](#eventsubscriptionall) - [EventSubscription::InputVolumeMeters](#eventsubscriptioninputvolumemeters) - [EventSubscription::InputActiveStateChanged](#eventsubscriptioninputactivestatechanged) @@ -1011,6 +1014,28 @@ The specified input (obs_source_t-OBS_SOURCE_TYPE_INPUT) had the wrong kind. --- +### RequestStatus::ResourceNotConfigurable + +The resource does not support being configured. + +This is particularly relevant to transitions, where they do not always have changeable settings. + +- Identifier Value: `606` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + +--- + +### RequestStatus::InvalidFilterKind + +The specified filter (obs_source_t-OBS_SOURCE_TYPE_FILTER) had the wrong kind. + +- Identifier Value: `607` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + +--- + ### RequestStatus::ResourceCreationFailed Creating the resource failed. @@ -1162,6 +1187,16 @@ Subscription value to receive the `VendorEvent` event. --- +### EventSubscription::Ui + +Subscription value to receive events in the `Ui` category. + +- Identifier Value: `(1 << 10)` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + +--- + ### EventSubscription::All Helper to receive all non-high-volume events. @@ -1216,7 +1251,6 @@ Subscription value to receive the `SceneItemTransformChanged` high-volume event. ### Events Table of Contents - [General](#general) - [ExitStarted](#exitstarted) - - [StudioModeStateChanged](#studiomodestatechanged) - [VendorEvent](#vendorevent) - [Config](#config) - [CurrentSceneCollectionChanging](#currentscenecollectionchanging) @@ -1225,6 +1259,55 @@ Subscription value to receive the `SceneItemTransformChanged` high-volume event. - [CurrentProfileChanging](#currentprofilechanging) - [CurrentProfileChanged](#currentprofilechanged) - [ProfileListChanged](#profilelistchanged) +- [Scenes](#scenes) + - [SceneCreated](#scenecreated) + - [SceneRemoved](#sceneremoved) + - [SceneNameChanged](#scenenamechanged) + - [CurrentProgramSceneChanged](#currentprogramscenechanged) + - [CurrentPreviewSceneChanged](#currentpreviewscenechanged) + - [SceneListChanged](#scenelistchanged) +- [Inputs](#inputs) + - [InputCreated](#inputcreated) + - [InputRemoved](#inputremoved) + - [InputNameChanged](#inputnamechanged) + - [InputActiveStateChanged](#inputactivestatechanged) + - [InputShowStateChanged](#inputshowstatechanged) + - [InputMuteStateChanged](#inputmutestatechanged) + - [InputVolumeChanged](#inputvolumechanged) + - [InputAudioBalanceChanged](#inputaudiobalancechanged) + - [InputAudioSyncOffsetChanged](#inputaudiosyncoffsetchanged) + - [InputAudioTracksChanged](#inputaudiotrackschanged) + - [InputAudioMonitorTypeChanged](#inputaudiomonitortypechanged) + - [InputVolumeMeters](#inputvolumemeters) +- [Transitions](#transitions) + - [CurrentSceneTransitionChanged](#currentscenetransitionchanged) + - [CurrentSceneTransitionDurationChanged](#currentscenetransitiondurationchanged) +- [Filters](#filters) + - [SourceFilterCreated](#sourcefiltercreated) + - [SourceFilterRemoved](#sourcefilterremoved) + - [SourceFilterListReindexed](#sourcefilterlistreindexed) + - [SourceFilterEnableStateChanged](#sourcefilterenablestatechanged) + - [SourceFilterNameChanged](#sourcefilternamechanged) +- [Scene Items](#scene-items) + - [SceneItemCreated](#sceneitemcreated) + - [SceneItemRemoved](#sceneitemremoved) + - [SceneItemListReindexed](#sceneitemlistreindexed) + - [SceneItemEnableStateChanged](#sceneitemenablestatechanged) + - [SceneItemLockStateChanged](#sceneitemlockstatechanged) + - [SceneItemSelected](#sceneitemselected) + - [SceneItemTransformChanged](#sceneitemtransformchanged) +- [Outputs](#outputs) + - [StreamStateChanged](#streamstatechanged) + - [RecordStateChanged](#recordstatechanged) + - [ReplayBufferStateChanged](#replaybufferstatechanged) + - [VirtualcamStateChanged](#virtualcamstatechanged) + - [ReplayBufferSaved](#replaybuffersaved) +- [Media Inputs](#media-inputs) + - [MediaInputPlaybackStarted](#mediainputplaybackstarted) + - [MediaInputPlaybackEnded](#mediainputplaybackended) + - [MediaInputActionTriggered](#mediainputactiontriggered) +- [Ui](#ui) + - [StudioModeStateChanged](#studiomodestatechanged) ## General @@ -1239,23 +1322,6 @@ OBS has begun the shutdown process. --- -### StudioModeStateChanged - -Studio mode has been enabled or disabled. - -- Complexity Rating: `1/5` -- Latest Supported RPC Version: `1` -- Added in v5.0.0 - - -**Data Fields:** - -| Name | Type | Description | -| ---- | :---: | ----------- | -| studioModeEnabled | Boolean | True == Enabled, False == Disabled | - ---- - ### VendorEvent An event has been emitted from a vendor. @@ -1381,6 +1447,754 @@ The profile list has changed. | Name | Type | Description | | ---- | :---: | ----------- | | profiles | Array<String> | Updated list of profiles | +## Scenes + +### SceneCreated + +A new scene has been created. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneName | String | Name of the new scene | +| isGroup | Boolean | Whether the new scene is a group | + +--- + +### SceneRemoved + +A scene has been removed. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneName | String | Name of the removed scene | +| isGroup | Boolean | Whether the scene was a group | + +--- + +### SceneNameChanged + +The name of a scene has changed. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| oldSceneName | String | Old name of the scene | +| sceneName | String | New name of the scene | + +--- + +### CurrentProgramSceneChanged + +The current program scene has changed. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneName | String | Name of the scene that was switched to | + +--- + +### CurrentPreviewSceneChanged + +The current preview scene has changed. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneName | String | Name of the scene that was switched to | + +--- + +### SceneListChanged + +The list of scenes has changed. + +TODO: Make OBS fire this event when scenes are reordered. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| scenes | Array<Object> | Updated array of scenes | +## Inputs + +### InputCreated + +An input has been created. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| inputName | String | Name of the input | +| inputKind | String | The kind of the input | +| unversionedInputKind | String | The unversioned kind of input (aka no `_v2` stuff) | +| inputSettings | Object | The settings configured to the input when it was created | +| defaultInputSettings | Object | The default settings for the input | + +--- + +### InputRemoved + +An input has been removed. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| inputName | String | Name of the input | + +--- + +### InputNameChanged + +The name of an input has changed. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| oldInputName | String | Old name of the input | +| inputName | String | New name of the input | + +--- + +### InputActiveStateChanged + +An input's active state has changed. + +When an input is active, it means it's being shown by the program feed. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| inputName | String | Name of the input | +| videoActive | Boolean | Whether the input is active | + +--- + +### InputShowStateChanged + +An input's show state has changed. + +When an input is showing, it means it's being shown by the preview or a dialog. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| inputName | String | Name of the input | +| videoShowing | Boolean | Whether the input is showing | + +--- + +### InputMuteStateChanged + +An input's mute state has changed. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| inputName | String | Name of the input | +| inputMuted | Boolean | Whether the input is muted | + +--- + +### InputVolumeChanged + +An input's volume level has changed. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| inputName | String | Name of the input | +| inputVolumeMul | Number | New volume level in multimap | +| inputVolumeDb | Number | New volume level in dB | + +--- + +### InputAudioBalanceChanged + +The audio balance value of an input has changed. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| inputName | String | Name of the affected input | +| inputAudioBalance | Number | New audio balance value of the input | + +--- + +### InputAudioSyncOffsetChanged + +The sync offset of an input has changed. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| inputName | String | Name of the input | +| inputAudioSyncOffset | Number | New sync offset in milliseconds | + +--- + +### InputAudioTracksChanged + +The audio tracks of an input have changed. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| inputName | String | Name of the input | +| inputAudioTracks | Object | Object of audio tracks along with their associated enable states | + +--- + +### InputAudioMonitorTypeChanged + +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` + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| inputName | String | Name of the input | +| monitorType | String | New monitor type of the input | + +--- + +### InputVolumeMeters + +A high-volume event providing volume levels of all active inputs every 50 milliseconds. + +- Complexity Rating: `4/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| inputs | Array<Object> | Array of active inputs with their associated volume levels | +## Transitions + +### CurrentSceneTransitionChanged + +The current scene transition has changed. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| transitionName | String | Name of the new transition | + +--- + +### CurrentSceneTransitionDurationChanged + +The current scene transition duration has changed. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| transitionDuration | Number | Transition duration in milliseconds | +## Filters + +### SourceFilterCreated + +A filter has been added to a source. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sourceName | String | Name of the source the filter was added to | +| filterName | String | Name of the filter | +| filterKind | String | The kind of the filter | +| filterIndex | Number | Index position of the filter | +| filterSettings | Object | The settings configured to the filter when it was created | +| defaultFilterSettings | Object | The default settings for the filter | + +--- + +### SourceFilterRemoved + +A filter has been removed from a source. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sourceName | String | Name of the source the filter was on | +| filterName | String | Name of the filter | + +--- + +### SourceFilterListReindexed + +A source's filter list has been reindexed. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sourceName | String | Name of the source | +| filters | Array<Object> | Array of filter objects | + +--- + +### SourceFilterEnableStateChanged + +A source filter's enable state has changed. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sourceName | String | Name of the source the filter is on | +| filterName | String | Name of the filter | +| filterEnabled | Boolean | Whether the filter is enabled | + +--- + +### SourceFilterNameChanged + +The name of a source filter has changed. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sourceName | String | The source the filter is on | +| oldFilterName | String | Old name of the filter | +| filterName | String | New name of the filter | +## Scene Items + +### SceneItemCreated + +A scene item has been created. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneName | String | Name of the scene the item was added to | +| sourceName | String | Name of the underlying source (input/scene) | +| sceneItemId | Number | Numeric ID of the scene item | +| sceneItemIndex | Number | Index position of the item | + +--- + +### SceneItemRemoved + +A scene item has been removed. + +This event is not emitted when the scene the item is in is removed. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneName | String | Name of the scene the item was removed from | +| sourceName | String | Name of the underlying source (input/scene) | +| sceneItemId | Number | Numeric ID of the scene item | + +--- + +### SceneItemListReindexed + +A scene's item list has been reindexed. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneName | String | Name of the scene | +| sceneItems | Array<Object> | Array of scene item objects | + +--- + +### SceneItemEnableStateChanged + +A scene item's enable state has changed. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneName | String | Name of the scene the item is in | +| sceneItemId | Number | Numeric ID of the scene item | +| sceneItemEnabled | Boolean | Whether the scene item is enabled (visible) | + +--- + +### SceneItemLockStateChanged + +A scene item's lock state has changed. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneName | String | Name of the scene the item is in | +| sceneItemId | Number | Numeric ID of the scene item | +| sceneItemLocked | Boolean | Whether the scene item is locked | + +--- + +### SceneItemSelected + +A scene item has been selected in the Ui. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneName | String | Name of the scene the item is in | +| sceneItemId | Number | Numeric ID of the scene item | + +--- + +### SceneItemTransformChanged + +The transform/crop of a scene item has changed. + +- Complexity Rating: `4/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneName | String | The name of the scene the item is in | +| sceneItemId | Number | Numeric ID of the scene item | +| sceneItemTransform | Object | New transform/crop info of the scene item | +## Outputs + +### StreamStateChanged + +The state of the stream output has changed. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| outputActive | Boolean | Whether the output is active | +| outputState | String | The specific state of the output | + +--- + +### RecordStateChanged + +The state of the record output has changed. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| outputActive | Boolean | Whether the output is active | +| outputState | String | The specific state of the output | + +--- + +### ReplayBufferStateChanged + +The state of the replay buffer output has changed. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| outputActive | Boolean | Whether the output is active | +| outputState | String | The specific state of the output | + +--- + +### VirtualcamStateChanged + +The state of the virtualcam output has changed. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| outputActive | Boolean | Whether the output is active | +| outputState | String | The specific state of the output | + +--- + +### ReplayBufferSaved + +The replay buffer has been saved. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| savedReplayPath | String | Path of the saved replay file | +## Media Inputs + +### MediaInputPlaybackStarted + +A media input has started playing. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| inputName | String | Name of the input | + +--- + +### MediaInputPlaybackEnded + +A media input has finished playing. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| inputName | String | Name of the input | + +--- + +### MediaInputActionTriggered + +An action has been performed on an input. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| inputName | String | Name of the input | +| mediaAction | String | Action performed on the input. See `ObsMediaInputAction` enum | +## Ui + +### StudioModeStateChanged + +Studio mode has been enabled or disabled. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Data Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| studioModeEnabled | Boolean | True == Enabled, False == Disabled | # Requests @@ -1394,8 +2208,6 @@ The profile list has changed. - [GetHotkeyList](#gethotkeylist) - [TriggerHotkeyByName](#triggerhotkeybyname) - [TriggerHotkeyByKeySequence](#triggerhotkeybykeysequence) - - [GetStudioModeEnabled](#getstudiomodeenabled) - - [SetStudioModeEnabled](#setstudiomodeenabled) - [Sleep](#sleep) - [Config](#config-1) - [GetPersistentData](#getpersistentdata) @@ -1417,8 +2229,9 @@ The profile list has changed. - [GetSourceActive](#getsourceactive) - [GetSourceScreenshot](#getsourcescreenshot) - [SaveSourceScreenshot](#savesourcescreenshot) -- [Scenes](#scenes) +- [Scenes](#scenes-1) - [GetSceneList](#getscenelist) + - [GetGroupList](#getgrouplist) - [GetCurrentProgramScene](#getcurrentprogramscene) - [SetCurrentProgramScene](#setcurrentprogramscene) - [GetCurrentPreviewScene](#getcurrentpreviewscene) @@ -1426,9 +2239,12 @@ The profile list has changed. - [CreateScene](#createscene) - [RemoveScene](#removescene) - [SetSceneName](#setscenename) -- [Inputs](#inputs) + - [GetSceneSceneTransitionOverride](#getscenescenetransitionoverride) + - [SetSceneSceneTransitionOverride](#setscenescenetransitionoverride) +- [Inputs](#inputs-1) - [GetInputList](#getinputlist) - [GetInputKindList](#getinputkindlist) + - [GetSpecialInputs](#getspecialinputs) - [CreateInput](#createinput) - [RemoveInput](#removeinput) - [SetInputName](#setinputname) @@ -1440,12 +2256,88 @@ The profile list has changed. - [ToggleInputMute](#toggleinputmute) - [GetInputVolume](#getinputvolume) - [SetInputVolume](#setinputvolume) + - [GetInputAudioBalance](#getinputaudiobalance) + - [SetInputAudioBalance](#setinputaudiobalance) - [GetInputAudioSyncOffset](#getinputaudiosyncoffset) - [SetInputAudioSyncOffset](#setinputaudiosyncoffset) - [GetInputAudioMonitorType](#getinputaudiomonitortype) - [SetInputAudioMonitorType](#setinputaudiomonitortype) + - [GetInputAudioTracks](#getinputaudiotracks) + - [SetInputAudioTracks](#setinputaudiotracks) - [GetInputPropertiesListPropertyItems](#getinputpropertieslistpropertyitems) - [PressInputPropertiesButton](#pressinputpropertiesbutton) +- [Transitions](#transitions-1) + - [GetTransitionKindList](#gettransitionkindlist) + - [GetSceneTransitionList](#getscenetransitionlist) + - [GetCurrentSceneTransition](#getcurrentscenetransition) + - [SetCurrentSceneTransition](#setcurrentscenetransition) + - [SetCurrentSceneTransitionDuration](#setcurrentscenetransitionduration) + - [SetCurrentSceneTransitionSettings](#setcurrentscenetransitionsettings) + - [GetCurrentSceneTransitionCursor](#getcurrentscenetransitioncursor) + - [TriggerStudioModeTransition](#triggerstudiomodetransition) + - [SetTBarPosition](#settbarposition) +- [Filters](#filters-1) + - [GetSourceFilterList](#getsourcefilterlist) + - [GetSourceFilterDefaultSettings](#getsourcefilterdefaultsettings) + - [CreateSourceFilter](#createsourcefilter) + - [RemoveSourceFilter](#removesourcefilter) + - [SetSourceFilterName](#setsourcefiltername) + - [GetSourceFilter](#getsourcefilter) + - [SetSourceFilterIndex](#setsourcefilterindex) + - [SetSourceFilterSettings](#setsourcefiltersettings) +- [Scene Items](#scene-items-1) + - [GetSceneItemList](#getsceneitemlist) + - [GetGroupItemList](#getgroupitemlist) + - [GetSceneItemId](#getsceneitemid) + - [CreateSceneItem](#createsceneitem) + - [RemoveSceneItem](#removesceneitem) + - [DuplicateSceneItem](#duplicatesceneitem) + - [GetSceneItemTransform](#getsceneitemtransform) + - [SetSceneItemTransform](#setsceneitemtransform) + - [GetSceneItemEnabled](#getsceneitemenabled) + - [SetSceneItemEnabled](#setsceneitemenabled) + - [GetSceneItemLocked](#getsceneitemlocked) + - [SetSceneItemLocked](#setsceneitemlocked) + - [GetSceneItemIndex](#getsceneitemindex) + - [SetSceneItemIndex](#setsceneitemindex) + - [GetSceneItemBlendMode](#getsceneitemblendmode) + - [SetSceneItemBlendMode](#setsceneitemblendmode) +- [Outputs](#outputs-1) + - [GetVirtualCamStatus](#getvirtualcamstatus) + - [ToggleVirtualCam](#togglevirtualcam) + - [StartVirtualCam](#startvirtualcam) + - [StopVirtualCam](#stopvirtualcam) + - [GetReplayBufferStatus](#getreplaybufferstatus) + - [ToggleReplayBuffer](#togglereplaybuffer) + - [StartReplayBuffer](#startreplaybuffer) + - [StopReplayBuffer](#stopreplaybuffer) + - [SaveReplayBuffer](#savereplaybuffer) + - [GetLastReplayBufferReplay](#getlastreplaybufferreplay) +- [Stream](#stream) + - [GetStreamStatus](#getstreamstatus) + - [ToggleStream](#togglestream) + - [StartStream](#startstream) + - [StopStream](#stopstream) + - [SendStreamCaption](#sendstreamcaption) +- [Record](#record) + - [GetRecordStatus](#getrecordstatus) + - [ToggleRecord](#togglerecord) + - [StartRecord](#startrecord) + - [StopRecord](#stoprecord) + - [ToggleRecordPause](#togglerecordpause) + - [PauseRecord](#pauserecord) + - [ResumeRecord](#resumerecord) +- [Media Inputs](#media-inputs-1) + - [GetMediaInputStatus](#getmediainputstatus) + - [SetMediaInputCursor](#setmediainputcursor) + - [OffsetMediaInputCursor](#offsetmediainputcursor) + - [TriggerMediaInputAction](#triggermediainputaction) +- [Ui](#ui-1) + - [GetStudioModeEnabled](#getstudiomodeenabled) + - [SetStudioModeEnabled](#setstudiomodeenabled) + - [OpenInputPropertiesDialog](#openinputpropertiesdialog) + - [OpenInputFiltersDialog](#openinputfiltersdialog) + - [OpenInputInteractDialog](#openinputinteractdialog) @@ -1602,40 +2494,6 @@ Triggers a hotkey using a sequence of keys. --- -### GetStudioModeEnabled - -Gets whether studio is enabled. - -- Complexity Rating: `1/5` -- Latest Supported RPC Version: `1` -- Added in v5.0.0 - - -**Response Fields:** - -| Name | Type | Description | -| ---- | :---: | ----------- | -| studioModeEnabled | Boolean | Whether studio mode is enabled | - ---- - -### SetStudioModeEnabled - -Enables or disables studio mode - -- Complexity Rating: `1/5` -- Latest Supported RPC Version: `1` -- Added in v5.0.0 - - -**Request Fields:** - -| Name | Type | Description | Value Restrictions | ?Default Behavior | -| ---- | :---: | ----------- | :----------------: | ----------------- | -| studioModeEnabled | Boolean | True == Enabled, False == Disabled | None | N/A | - ---- - ### Sleep Sleeps for a time duration or number of frames. Only available in request batches with types `SERIAL_REALTIME` or `SERIAL_FRAME`. @@ -1828,7 +2686,7 @@ Removes a profile. If the current profile is chosen, it will change to a differe Gets a parameter from the current profile's configuration. -- Complexity Rating: `3/5` +- Complexity Rating: `4/5` - Latest Supported RPC Version: `1` - Added in v5.0.0 @@ -1854,7 +2712,7 @@ Gets a parameter from the current profile's configuration. Sets the value of a parameter in the current profile's configuration. -- Complexity Rating: `3/5` +- Complexity Rating: `4/5` - Latest Supported RPC Version: `1` - Added in v5.0.0 @@ -2064,9 +2922,28 @@ Gets an array of all scenes in OBS. | Name | Type | Description | | ---- | :---: | ----------- | -| scenes | Array<String> | Array of scenes in OBS | | currentProgramSceneName | String | Current program scene | | currentPreviewSceneName | String | Current preview scene. `null` if not in studio mode | +| scenes | Array<Object> | Array of scenes | + +--- + +### GetGroupList + +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. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| groups | Array<String> | Array of group names | --- @@ -2192,6 +3069,50 @@ Sets the name of a scene (rename). | sceneName | String | Name of the scene to be renamed | None | N/A | | newSceneName | String | New name for the scene | None | N/A | +--- + +### GetSceneSceneTransitionOverride + +Gets the scene transition overridden for a scene. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the scene | None | N/A | + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| transitionName | String | Name of the overridden scene transition, else `null` | +| transitionDuration | Number | Duration of the overridden scene transition, else `null` | + +--- + +### SetSceneSceneTransitionOverride + +Gets the scene transition overridden for a scene. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the scene | None | N/A | +| ?transitionName | String | Name of the scene transition to use as override. Specify `null` to remove | None | Unchanged | +| ?transitionDuration | Number | Duration to use for any overridden transition. Specify `null` to remove | >= 50, <= 20000 | Unchanged | + ## Inputs @@ -2243,6 +3164,28 @@ Gets an array of all available input kinds in OBS. --- +### GetSpecialInputs + +Gets the names of all special inputs. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| desktop1 | String | Name of the Desktop Audio input | +| desktop2 | String | Name of the Desktop Audio 2 input | +| mic1 | String | Name of the Mic/Auxiliary Audio input | +| mic2 | String | Name of the Mic/Auxiliary Audio 2 input | +| mic3 | String | Name of the Mic/Auxiliary Audio 3 input | +| mic4 | String | Name of the Mic/Auxiliary Audio 4 input | + +--- + ### CreateInput Creates a new input, adding it as a scene item to the specified scene. @@ -2374,6 +3317,7 @@ Sets the settings of an input. | ---- | :---: | ----------- | :----------------: | ----------------- | | inputName | String | Name of the input to set the settings of | None | N/A | | inputSettings | Object | Object of settings to apply | None | N/A | +| ?overlay | Boolean | True == apply the settings on top of existing ones, False == reset the input to its defaults, then apply settings. | None | true | --- @@ -2483,7 +3427,49 @@ Sets the volume setting of an input. | ---- | :---: | ----------- | :----------------: | ----------------- | | inputName | String | Name of the input to set the volume of | None | N/A | | ?inputVolumeMul | Number | Volume setting in mul | >= 0, <= 20 | `inputVolumeDb` should be specified | -| ?inputVolumeDb | Number | Volume setting in dB | >= -100, <= -26 | `inputVolumeMul` should be specified | +| ?inputVolumeDb | Number | Volume setting in dB | >= -100, <= 26 | `inputVolumeMul` should be specified | + +--- + +### GetInputAudioBalance + +Gets the audio balance of an input. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| inputName | String | Name of the input to get the audio balance of | None | N/A | + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| inputAudioBalance | Number | Audio balance value from 0.0-1.0 | + +--- + +### SetInputAudioBalance + +Sets the audio balance of an input. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| inputName | String | Name of the input to set the audio balance of | None | N/A | +| inputAudioBalance | Number | New audio balance value | >= 0.0, <= 1.0 | N/A | --- @@ -2578,6 +3564,48 @@ Sets the audio monitor type of an input. --- +### GetInputAudioTracks + +Gets the enable state of all audio tracks of an input. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| inputName | String | Name of the input | None | N/A | + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| inputAudioTracks | Object | Object of audio tracks and associated enable states | + +--- + +### SetInputAudioTracks + +Sets the enable state of audio tracks of an input. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| inputName | String | Name of the input | None | N/A | +| inputAudioTracks | Object | Track settings to apply | None | N/A | + +--- + ### GetInputPropertiesListPropertyItems Gets the items of a list property from an input's properties. @@ -2624,3 +3652,1225 @@ Note: Use this in cases where there is a button in the properties of an input th | propertyName | String | Name of the button property to press | None | N/A | +## Transitions + +### GetTransitionKindList + +Gets an array of all available transition kinds. + +Similar to `GetInputKindList` + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| transitionKinds | Array<String> | Array of transition kinds | + +--- + +### GetSceneTransitionList + +Gets an array of all scene transitions in OBS. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| currentSceneTransitionName | String | Name of the current scene transition. Can be null | +| currentSceneTransitionKind | String | Kind of the current scene transition. Can be null | +| transitions | Array<Object> | Array of transitions | + +--- + +### GetCurrentSceneTransition + +Gets information about the current scene transition. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| transitionName | String | Name of the transition | +| transitionKind | String | Kind of the transition | +| transitionFixed | Boolean | Whether the transition uses a fixed (unconfigurable) duration | +| transitionDuration | Number | Configured transition duration in milliseconds. `null` if transition is fixed | +| transitionConfigurable | Boolean | Whether the transition supports being configured | +| transitionSettings | Object | Object of settings for the transition. `null` if transition is not configurable | + +--- + +### SetCurrentSceneTransition + +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. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| transitionName | String | Name of the transition to make active | None | N/A | + +--- + +### SetCurrentSceneTransitionDuration + +Sets the duration of the current scene transition, if it is not fixed. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| transitionDuration | Number | Duration in milliseconds | >= 50, <= 20000 | N/A | + +--- + +### SetCurrentSceneTransitionSettings + +Sets the settings of the current scene transition. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| transitionSettings | Object | Settings object to apply to the transition. Can be `{}` | None | N/A | +| ?overlay | Boolean | Whether to overlay over the current settings or replace them | None | true | + +--- + +### GetCurrentSceneTransitionCursor + +Gets the cursor position of the current scene transition. + +Note: `transitionCursor` will return 1.0 when the transition is inactive. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| transitionCursor | Number | Cursor position, between 0.0 and 1.0 | + +--- + +### TriggerStudioModeTransition + +Triggers the current scene transition. Same functionality as the `Transition` button in studio mode. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + +--- + +### SetTBarPosition + +Sets the position of the TBar. + +**Very important note**: This will be deprecated and replaced in a future version of obs-websocket. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| position | Number | New position | >= 0.0, <= 1.0 | N/A | +| ?release | Boolean | Whether to release the TBar. Only set `false` if you know that you will be sending another position update | None | `true` | + + +## Filters + +### GetSourceFilterList + +Gets an array of all of a source's filters. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sourceName | String | Name of the source | None | N/A | + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| filters | Array<Object> | Array of filters | + +--- + +### GetSourceFilterDefaultSettings + +Gets the default settings for a filter kind. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| filterKind | String | Filter kind to get the default settings for | None | N/A | + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| defaultFilterSettings | Object | Object of default settings for the filter kind | + +--- + +### CreateSourceFilter + +Creates a new filter, adding it to the specified source. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sourceName | String | Name of the source to add the filter to | None | N/A | +| filterName | String | Name of the new filter to be created | None | N/A | +| filterKind | String | The kind of filter to be created | None | N/A | +| ?filterSettings | Object | Settings object to initialize the filter with | None | Default settings used | + +--- + +### RemoveSourceFilter + +Removes a filter from a source. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sourceName | String | Name of the source the filter is on | None | N/A | +| filterName | String | Name of the filter to remove | None | N/A | + +--- + +### SetSourceFilterName + +Sets the name of a source filter (rename). + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sourceName | String | Name of the source the filter is on | None | N/A | +| filterName | String | Current name of the filter | None | N/A | +| newFilterName | String | New name for the filter | None | N/A | + +--- + +### GetSourceFilter + +Gets the info for a specific source filter. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sourceName | String | Name of the source | None | N/A | +| filterName | String | Name of the filter | None | N/A | + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| filterEnabled | Boolean | Whether the filter is enabled | +| filterIndex | Number | Index of the filter in the list, beginning at 0 | +| filterKind | String | The kind of filter | +| filterSettings | Object | Settings object associated with the filter | + +--- + +### SetSourceFilterIndex + +Sets the index position of a filter on a source. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sourceName | String | Name of the source the filter is on | None | N/A | +| filterName | String | Name of the filter | None | N/A | +| filterIndex | Number | New index position of the filter | >= 0 | N/A | + +--- + +### SetSourceFilterSettings + +Sets the settings of a source filter. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sourceName | String | Name of the source the filter is on | None | N/A | +| filterName | String | Name of the filter to set the settings of | None | N/A | +| filterSettings | Object | Object of settings to apply | None | N/A | +| ?overlay | Boolean | True == apply the settings on top of existing ones, False == reset the input to its defaults, then apply settings. | None | true | + + +## Scene Items + +### GetSceneItemList + +Gets a list of all scene items in a scene. + +Scenes only + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the scene to get the items of | None | N/A | + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneItems | Array<Object> | Array of scene items in the scene | + +--- + +### GetGroupItemList + +Basically GetSceneItemList, but for groups. + +Using groups at all in OBS is discouraged, as they are very broken under the hood. + +Groups only + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the group to get the items of | None | N/A | + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneItems | Array<Object> | Array of scene items in the group | + +--- + +### GetSceneItemId + +Searches a scene for a source, and returns its id. + +Scenes and Groups + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the scene or group to search in | None | N/A | +| sourceName | String | Name of the source to find | None | N/A | + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneItemId | Number | Numeric ID of the scene item | + +--- + +### CreateSceneItem + +Creates a new scene item using a source. + +Scenes only + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the scene to create the new item in | None | N/A | +| sourceName | String | Name of the source to add to the scene | None | N/A | +| ?sceneItemEnabled | Boolean | Enable state to apply to the scene item on creation | None | True | + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneItemId | Number | Numeric ID of the scene item | + +--- + +### RemoveSceneItem + +Removes a scene item from a scene. + +Scenes only + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the scene the item is in | None | N/A | +| sceneItemId | Number | Numeric ID of the scene item | >= 0 | N/A | + +--- + +### DuplicateSceneItem + +Duplicates a scene item, copying all transform and crop info. + +Scenes only + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the scene the item is in | None | N/A | +| sceneItemId | Number | Numeric ID of the scene item | >= 0 | N/A | +| ?destinationSceneName | String | Name of the scene to create the duplicated item in | None | `sceneName` is assumed | + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneItemId | Number | Numeric ID of the duplicated scene item | + +--- + +### GetSceneItemTransform + +Gets the transform and crop info of a scene item. + +Scenes and Groups + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the scene the item is in | None | N/A | +| sceneItemId | Number | Numeric ID of the scene item | >= 0 | N/A | + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneItemTransform | Object | Object containing scene item transform info | + +--- + +### SetSceneItemTransform + +Sets the transform and crop info of a scene item. + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the scene the item is in | None | N/A | +| sceneItemId | Number | Numeric ID of the scene item | >= 0 | N/A | +| sceneItemTransform | Object | Object containing scene item transform info to update | None | N/A | + +--- + +### GetSceneItemEnabled + +Gets the enable state of a scene item. + +Scenes and Groups + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the scene the item is in | None | N/A | +| sceneItemId | Number | Numeric ID of the scene item | >= 0 | N/A | + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneItemEnabled | Boolean | Whether the scene item is enabled. `true` for enabled, `false` for disabled | + +--- + +### SetSceneItemEnabled + +Sets the enable state of a scene item. + +Scenes and Groups + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the scene the item is in | None | N/A | +| sceneItemId | Number | Numeric ID of the scene item | >= 0 | N/A | +| sceneItemEnabled | Boolean | New enable state of the scene item | None | N/A | + +--- + +### GetSceneItemLocked + +Gets the lock state of a scene item. + +Scenes and Groups + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the scene the item is in | None | N/A | +| sceneItemId | Number | Numeric ID of the scene item | >= 0 | N/A | + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneItemLocked | Boolean | Whether the scene item is locked. `true` for locked, `false` for unlocked | + +--- + +### SetSceneItemLocked + +Sets the lock state of a scene item. + +Scenes and Group + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the scene the item is in | None | N/A | +| sceneItemId | Number | Numeric ID of the scene item | >= 0 | N/A | +| sceneItemLocked | Boolean | New lock state of the scene item | None | N/A | + +--- + +### GetSceneItemIndex + +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 + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the scene the item is in | None | N/A | +| sceneItemId | Number | Numeric ID of the scene item | >= 0 | N/A | + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneItemIndex | Number | Index position of the scene item | + +--- + +### SetSceneItemIndex + +Sets the index position of a scene item in a scene. + +Scenes and Groups + +- Complexity Rating: `3/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the scene the item is in | None | N/A | +| sceneItemId | Number | Numeric ID of the scene item | >= 0 | N/A | +| sceneItemIndex | Number | New index position of the scene item | >= 0 | N/A | + +--- + +### GetSceneItemBlendMode + +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 + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the scene the item is in | None | N/A | +| sceneItemId | Number | Numeric ID of the scene item | >= 0 | N/A | + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| sceneItemBlendMode | String | Current blend mode | + +--- + +### SetSceneItemBlendMode + +Sets the blend mode of a scene item. + +Scenes and Groups + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| sceneName | String | Name of the scene the item is in | None | N/A | +| sceneItemId | Number | Numeric ID of the scene item | >= 0 | N/A | +| sceneItemBlendMode | String | New blend mode | None | N/A | + + +## Outputs + +### GetVirtualCamStatus + +Gets the status of the virtualcam output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| outputActive | Boolean | Whether the output is active | + +--- + +### ToggleVirtualCam + +Toggles the state of the virtualcam output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| outputActive | Boolean | Whether the output is active | + +--- + +### StartVirtualCam + +Starts the virtualcam output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + +--- + +### StopVirtualCam + +Stops the virtualcam output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + +--- + +### GetReplayBufferStatus + +Gets the status of the replay buffer output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| outputActive | Boolean | Whether the output is active | + +--- + +### ToggleReplayBuffer + +Toggles the state of the replay buffer output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| outputActive | Boolean | Whether the output is active | + +--- + +### StartReplayBuffer + +Starts the replay buffer output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + +--- + +### StopReplayBuffer + +Stops the replay buffer output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + +--- + +### SaveReplayBuffer + +Saves the contents of the replay buffer output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + +--- + +### GetLastReplayBufferReplay + +Gets the filename of the last replay buffer save file. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| savedReplayPath | String | File path | + + +## Stream + +### GetStreamStatus + +Gets the status of the stream output. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| outputActive | Boolean | Whether the output is active | +| outputReconnecting | Boolean | Whether the output is currently reconnecting | +| outputTimecode | String | Current formatted timecode string for the output | +| outputDuration | Number | Current duration in milliseconds for the output | +| outputBytes | Number | Number of bytes sent by the output | +| outputSkippedFrames | Number | Number of frames skipped by the output's process | +| outputTotalFrames | Number | Total number of frames delivered by the output's process | + +--- + +### ToggleStream + +Toggles the status of the stream output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| outputActive | Boolean | New state of the stream output | + +--- + +### StartStream + +Starts the stream output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + +--- + +### StopStream + +Stops the stream output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + +--- + +### SendStreamCaption + +Sends CEA-608 caption text over the stream output. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| captionText | String | Caption text | None | N/A | + + +## Record + +### GetRecordStatus + +Gets the status of the record output. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| outputActive | Boolean | Whether the output is active | +| ouputPaused | Boolean | Whether the output is paused | +| outputTimecode | String | Current formatted timecode string for the output | +| outputDuration | Number | Current duration in milliseconds for the output | +| outputBytes | Number | Number of bytes sent by the output | + +--- + +### ToggleRecord + +Toggles the status of the record output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + +--- + +### StartRecord + +Starts the record output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + +--- + +### StopRecord + +Stops the record output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + +--- + +### ToggleRecordPause + +Toggles pause on the record output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + +--- + +### PauseRecord + +Pauses the record output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + +--- + +### ResumeRecord + +Resumes the record output. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +## Media Inputs + +### GetMediaInputStatus + +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` + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| inputName | String | Name of the media input | None | N/A | + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| mediaState | String | State of the media input | +| mediaDuration | Number | Total duration of the playing media in milliseconds. `null` if not playing | +| mediaCursor | Number | Position of the cursor in milliseconds. `null` if not playing | + +--- + +### SetMediaInputCursor + +Sets the cursor position of a media input. + +This request does not perform bounds checking of the cursor position. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| inputName | String | Name of the media input | None | N/A | +| mediaCursor | Number | New cursor position to set | >= 0 | N/A | + +--- + +### OffsetMediaInputCursor + +Offsets the current cursor position of a media input by the specified value. + +This request does not perform bounds checking of the cursor position. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| inputName | String | Name of the media input | None | N/A | +| mediaCursorOffset | Number | Value to offset the current cursor position by | None | N/A | + +--- + +### TriggerMediaInputAction + +Triggers an action on a media input. + +- Complexity Rating: `2/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| inputName | String | Name of the media input | None | N/A | +| mediaAction | String | Identifier of the `ObsMediaInputAction` enum | None | N/A | + + +## Ui + +### GetStudioModeEnabled + +Gets whether studio is enabled. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Response Fields:** + +| Name | Type | Description | +| ---- | :---: | ----------- | +| studioModeEnabled | Boolean | Whether studio mode is enabled | + +--- + +### SetStudioModeEnabled + +Enables or disables studio mode + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| studioModeEnabled | Boolean | True == Enabled, False == Disabled | None | N/A | + +--- + +### OpenInputPropertiesDialog + +Opens the properties dialog of an input. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| inputName | String | Name of the input to open the dialog of | None | N/A | + +--- + +### OpenInputFiltersDialog + +Opens the filters dialog of an input. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| inputName | String | Name of the input to open the dialog of | None | N/A | + +--- + +### OpenInputInteractDialog + +Opens the interact dialog of an input. + +- Complexity Rating: `1/5` +- Latest Supported RPC Version: `1` +- Added in v5.0.0 + + +**Request Fields:** + +| Name | Type | Description | Value Restrictions | ?Default Behavior | +| ---- | :---: | ----------- | :----------------: | ----------------- | +| inputName | String | Name of the input to open the dialog of | None | N/A | + + diff --git a/lib/obs-websocket-api.h b/lib/obs-websocket-api.h index 5607038a..ccaa187f 100644 --- a/lib/obs-websocket-api.h +++ b/lib/obs-websocket-api.h @@ -22,6 +22,8 @@ with this program. If not, see #include +#define OBS_WEBSOCKET_API_VERSION 1 + #ifdef __cplusplus extern "C" { #endif diff --git a/src/Config.cpp b/src/Config.cpp index 2d42d7da..5c45b2dd 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -41,7 +41,7 @@ Config::Config() : PasswordOverridden(false), FirstLoad(true), ServerEnabled(true), - ServerPort(4444), + ServerPort(4455), DebugEnabled(false), AlertsEnabled(false), AuthRequired(true), @@ -70,7 +70,7 @@ void Config::Load() // future loads use the override flag. if (FirstLoad) { FirstLoad = false; - if (!ServerPassword.isEmpty()) { + if (ServerPassword.isEmpty()) { blog(LOG_INFO, "[Config::Load] (FirstLoad) Generating new server password."); ServerPassword = QString::fromStdString(Utils::Crypto::GeneratePassword()); } else { diff --git a/src/WebSocketApi.cpp b/src/WebSocketApi.cpp index bd5540ba..c7901764 100644 --- a/src/WebSocketApi.cpp +++ b/src/WebSocketApi.cpp @@ -16,8 +16,7 @@ WebSocketApi::Vendor *get_vendor(calldata_t *cd) return static_cast(voidVendor); } -WebSocketApi::WebSocketApi(EventCallback cb) : - _eventCallback(cb) +WebSocketApi::WebSocketApi() { blog_debug("[WebSocketApi::WebSocketApi] Setting up..."); @@ -50,6 +49,11 @@ WebSocketApi::~WebSocketApi() blog_debug("[WebSocketApi::~WebSocketApi] Finished."); } +void WebSocketApi::SetEventCallback(EventCallback cb) +{ + _eventCallback = cb; +} + enum WebSocketApi::RequestReturnCode WebSocketApi::PerformVendorRequest(std::string vendorName, std::string requestType, obs_data_t *requestData, obs_data_t *responseData) { std::shared_lock l(_mutex); @@ -196,6 +200,9 @@ void WebSocketApi::vendor_event_emit_cb(void *priv_data, calldata_t *cd) auto eventData = static_cast(voidEventData); + if (!c->_eventCallback) + RETURN_FAILURE(); + c->_eventCallback(v->_name, eventType, eventData); RETURN_SUCCESS(); diff --git a/src/WebSocketApi.h b/src/WebSocketApi.h index 78d02cea..5c18c678 100644 --- a/src/WebSocketApi.h +++ b/src/WebSocketApi.h @@ -25,9 +25,11 @@ class WebSocketApi { std::map _requests; }; - WebSocketApi(EventCallback cb); + WebSocketApi(); ~WebSocketApi(); + void SetEventCallback(EventCallback cb); + enum RequestReturnCode PerformVendorRequest(std::string vendorName, std::string requestName, obs_data_t *requestData, obs_data_t *responseData); static void get_ph_cb(void *priv_data, calldata_t *cd); diff --git a/src/eventhandler/EventHandler.cpp b/src/eventhandler/EventHandler.cpp index 12b36733..07638dc0 100644 --- a/src/eventhandler/EventHandler.cpp +++ b/src/eventhandler/EventHandler.cpp @@ -134,9 +134,13 @@ void EventHandler::ConnectSourceSignals(obs_source_t *source) // Applies to inpu signal_handler_connect(sh, "hide", HandleInputShowStateChanged, this); signal_handler_connect(sh, "mute", HandleInputMuteStateChanged, this); signal_handler_connect(sh, "volume", HandleInputVolumeChanged, this); + signal_handler_connect(sh, "audio_balance", HandleInputAudioBalanceChanged, this); signal_handler_connect(sh, "audio_sync", HandleInputAudioSyncOffsetChanged, this); signal_handler_connect(sh, "audio_mixers", HandleInputAudioTracksChanged, this); - //signal_handler_connect(sh, "audio_monitoring", HandleInputAudioMonitorTypeChanged, this); + signal_handler_connect(sh, "audio_monitoring", HandleInputAudioMonitorTypeChanged, this); + signal_handler_connect(sh, "filter_add", HandleSourceFilterCreated, this); + signal_handler_connect(sh, "filter_remove", HandleSourceFilterRemoved, this); + signal_handler_connect(sh, "reorder_filters", HandleSourceFilterListReindexed, this); if (sourceType == OBS_SOURCE_TYPE_INPUT) { signal_handler_connect(sh, "media_started", HandleMediaInputPlaybackStarted, this); @@ -156,6 +160,7 @@ void EventHandler::ConnectSourceSignals(obs_source_t *source) // Applies to inpu signal_handler_connect(sh, "reorder", HandleSceneItemListReindexed, this); signal_handler_connect(sh, "item_visible", HandleSceneItemEnableStateChanged, this); signal_handler_connect(sh, "item_locked", HandleSceneItemLockStateChanged, this); + signal_handler_connect(sh, "item_select", HandleSceneItemSelected, this); signal_handler_connect(sh, "item_transform", HandleSceneItemTransformChanged, this); } } @@ -174,9 +179,10 @@ void EventHandler::DisconnectSourceSignals(obs_source_t *source) signal_handler_disconnect(sh, "hide", HandleInputShowStateChanged, this); signal_handler_disconnect(sh, "mute", HandleInputMuteStateChanged, this); signal_handler_disconnect(sh, "volume", HandleInputVolumeChanged, this); + signal_handler_disconnect(sh, "audio_balance", HandleInputAudioBalanceChanged, this); signal_handler_disconnect(sh, "audio_sync", HandleInputAudioSyncOffsetChanged, this); signal_handler_disconnect(sh, "audio_mixers", HandleInputAudioTracksChanged, this); - //signal_handler_disconnect(sh, "audio_monitoring", HandleInputAudioMonitorTypeChanged, this); + signal_handler_disconnect(sh, "audio_monitoring", HandleInputAudioMonitorTypeChanged, this); signal_handler_disconnect(sh, "media_started", HandleMediaInputPlaybackStarted, this); signal_handler_disconnect(sh, "media_ended", HandleMediaInputPlaybackEnded, this); signal_handler_disconnect(sh, "media_pause", SourceMediaPauseMultiHandler, this); @@ -185,6 +191,9 @@ void EventHandler::DisconnectSourceSignals(obs_source_t *source) signal_handler_disconnect(sh, "media_stopped", SourceMediaStopMultiHandler, this); signal_handler_disconnect(sh, "media_next", SourceMediaNextMultiHandler, this); signal_handler_disconnect(sh, "media_previous", SourceMediaPreviousMultiHandler, this); + signal_handler_disconnect(sh, "filter_add", HandleSourceFilterCreated, this); + signal_handler_disconnect(sh, "filter_remove", HandleSourceFilterRemoved, this); + signal_handler_disconnect(sh, "reorder_filters", HandleSourceFilterListReindexed, this); // Scenes signal_handler_disconnect(sh, "item_add", HandleSceneItemCreated, this); @@ -192,12 +201,37 @@ void EventHandler::DisconnectSourceSignals(obs_source_t *source) signal_handler_disconnect(sh, "reorder", HandleSceneItemListReindexed, this); signal_handler_disconnect(sh, "item_visible", HandleSceneItemEnableStateChanged, this); signal_handler_disconnect(sh, "item_locked", HandleSceneItemLockStateChanged, this); + signal_handler_disconnect(sh, "item_select", HandleSceneItemSelected, this); signal_handler_disconnect(sh, "item_transform", HandleSceneItemTransformChanged, this); } +void EventHandler::ConnectFilterSignals(obs_source_t *filter) +{ + if (!filter || obs_source_removed(filter)) + return; + + DisconnectFilterSignals(filter); + + signal_handler_t* sh = obs_source_get_signal_handler(filter); + + signal_handler_connect(sh, "enable", HandleSourceFilterEnableStateChanged, this); + signal_handler_connect(sh, "rename", HandleSourceFilterNameChanged, this); +} + +void EventHandler::DisconnectFilterSignals(obs_source_t *filter) +{ + if (!filter) + return; + + signal_handler_t* sh = obs_source_get_signal_handler(filter); + + signal_handler_disconnect(sh, "enable", HandleSourceFilterEnableStateChanged, this); + signal_handler_disconnect(sh, "rename", HandleSourceFilterNameChanged, this); +} + void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_data) { - auto eventHandler = reinterpret_cast(private_data); + auto eventHandler = static_cast(private_data); if (!eventHandler->_obsLoaded.load()) { if (event == OBS_FRONTEND_EVENT_FINISHED_LOADING) { @@ -207,18 +241,38 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_ // In the case that plugins become hotloadable, this will have to go back into `EventHandler::EventHandler()` // Enumerate inputs and connect each one - obs_enum_sources([](void* param, obs_source_t* source) { - auto eventHandler = reinterpret_cast(param); - eventHandler->ConnectSourceSignals(source); - return true; - }, private_data); + { + auto enumInputs = [](void *param, obs_source_t *source) { + auto eventHandler = static_cast(param); + eventHandler->ConnectSourceSignals(source); + + auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param){ + auto eventHandler = static_cast(param); + eventHandler->ConnectFilterSignals(filter); + }; + obs_source_enum_filters(source, enumFilters, param); + + return true; + }; + obs_enum_sources(enumInputs, private_data); + } // Enumerate scenes and connect each one - obs_enum_scenes([](void* param, obs_source_t* source) { - auto eventHandler = reinterpret_cast(param); - eventHandler->ConnectSourceSignals(source); - return true; - }, private_data); + { + auto enumScenes = [](void *param, obs_source_t *source) { + auto eventHandler = static_cast(param); + eventHandler->ConnectSourceSignals(source); + + auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param){ + auto eventHandler = static_cast(param); + eventHandler->ConnectFilterSignals(filter); + }; + obs_source_enum_filters(source, enumFilters, param); + + return true; + }; + obs_enum_scenes(enumScenes, private_data); + } blog_debug("[EventHandler::OnFrontendEvent] Finished."); @@ -240,18 +294,38 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_ // In the case that plugins become hotloadable, this will have to go back into `EventHandler::~EventHandler()` // Enumerate inputs and disconnect each one - obs_enum_sources([](void* param, obs_source_t* source) { - auto eventHandler = reinterpret_cast(param); - eventHandler->DisconnectSourceSignals(source); - return true; - }, private_data); + { + auto enumInputs = [](void *param, obs_source_t *source) { + auto eventHandler = static_cast(param); + eventHandler->DisconnectSourceSignals(source); + + auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param){ + auto eventHandler = static_cast(param); + eventHandler->ConnectFilterSignals(filter); + }; + obs_source_enum_filters(source, enumFilters, param); + + return true; + }; + obs_enum_sources(enumInputs, private_data); + } // Enumerate scenes and disconnect each one - obs_enum_scenes([](void* param, obs_source_t* source) { - auto eventHandler = reinterpret_cast(param); - eventHandler->DisconnectSourceSignals(source); - return true; - }, private_data); + { + auto enumScenes = [](void *param, obs_source_t *source) { + auto eventHandler = static_cast(param); + eventHandler->DisconnectSourceSignals(source); + + auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param){ + auto eventHandler = static_cast(param); + eventHandler->ConnectFilterSignals(filter); + }; + obs_source_enum_filters(source, enumFilters, param); + + return true; + }; + obs_enum_scenes(enumScenes, private_data); + } blog_debug("[EventHandler::OnFrontendEvent] Finished."); @@ -264,18 +338,18 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_ break; // Config - //case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING: - // eventHandler->HandleCurrentSceneCollectionChanging(); - // break; + case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING: + eventHandler->HandleCurrentSceneCollectionChanging(); + break; case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED: eventHandler->HandleCurrentSceneCollectionChanged(); break; case OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED: eventHandler->HandleSceneCollectionListChanged(); break; - //case OBS_FRONTEND_EVENT_PROFILE_CHANGING: - // eventHandler->HandleCurrentProfileChanging(); - // break; + case OBS_FRONTEND_EVENT_PROFILE_CHANGING: + eventHandler->HandleCurrentProfileChanging(); + break; case OBS_FRONTEND_EVENT_PROFILE_CHANGED: eventHandler->HandleCurrentProfileChanged(); break; @@ -285,7 +359,7 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_ // Scenes case OBS_FRONTEND_EVENT_SCENE_CHANGED: - eventHandler->HandleCurrentSceneChanged(); + eventHandler->HandleCurrentProgramSceneChanged(); break; case OBS_FRONTEND_EVENT_PREVIEW_SCENE_CHANGED: eventHandler->HandleCurrentPreviewSceneChanged(); @@ -296,10 +370,12 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_ // Transitions case OBS_FRONTEND_EVENT_TRANSITION_CHANGED: + eventHandler->HandleCurrentSceneTransitionChanged(); break; case OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED: break; case OBS_FRONTEND_EVENT_TRANSITION_DURATION_CHANGED: + eventHandler->HandleCurrentSceneTransitionDurationChanged(); break; // Outputs @@ -363,7 +439,7 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_ // Only called for creation of a public source void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); // Don't react to signals until OBS has finished loading if (!eventHandler->_obsLoaded.load()) @@ -379,10 +455,6 @@ void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data) case OBS_SOURCE_TYPE_INPUT: eventHandler->HandleInputCreated(source); break; - case OBS_SOURCE_TYPE_FILTER: - break; - case OBS_SOURCE_TYPE_TRANSITION: - break; case OBS_SOURCE_TYPE_SCENE: eventHandler->HandleSceneCreated(source); break; @@ -394,7 +466,7 @@ void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data) // Only called for destruction of a public source void EventHandler::SourceDestroyedMultiHandler(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); // We can't use any smart types here because releasing the source will cause infinite recursion obs_source_t *source = GetCalldataPointer(data, "source"); @@ -413,10 +485,6 @@ void EventHandler::SourceDestroyedMultiHandler(void *param, calldata_t *data) // We have to call `InputRemoved` with source_destroy because source_removed is not called when an input's last scene item is removed eventHandler->HandleInputRemoved(source); break; - case OBS_SOURCE_TYPE_FILTER: - break; - case OBS_SOURCE_TYPE_TRANSITION: - break; case OBS_SOURCE_TYPE_SCENE: break; default: @@ -426,7 +494,7 @@ void EventHandler::SourceDestroyedMultiHandler(void *param, calldata_t *data) void EventHandler::SourceRemovedMultiHandler(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); if (!eventHandler->_obsLoaded.load()) return; @@ -438,10 +506,6 @@ void EventHandler::SourceRemovedMultiHandler(void *param, calldata_t *data) switch (obs_source_get_type(source)) { case OBS_SOURCE_TYPE_INPUT: break; - case OBS_SOURCE_TYPE_FILTER: - break; - case OBS_SOURCE_TYPE_TRANSITION: - break; case OBS_SOURCE_TYPE_SCENE: // Scenes emit the `removed` signal when they are removed from OBS, as expected eventHandler->HandleSceneRemoved(source); @@ -453,7 +517,7 @@ void EventHandler::SourceRemovedMultiHandler(void *param, calldata_t *data) void EventHandler::SourceRenamedMultiHandler(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); if (!eventHandler->_obsLoaded.load()) return; @@ -471,8 +535,6 @@ void EventHandler::SourceRenamedMultiHandler(void *param, calldata_t *data) case OBS_SOURCE_TYPE_INPUT: eventHandler->HandleInputNameChanged(source, oldSourceName, sourceName); break; - case OBS_SOURCE_TYPE_FILTER: - break; case OBS_SOURCE_TYPE_TRANSITION: break; case OBS_SOURCE_TYPE_SCENE: diff --git a/src/eventhandler/EventHandler.h b/src/eventhandler/EventHandler.h index 317a2b76..00ac4410 100644 --- a/src/eventhandler/EventHandler.h +++ b/src/eventhandler/EventHandler.h @@ -22,12 +22,11 @@ with this program. If not, see #include #include #include -#include #include "types/EventSubscription.h" #include "../obs-websocket.h" #include "../utils/Obs.h" -#include "../utils/ObsVolumeMeter.h" +#include "../utils/Obs_VolumeMeter.h" #include "../plugin-macros.generated.h" class EventHandler @@ -59,6 +58,9 @@ class EventHandler void ConnectSourceSignals(obs_source_t *source); void DisconnectSourceSignals(obs_source_t *source); + void ConnectFilterSignals(obs_source_t *filter); + void DisconnectFilterSignals(obs_source_t *filter); + void BroadcastEvent(uint64_t requiredIntent, std::string eventType, json eventData = nullptr, uint8_t rpcVersion = 0); // Signal handler: frontend @@ -95,7 +97,7 @@ class EventHandler void HandleSceneCreated(obs_source_t *source); void HandleSceneRemoved(obs_source_t *source); void HandleSceneNameChanged(obs_source_t *source, std::string oldSceneName, std::string sceneName); - void HandleCurrentSceneChanged(); + void HandleCurrentProgramSceneChanged(); void HandleCurrentPreviewSceneChanged(); void HandleSceneListChanged(); @@ -108,14 +110,14 @@ class EventHandler static void HandleInputShowStateChanged(void *param, calldata_t *data); // Direct callback static void HandleInputMuteStateChanged(void *param, calldata_t *data); // Direct callback static void HandleInputVolumeChanged(void *param, calldata_t *data); // Direct callback + static void HandleInputAudioBalanceChanged(void *param, calldata_t *data); // Direct callback static void HandleInputAudioSyncOffsetChanged(void *param, calldata_t *data); // Direct callback static void HandleInputAudioTracksChanged(void *param, calldata_t *data); // Direct callback static void HandleInputAudioMonitorTypeChanged(void *param, calldata_t *data); // Direct callback // Transitions - void HandleTransitionCreated(obs_source_t *source); - void HandleTransitionRemoved(obs_source_t *source); - void HandleTransitionNameChanged(obs_source_t *source, std::string oldTransitionName, std::string transitionName); + void HandleCurrentSceneTransitionChanged(); + void HandleCurrentSceneTransitionDurationChanged(); // Outputs void HandleStreamStateChanged(ObsOutputState state); @@ -130,10 +132,18 @@ class EventHandler static void HandleSceneItemListReindexed(void *param, calldata_t *data); // Direct callback static void HandleSceneItemEnableStateChanged(void *param, calldata_t *data); // Direct callback static void HandleSceneItemLockStateChanged(void *param, calldata_t *data); // Direct callback + static void HandleSceneItemSelected(void *param, calldata_t *data); // Direct callback static void HandleSceneItemTransformChanged(void *param, calldata_t *data); // Direct callback // Media Inputs static void HandleMediaInputPlaybackStarted(void *param, calldata_t *data); // Direct callback static void HandleMediaInputPlaybackEnded(void *param, calldata_t *data); // Direct callback void HandleMediaInputActionTriggered(obs_source_t *source, ObsMediaInputAction action); + + // Filters + static void HandleSourceFilterNameChanged(void *param, calldata_t *data); // Direct callback + static void HandleSourceFilterCreated(void *param, calldata_t *data); // Direct callback + static void HandleSourceFilterRemoved(void *param, calldata_t *data); // Direct callback + static void HandleSourceFilterListReindexed(void *param, calldata_t *data); // Direct callback + static void HandleSourceFilterEnableStateChanged(void *param, calldata_t *data); // Direct callback }; diff --git a/src/eventhandler/EventHandler_Config.cpp b/src/eventhandler/EventHandler_Config.cpp index 8082ecba..d93bc621 100644 --- a/src/eventhandler/EventHandler_Config.cpp +++ b/src/eventhandler/EventHandler_Config.cpp @@ -80,7 +80,7 @@ void EventHandler::HandleCurrentSceneCollectionChanged() void EventHandler::HandleSceneCollectionListChanged() { json eventData; - eventData["sceneCollections"] = Utils::Obs::ListHelper::GetSceneCollectionList(); + eventData["sceneCollections"] = Utils::Obs::ArrayHelper::GetSceneCollectionList(); BroadcastEvent(EventSubscription::Config, "SceneCollectionListChanged", eventData); } @@ -140,6 +140,6 @@ void EventHandler::HandleCurrentProfileChanged() void EventHandler::HandleProfileListChanged() { json eventData; - eventData["profiles"] = Utils::Obs::ListHelper::GetProfileList(); + eventData["profiles"] = Utils::Obs::ArrayHelper::GetProfileList(); BroadcastEvent(EventSubscription::Config, "ProfileListChanged", eventData); } diff --git a/src/eventhandler/EventHandler_Filters.cpp b/src/eventhandler/EventHandler_Filters.cpp index 6e0f4c11..a66d5035 100644 --- a/src/eventhandler/EventHandler_Filters.cpp +++ b/src/eventhandler/EventHandler_Filters.cpp @@ -18,3 +18,174 @@ with this program. If not, see */ #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(param); + + obs_source_t *source = GetCalldataPointer(data, "source"); + obs_source_t *filter = GetCalldataPointer(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(param); + + obs_source_t *source = GetCalldataPointer(data, "source"); + obs_source_t *filter = GetCalldataPointer(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 | 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(param); + + obs_source_t *source = GetCalldataPointer(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(param); + + obs_source_t *filter = GetCalldataPointer(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(param); + + obs_source_t *filter = GetCalldataPointer(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); +} diff --git a/src/eventhandler/EventHandler_General.cpp b/src/eventhandler/EventHandler_General.cpp index db1ee8ea..ad46e4e9 100644 --- a/src/eventhandler/EventHandler_General.cpp +++ b/src/eventhandler/EventHandler_General.cpp @@ -34,23 +34,3 @@ void EventHandler::HandleExitStarted() { BroadcastEvent(EventSubscription::General, "ExitStarted"); } - -/** - * Studio mode has been enabled or disabled. - * - * @dataField studioModeEnabled | Boolean | True == Enabled, False == Disabled - * - * @eventType StudioModeStateChanged - * @eventSubscription General - * @complexity 1 - * @rpcVersion -1 - * @initialVersion 5.0.0 - * @category general - * @api events - */ -void EventHandler::HandleStudioModeStateChanged(bool enabled) -{ - json eventData; - eventData["studioModeEnabled"] = enabled; - BroadcastEvent(EventSubscription::General, "StudioModeStateChanged", eventData); -} diff --git a/src/eventhandler/EventHandler_Inputs.cpp b/src/eventhandler/EventHandler_Inputs.cpp index c827c697..2a2050fb 100644 --- a/src/eventhandler/EventHandler_Inputs.cpp +++ b/src/eventhandler/EventHandler_Inputs.cpp @@ -19,6 +19,23 @@ with this program. If not, see #include "EventHandler.h" +/** + * An input has been created. + * + * @dataField inputName | String | Name of the input + * @dataField inputKind | String | The kind of the input + * @dataField unversionedInputKind | String | The unversioned kind of input (aka no `_v2` stuff) + * @dataField inputSettings | Object | The settings configured to the input when it was created + * @dataField defaultInputSettings | Object | The default settings for the input + * + * @eventType InputCreated + * @eventSubscription Inputs + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category inputs + */ void EventHandler::HandleInputCreated(obs_source_t *source) { std::string inputKind = obs_source_get_id(source); @@ -34,6 +51,19 @@ void EventHandler::HandleInputCreated(obs_source_t *source) BroadcastEvent(EventSubscription::Inputs, "InputCreated", eventData); } +/** + * An input has been removed. + * + * @dataField inputName | String | Name of the input + * + * @eventType InputRemoved + * @eventSubscription Inputs + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category inputs + */ void EventHandler::HandleInputRemoved(obs_source_t *source) { json eventData; @@ -41,6 +71,20 @@ void EventHandler::HandleInputRemoved(obs_source_t *source) BroadcastEvent(EventSubscription::Inputs, "InputRemoved", eventData); } +/** + * The name of an input has changed. + * + * @dataField oldInputName | String | Old name of the input + * @dataField inputName | String | New name of the input + * + * @eventType InputNameChanged + * @eventSubscription Inputs + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category inputs + */ void EventHandler::HandleInputNameChanged(obs_source_t *, std::string oldInputName, std::string inputName) { json eventData; @@ -49,16 +93,25 @@ void EventHandler::HandleInputNameChanged(obs_source_t *, std::string oldInputNa BroadcastEvent(EventSubscription::Inputs, "InputNameChanged", eventData); } -void EventHandler::HandleInputVolumeMeters(std::vector inputs) -{ - json eventData; - eventData["inputs"] = inputs; - BroadcastEvent(EventSubscription::InputVolumeMeters, "InputVolumeMeters", eventData); -} - +/** + * An input's active state has changed. + * + * When an input is active, it means it's being shown by the program feed. + * + * @dataField inputName | String | Name of the input + * @dataField videoActive | Boolean | Whether the input is active + * + * @eventType InputActiveStateChanged + * @eventSubscription InputActiveStateChanged + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category inputs + */ void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); if (!eventHandler->_inputActiveStateChangedRef.load()) return; @@ -76,9 +129,25 @@ void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data) eventHandler->BroadcastEvent(EventSubscription::InputActiveStateChanged, "InputActiveStateChanged", eventData); } +/** + * An input's show state has changed. + * + * When an input is showing, it means it's being shown by the preview or a dialog. + * + * @dataField inputName | String | Name of the input + * @dataField videoShowing | Boolean | Whether the input is showing + * + * @eventType InputShowStateChanged + * @eventSubscription InputShowStateChanged + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category inputs + */ void EventHandler::HandleInputShowStateChanged(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); if (!eventHandler->_inputShowStateChangedRef.load()) return; @@ -96,9 +165,23 @@ void EventHandler::HandleInputShowStateChanged(void *param, calldata_t *data) eventHandler->BroadcastEvent(EventSubscription::InputShowStateChanged, "InputShowStateChanged", eventData); } +/** + * An input's mute state has changed. + * + * @dataField inputName | String | Name of the input + * @dataField inputMuted | Boolean | Whether the input is muted + * + * @eventType InputMuteStateChanged + * @eventSubscription Inputs + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category inputs + */ void EventHandler::HandleInputMuteStateChanged(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_source_t *source = GetCalldataPointer(data, "source"); if (!source) @@ -113,9 +196,24 @@ void EventHandler::HandleInputMuteStateChanged(void *param, calldata_t *data) eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputMuteStateChanged", eventData); } +/** + * An input's volume level has changed. + * + * @dataField inputName | String | Name of the input + * @dataField inputVolumeMul | Number | New volume level in multimap + * @dataField inputVolumeDb | Number | New volume level in dB + * + * @eventType InputVolumeChanged + * @eventSubscription Inputs + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category inputs + */ void EventHandler::HandleInputVolumeChanged(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_source_t *source = GetCalldataPointer(data, "source"); if (!source) @@ -138,9 +236,56 @@ void EventHandler::HandleInputVolumeChanged(void *param, calldata_t *data) eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputVolumeChanged", eventData); } +/** + * The audio balance value of an input has changed. + * + * @dataField inputName | String | Name of the affected input + * @dataField inputAudioBalance | Number | New audio balance value of the input + * + * @eventType InputAudioBalanceChanged + * @eventSubscription Inputs + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @category inputs + * @api events + */ +void EventHandler::HandleInputAudioBalanceChanged(void *param, calldata_t *data) +{ + auto eventHandler = static_cast(param); + + obs_source_t *source = GetCalldataPointer(data, "source"); + if (!source) + return; + + if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT) + return; + + float inputAudioBalance = (float)calldata_float(data, "balance"); + + json eventData; + eventData["inputName"] = obs_source_get_name(source); + eventData["inputAudioBalance"] = inputAudioBalance; + eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioBalanceChanged", eventData); +} + +/** + * The sync offset of an input has changed. + * + * @dataField inputName | String | Name of the input + * @dataField inputAudioSyncOffset | Number | New sync offset in milliseconds + * + * @eventType InputAudioSyncOffsetChanged + * @eventSubscription Inputs + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category inputs + */ void EventHandler::HandleInputAudioSyncOffsetChanged(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_source_t *source = GetCalldataPointer(data, "source"); if (!source) @@ -157,9 +302,23 @@ void EventHandler::HandleInputAudioSyncOffsetChanged(void *param, calldata_t *da eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioSyncOffsetChanged", eventData); } +/** + * The audio tracks of an input have changed. + * + * @dataField inputName | String | Name of the input + * @dataField inputAudioTracks | Object | Object of audio tracks along with their associated enable states + * + * @eventType InputAudioTracksChanged + * @eventSubscription Inputs + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category inputs + */ void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_source_t *source = GetCalldataPointer(data, "source"); if (!source) @@ -181,9 +340,28 @@ void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data) eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioTracksChanged", eventData); } +/** + * The monitor type of an input has changed. + * + * Available types are: + * - `OBS_MONITORING_TYPE_NONE` + * - `OBS_MONITORING_TYPE_MONITOR_ONLY` + * - `OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT` + * + * @dataField inputName | String | Name of the input + * @dataField monitorType | String | New monitor type of the input + * + * @eventType InputAudioMonitorTypeChanged + * @eventSubscription Inputs + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category inputs + */ void EventHandler::HandleInputAudioMonitorTypeChanged(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_source_t *source = GetCalldataPointer(data, "source"); if (!source) @@ -194,22 +372,30 @@ void EventHandler::HandleInputAudioMonitorTypeChanged(void *param, calldata_t *d enum obs_monitoring_type monitorType = (obs_monitoring_type)calldata_int(data, "type"); - std::string monitorTypeString; - switch (monitorType) { - default: - case OBS_MONITORING_TYPE_NONE: - monitorTypeString = "OBS_WEBSOCKET_MONITOR_TYPE_NONE"; - break; - case OBS_MONITORING_TYPE_MONITOR_ONLY: - monitorTypeString = "OBS_WEBSOCKET_MONITOR_TYPE_MONITOR_ONLY"; - break; - case OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT: - monitorTypeString = "OBS_WEBSOCKET_MONITOR_TYPE_MONITOR_AND_OUTPUT"; - break; - } + std::string monitorTypeString = Utils::Obs::StringHelper::GetInputMonitorType(monitorType); json eventData; eventData["inputName"] = obs_source_get_name(source); eventData["monitorType"] = monitorTypeString; eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioMonitorTypeChanged", eventData); } + +/** + * A high-volume event providing volume levels of all active inputs every 50 milliseconds. + * + * @dataField inputs | Array | 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 inputs) +{ + json eventData; + eventData["inputs"] = inputs; + BroadcastEvent(EventSubscription::InputVolumeMeters, "InputVolumeMeters", eventData); +} diff --git a/src/eventhandler/EventHandler_MediaInputs.cpp b/src/eventhandler/EventHandler_MediaInputs.cpp index 146a8c8c..faa000c6 100644 --- a/src/eventhandler/EventHandler_MediaInputs.cpp +++ b/src/eventhandler/EventHandler_MediaInputs.cpp @@ -35,7 +35,7 @@ std::string GetMediaInputActionString(ObsMediaInputAction action) { void EventHandler::SourceMediaPauseMultiHandler(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_source_t *source = GetCalldataPointer(data, "source"); if (!source) @@ -49,7 +49,7 @@ void EventHandler::SourceMediaPauseMultiHandler(void *param, calldata_t *data) void EventHandler::SourceMediaPlayMultiHandler(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_source_t *source = GetCalldataPointer(data, "source"); if (!source) @@ -63,7 +63,7 @@ void EventHandler::SourceMediaPlayMultiHandler(void *param, calldata_t *data) void EventHandler::SourceMediaRestartMultiHandler(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_source_t *source = GetCalldataPointer(data, "source"); if (!source) @@ -77,7 +77,7 @@ void EventHandler::SourceMediaRestartMultiHandler(void *param, calldata_t *data) void EventHandler::SourceMediaStopMultiHandler(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_source_t *source = GetCalldataPointer(data, "source"); if (!source) @@ -91,7 +91,7 @@ void EventHandler::SourceMediaStopMultiHandler(void *param, calldata_t *data) void EventHandler::SourceMediaNextMultiHandler(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_source_t *source = GetCalldataPointer(data, "source"); if (!source) @@ -105,7 +105,7 @@ void EventHandler::SourceMediaNextMultiHandler(void *param, calldata_t *data) void EventHandler::SourceMediaPreviousMultiHandler(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_source_t *source = GetCalldataPointer(data, "source"); if (!source) @@ -117,9 +117,22 @@ void EventHandler::SourceMediaPreviousMultiHandler(void *param, calldata_t *data eventHandler->HandleMediaInputActionTriggered(source, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS); } +/** + * A media input has started playing. + * + * @dataField inputName | String | Name of the input + * + * @eventType MediaInputPlaybackStarted + * @eventSubscription MediaInputs + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category media inputs + */ void EventHandler::HandleMediaInputPlaybackStarted(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_source_t *source = GetCalldataPointer(data, "source"); if (!source) @@ -133,9 +146,22 @@ void EventHandler::HandleMediaInputPlaybackStarted(void *param, calldata_t *data eventHandler->BroadcastEvent(EventSubscription::MediaInputs, "MediaInputPlaybackStarted", eventData); } +/** + * A media input has finished playing. + * + * @dataField inputName | String | Name of the input + * + * @eventType MediaInputPlaybackEnded + * @eventSubscription MediaInputs + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category media inputs + */ void EventHandler::HandleMediaInputPlaybackEnded(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_source_t *source = GetCalldataPointer(data, "source"); if (!source) @@ -149,6 +175,20 @@ void EventHandler::HandleMediaInputPlaybackEnded(void *param, calldata_t *data) eventHandler->BroadcastEvent(EventSubscription::MediaInputs, "MediaInputPlaybackEnded", eventData); } +/** + * An action has been performed on an input. + * + * @dataField inputName | String | Name of the input + * @dataField mediaAction | String | Action performed on the input. See `ObsMediaInputAction` enum + * + * @eventType MediaInputActionTriggered + * @eventSubscription MediaInputs + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category media inputs + */ void EventHandler::HandleMediaInputActionTriggered(obs_source_t *source, ObsMediaInputAction action) { json eventData; diff --git a/src/eventhandler/EventHandler_Outputs.cpp b/src/eventhandler/EventHandler_Outputs.cpp index 19238d75..bf9acfb6 100644 --- a/src/eventhandler/EventHandler_Outputs.cpp +++ b/src/eventhandler/EventHandler_Outputs.cpp @@ -19,21 +19,7 @@ with this program. If not, see #include "EventHandler.h" -#define CASE(x) case x: return #x; - -std::string GetOutputStateString(ObsOutputState state) { - switch (state) { - default: - CASE(OBS_WEBSOCKET_OUTPUT_STARTING) - CASE(OBS_WEBSOCKET_OUTPUT_STARTED) - CASE(OBS_WEBSOCKET_OUTPUT_STOPPING) - CASE(OBS_WEBSOCKET_OUTPUT_STOPPED) - CASE(OBS_WEBSOCKET_OUTPUT_PAUSED) - CASE(OBS_WEBSOCKET_OUTPUT_RESUMED) - } -} - -bool GetOutputStateActive(ObsOutputState state) { +static bool GetOutputStateActive(ObsOutputState state) { switch(state) { case OBS_WEBSOCKET_OUTPUT_STARTED: case OBS_WEBSOCKET_OUTPUT_RESUMED: @@ -48,38 +34,107 @@ bool GetOutputStateActive(ObsOutputState state) { } } +/** + * The state of the stream output has changed. + * + * @dataField outputActive | Boolean | Whether the output is active + * @dataField outputState | String | The specific state of the output + * + * @eventType StreamStateChanged + * @eventSubscription Outputs + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category outputs + */ void EventHandler::HandleStreamStateChanged(ObsOutputState state) { json eventData; eventData["outputActive"] = GetOutputStateActive(state); - eventData["outputState"] = GetOutputStateString(state); + eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state); BroadcastEvent(EventSubscription::Outputs, "StreamStateChanged", eventData); } +/** + * The state of the record output has changed. + * + * @dataField outputActive | Boolean | Whether the output is active + * @dataField outputState | String | The specific state of the output + * + * @eventType RecordStateChanged + * @eventSubscription Outputs + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category outputs + */ void EventHandler::HandleRecordStateChanged(ObsOutputState state) { json eventData; eventData["outputActive"] = GetOutputStateActive(state); - eventData["outputState"] = GetOutputStateString(state); + eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state); BroadcastEvent(EventSubscription::Outputs, "RecordStateChanged", eventData); } +/** + * The state of the replay buffer output has changed. + * + * @dataField outputActive | Boolean | Whether the output is active + * @dataField outputState | String | The specific state of the output + * + * @eventType ReplayBufferStateChanged + * @eventSubscription Outputs + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category outputs + */ void EventHandler::HandleReplayBufferStateChanged(ObsOutputState state) { json eventData; eventData["outputActive"] = GetOutputStateActive(state); - eventData["outputState"] = GetOutputStateString(state); + eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state); BroadcastEvent(EventSubscription::Outputs, "ReplayBufferStateChanged", eventData); } +/** + * The state of the virtualcam output has changed. + * + * @dataField outputActive | Boolean | Whether the output is active + * @dataField outputState | String | The specific state of the output + * + * @eventType VirtualcamStateChanged + * @eventSubscription Outputs + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category outputs + */ void EventHandler::HandleVirtualcamStateChanged(ObsOutputState state) { json eventData; eventData["outputActive"] = GetOutputStateActive(state); - eventData["outputState"] = GetOutputStateString(state); + eventData["outputState"] = Utils::Obs::StringHelper::GetOutputState(state); BroadcastEvent(EventSubscription::Outputs, "VirtualcamStateChanged", eventData); } +/** + * The replay buffer has been saved. + * + * @dataField savedReplayPath | String | Path of the saved replay file + * + * @eventType ReplayBufferSaved + * @eventSubscription Outputs + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category outputs + */ void EventHandler::HandleReplayBufferSaved() { json eventData; diff --git a/src/eventhandler/EventHandler_SceneItems.cpp b/src/eventhandler/EventHandler_SceneItems.cpp index b811dc22..a161f78e 100644 --- a/src/eventhandler/EventHandler_SceneItems.cpp +++ b/src/eventhandler/EventHandler_SceneItems.cpp @@ -19,9 +19,25 @@ with this program. If not, see #include "EventHandler.h" +/** + * A scene item has been created. + * + * @dataField sceneName | String | Name of the scene the item was added to + * @dataField sourceName | String | Name of the underlying source (input/scene) + * @dataField sceneItemId | Number | Numeric ID of the scene item + * @dataField sceneItemIndex | Number | Index position of the item + * + * @eventType SceneItemCreated + * @eventSubscription SceneItems + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category scene items + */ void EventHandler::HandleSceneItemCreated(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_scene_t *scene = GetCalldataPointer(data, "scene"); if (!scene) @@ -33,16 +49,32 @@ void EventHandler::HandleSceneItemCreated(void *param, calldata_t *data) json eventData; eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene)); - eventData["inputName"] = obs_source_get_name(obs_sceneitem_get_source(sceneItem)); + eventData["sourceName"] = obs_source_get_name(obs_sceneitem_get_source(sceneItem)); eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem); eventData["sceneItemIndex"] = obs_sceneitem_get_order_position(sceneItem); eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemCreated", eventData); } -// Will not be emitted if an item is removed due to the parent scene being removed. +/** + * A scene item has been removed. + * + * This event is not emitted when the scene the item is in is removed. + * + * @dataField sceneName | String | Name of the scene the item was removed from + * @dataField sourceName | String | Name of the underlying source (input/scene) + * @dataField sceneItemId | Number | Numeric ID of the scene item + * + * @eventType SceneItemRemoved + * @eventSubscription SceneItems + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category scene items + */ void EventHandler::HandleSceneItemRemoved(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_scene_t *scene = GetCalldataPointer(data, "scene"); if (!scene) @@ -54,15 +86,28 @@ void EventHandler::HandleSceneItemRemoved(void *param, calldata_t *data) json eventData; eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene)); - eventData["inputName"] = obs_source_get_name(obs_sceneitem_get_source(sceneItem)); + eventData["sourceName"] = obs_source_get_name(obs_sceneitem_get_source(sceneItem)); eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem); - eventData["sceneItemIndex"] = obs_sceneitem_get_order_position(sceneItem); eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemRemoved", eventData); } +/** + * A scene's item list has been reindexed. + * + * @dataField sceneName | String | Name of the scene + * @dataField sceneItems | Array | Array of scene item objects + * + * @eventType SceneItemListReindexed + * @eventSubscription SceneItems + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category scene items + */ void EventHandler::HandleSceneItemListReindexed(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_scene_t *scene = GetCalldataPointer(data, "scene"); if (!scene) @@ -70,13 +115,28 @@ void EventHandler::HandleSceneItemListReindexed(void *param, calldata_t *data) json eventData; eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene)); - eventData["sceneItems"] = Utils::Obs::ListHelper::GetSceneItemList(scene, true); + eventData["sceneItems"] = Utils::Obs::ArrayHelper::GetSceneItemList(scene, true); eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemListReindexed", eventData); } +/** + * A scene item's enable state has changed. + * + * @dataField sceneName | String | Name of the scene the item is in + * @dataField sceneItemId | Number | Numeric ID of the scene item + * @dataField sceneItemEnabled | Boolean | Whether the scene item is enabled (visible) + * + * @eventType SceneItemEnableStateChanged + * @eventSubscription SceneItems + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category scene items + */ void EventHandler::HandleSceneItemEnableStateChanged(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_scene_t *scene = GetCalldataPointer(data, "scene"); if (!scene) @@ -95,9 +155,24 @@ void EventHandler::HandleSceneItemEnableStateChanged(void *param, calldata_t *da eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemEnableStateChanged", eventData); } +/** + * A scene item's lock state has changed. + * + * @dataField sceneName | String | Name of the scene the item is in + * @dataField sceneItemId | Number | Numeric ID of the scene item + * @dataField sceneItemLocked | Boolean | Whether the scene item is locked + * + * @eventType SceneItemLockStateChanged + * @eventSubscription SceneItems + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category scene items + */ void EventHandler::HandleSceneItemLockStateChanged(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); obs_scene_t *scene = GetCalldataPointer(data, "scene"); if (!scene) @@ -116,9 +191,56 @@ void EventHandler::HandleSceneItemLockStateChanged(void *param, calldata_t *data eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemLockStateChanged", eventData); } +/** + * A scene item has been selected in the Ui. + * + * @dataField sceneName | String | Name of the scene the item is in + * @dataField sceneItemId | Number | Numeric ID of the scene item + * + * @eventType SceneItemSelected + * @eventSubscription SceneItems + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category scene items + */ +void EventHandler::HandleSceneItemSelected(void *param, calldata_t *data) +{ + auto eventHandler = static_cast(param); + + obs_scene_t *scene = GetCalldataPointer(data, "scene"); + if (!scene) + return; + + obs_sceneitem_t *sceneItem = GetCalldataPointer(data, "item"); + if (!sceneItem) + return; + + json eventData; + eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene)); + eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem); + eventHandler->BroadcastEvent(EventSubscription::SceneItems, "SceneItemSelected", eventData); +} + +/** + * The transform/crop of a scene item has changed. + * + * @dataField sceneName | String | The name of the scene the item is in + * @dataField sceneItemId | Number | Numeric ID of the scene item + * @dataField sceneItemTransform | Object | New transform/crop info of the scene item + * + * @eventType SceneItemTransformChanged + * @eventSubscription SceneItemTransformChanged + * @complexity 4 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category scene items + */ void EventHandler::HandleSceneItemTransformChanged(void *param, calldata_t *data) { - auto eventHandler = reinterpret_cast(param); + auto eventHandler = static_cast(param); if (!eventHandler->_sceneItemTransformChangedRef.load()) return; @@ -134,6 +256,6 @@ void EventHandler::HandleSceneItemTransformChanged(void *param, calldata_t *data json eventData; eventData["sceneName"] = obs_source_get_name(obs_scene_get_source(scene)); eventData["sceneItemId"] = obs_sceneitem_get_id(sceneItem); - eventData["sceneItemTransform"] = Utils::Obs::DataHelper::GetSceneItemTransform(sceneItem); + eventData["sceneItemTransform"] = Utils::Obs::ObjectHelper::GetSceneItemTransform(sceneItem); eventHandler->BroadcastEvent(EventSubscription::SceneItemTransformChanged, "SceneItemTransformChanged", eventData); } diff --git a/src/eventhandler/EventHandler_Scenes.cpp b/src/eventhandler/EventHandler_Scenes.cpp index 05c0f587..aca61e5a 100644 --- a/src/eventhandler/EventHandler_Scenes.cpp +++ b/src/eventhandler/EventHandler_Scenes.cpp @@ -19,6 +19,20 @@ with this program. If not, see #include "EventHandler.h" +/** + * A new scene has been created. + * + * @dataField sceneName | String | Name of the new scene + * @dataField isGroup | Boolean | Whether the new scene is a group + * + * @eventType SceneCreated + * @eventSubscription Scenes + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category scenes + */ void EventHandler::HandleSceneCreated(obs_source_t *source) { json eventData; @@ -27,6 +41,20 @@ void EventHandler::HandleSceneCreated(obs_source_t *source) BroadcastEvent(EventSubscription::Scenes, "SceneCreated", eventData); } +/** + * A scene has been removed. + * + * @dataField sceneName | String | Name of the removed scene + * @dataField isGroup | Boolean | Whether the scene was a group + * + * @eventType SceneRemoved + * @eventSubscription Scenes + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category scenes + */ void EventHandler::HandleSceneRemoved(obs_source_t *source) { json eventData; @@ -35,6 +63,20 @@ void EventHandler::HandleSceneRemoved(obs_source_t *source) BroadcastEvent(EventSubscription::Scenes, "SceneRemoved", eventData); } +/** + * The name of a scene has changed. + * + * @dataField oldSceneName | String | Old name of the scene + * @dataField sceneName | String | New name of the scene + * + * @eventType SceneNameChanged + * @eventSubscription Scenes + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category scenes + */ void EventHandler::HandleSceneNameChanged(obs_source_t *, std::string oldSceneName, std::string sceneName) { json eventData; @@ -43,15 +85,41 @@ void EventHandler::HandleSceneNameChanged(obs_source_t *, std::string oldSceneNa BroadcastEvent(EventSubscription::Scenes, "SceneNameChanged", eventData); } -void EventHandler::HandleCurrentSceneChanged() +/** + * The current program scene has changed. + * + * @dataField sceneName | String | Name of the scene that was switched to + * + * @eventType CurrentProgramSceneChanged + * @eventSubscription Scenes + * @complexity 1 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category scenes + */ +void EventHandler::HandleCurrentProgramSceneChanged() { OBSSourceAutoRelease currentScene = obs_frontend_get_current_scene(); json eventData; eventData["sceneName"] = obs_source_get_name(currentScene); - BroadcastEvent(EventSubscription::Scenes, "CurrentSceneChanged", eventData); + BroadcastEvent(EventSubscription::Scenes, "CurrentProgramSceneChanged", eventData); } +/** + * The current preview scene has changed. + * + * @dataField sceneName | String | Name of the scene that was switched to + * + * @eventType CurrentPreviewSceneChanged + * @eventSubscription Scenes + * @complexity 1 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category scenes + */ void EventHandler::HandleCurrentPreviewSceneChanged() { OBSSourceAutoRelease currentPreviewScene = obs_frontend_get_current_preview_scene(); @@ -65,9 +133,24 @@ void EventHandler::HandleCurrentPreviewSceneChanged() BroadcastEvent(EventSubscription::Scenes, "CurrentPreviewSceneChanged", eventData); } +/** + * The list of scenes has changed. + * + * TODO: Make OBS fire this event when scenes are reordered. + * + * @dataField scenes | Array | Updated array of scenes + * + * @eventType SceneListChanged + * @eventSubscription Scenes + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category scenes + */ void EventHandler::HandleSceneListChanged() { json eventData; - eventData["scenes"] = Utils::Obs::ListHelper::GetSceneList(); + eventData["scenes"] = Utils::Obs::ArrayHelper::GetSceneList(); BroadcastEvent(EventSubscription::Scenes, "SceneListChanged", eventData); } diff --git a/src/eventhandler/EventHandler_Transitions.cpp b/src/eventhandler/EventHandler_Transitions.cpp index 2ae8d85c..860d5cc6 100644 --- a/src/eventhandler/EventHandler_Transitions.cpp +++ b/src/eventhandler/EventHandler_Transitions.cpp @@ -19,26 +19,44 @@ with this program. If not, see #include "EventHandler.h" -void EventHandler::HandleTransitionCreated(obs_source_t *source) +/** + * The current scene transition has changed. + * + * @dataField transitionName | String | Name of the new transition + * + * @eventType CurrentSceneTransitionChanged + * @eventSubscription Transitions + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category transitions + */ +void EventHandler::HandleCurrentSceneTransitionChanged() { + OBSSourceAutoRelease transition = obs_frontend_get_current_transition(); + json eventData; - eventData["transitionName"] = obs_source_get_name(source); - eventData["transitionKind"] = obs_source_get_id(source); - eventData["transitionFixed"] = obs_transition_fixed(source); - BroadcastEvent(EventSubscription::Transitions, "TransitionCreated", eventData); + eventData["transitionName"] = obs_source_get_name(transition); + BroadcastEvent(EventSubscription::Transitions, "CurrentSceneTransitionChanged", eventData); } -void EventHandler::HandleTransitionRemoved(obs_source_t *source) +/** + * The current scene transition duration has changed. + * + * @dataField transitionDuration | Number | Transition duration in milliseconds + * + * @eventType CurrentSceneTransitionDurationChanged + * @eventSubscription Transitions + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api events + * @category transitions + */ +void EventHandler::HandleCurrentSceneTransitionDurationChanged() { json eventData; - eventData["transitionName"] = obs_source_get_name(source); - BroadcastEvent(EventSubscription::Transitions, "TransitionRemoved", eventData); -} - -void EventHandler::HandleTransitionNameChanged(obs_source_t *, std::string oldTransitionName, std::string transitionName) -{ - json eventData; - eventData["oldTransitionName"] = oldTransitionName; - eventData["transitionName"] = transitionName; - BroadcastEvent(EventSubscription::Transitions, "TransitionNameChanged", eventData); + eventData["transitionDuration"] = obs_frontend_get_transition_duration(); + BroadcastEvent(EventSubscription::Transitions, "CurrentSceneTransitionDurationChanged", eventData); } diff --git a/src/eventhandler/EventHandler_InputVolumeMeters.cpp b/src/eventhandler/EventHandler_Ui.cpp similarity index 59% rename from src/eventhandler/EventHandler_InputVolumeMeters.cpp rename to src/eventhandler/EventHandler_Ui.cpp index 64da0b0c..633baf8a 100644 --- a/src/eventhandler/EventHandler_InputVolumeMeters.cpp +++ b/src/eventhandler/EventHandler_Ui.cpp @@ -19,3 +19,22 @@ with this program. If not, see #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); +} diff --git a/src/eventhandler/types/EventSubscription.h b/src/eventhandler/types/EventSubscription.h index 242b8023..0951745f 100644 --- a/src/eventhandler/types/EventSubscription.h +++ b/src/eventhandler/types/EventSubscription.h @@ -130,7 +130,6 @@ namespace EventSubscription { * @initialVersion 5.0.0 * @api enums */ - // Receive events in the `MediaInputs` category MediaInputs = (1 << 8), /** * Subscription value to receive the `VendorEvent` event. @@ -142,9 +141,19 @@ namespace EventSubscription { * @initialVersion 5.0.0 * @api enums */ - // Receive events from external OBS plugins and scripts Vendors = (1 << 9), /** + * Subscription value to receive events in the `Ui` category. + * + * @enumIdentifier Ui + * @enumValue (1 << 10) + * @enumType EventSubscription + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api enums + */ + Ui = (1 << 10), + /** * Helper to receive all non-high-volume events. * * @enumIdentifier All @@ -154,8 +163,7 @@ namespace EventSubscription { * @initialVersion 5.0.0 * @api enums */ - // Receive all event categories (exclude high-volume) - All = (General | Config | Scenes | Inputs | Transitions | Filters | Outputs | SceneItems | MediaInputs | Vendors), + All = (General | Config | Scenes | Inputs | Transitions | Filters | Outputs | SceneItems | MediaInputs | Ui | Vendors), /** * Subscription value to receive the `InputVolumeMeters` high-volume event. * @@ -166,7 +174,6 @@ namespace EventSubscription { * @initialVersion 5.0.0 * @api enums */ - // InputVolumeMeters event (high-volume) InputVolumeMeters = (1 << 16), /** * Subscription value to receive the `InputActiveStateChanged` high-volume event. @@ -178,7 +185,6 @@ namespace EventSubscription { * @initialVersion 5.0.0 * @api enums */ - // InputActiveStateChanged event (high-volume) InputActiveStateChanged = (1 << 17), /** * Subscription value to receive the `InputShowStateChanged` high-volume event. @@ -190,7 +196,6 @@ namespace EventSubscription { * @initialVersion 5.0.0 * @api enums */ - // InputShowStateChanged event (high-volume) InputShowStateChanged = (1 << 18), /** * Subscription value to receive the `SceneItemTransformChanged` high-volume event. @@ -202,7 +207,6 @@ namespace EventSubscription { * @initialVersion 5.0.0 * @api enums */ - // SceneItemTransformChanged event (high-volume) SceneItemTransformChanged = (1 << 19), }; } diff --git a/src/forms/SettingsDialog.ui b/src/forms/SettingsDialog.ui index c790e17f..567b3a53 100644 --- a/src/forms/SettingsDialog.ui +++ b/src/forms/SettingsDialog.ui @@ -151,7 +151,7 @@ 65534 - 4444 + 4455 diff --git a/src/obs-websocket.cpp b/src/obs-websocket.cpp index 4f48e2b6..64396051 100644 --- a/src/obs-websocket.cpp +++ b/src/obs-websocket.cpp @@ -35,12 +35,12 @@ OBS_MODULE_AUTHOR("OBSProject") const char *obs_module_name(void) { return "obs-websocket"; } const char *obs_module_description(void) { return obs_module_text("OBSWebSocket.Plugin.Description"); } +os_cpu_usage_info_t* _cpuUsageInfo; ConfigPtr _config; +EventHandlerPtr _eventHandler; WebSocketApiPtr _webSocketApi; WebSocketServerPtr _webSocketServer; -EventHandlerPtr _eventHandler; SettingsDialog *_settingsDialog = nullptr; -os_cpu_usage_info_t* _cpuUsageInfo; void WebSocketApiEventCallback(std::string vendorName, std::string eventType, obs_data_t *obsEventData); @@ -48,32 +48,37 @@ bool obs_module_load(void) { blog(LOG_INFO, "[obs_module_load] you can haz websockets (Version: %s | RPC Version: %d)", OBS_WEBSOCKET_VERSION, OBS_WEBSOCKET_RPC_VERSION); blog(LOG_INFO, "[obs_module_load] Qt version (compile-time): %s | Qt version (run-time): %s", QT_VERSION_STR, qVersion()); + blog(LOG_INFO, "[obs_module_load] Linked ASIO Version: %d", ASIO_VERSION); - // Create the config object then load the parameters from storage + // Initialize the cpu stats + _cpuUsageInfo = os_cpu_usage_info_start(); + + // Create the config manager then load the parameters from storage _config = ConfigPtr(new Config()); _config->Load(); - // Initialize event handler before server, as the server configures the event handler. + // Initialize the event handler _eventHandler = EventHandlerPtr(new EventHandler()); - _webSocketApi = WebSocketApiPtr(new WebSocketApi(WebSocketApiEventCallback)); + // Initialize the plugin/script API + _webSocketApi = WebSocketApiPtr(new WebSocketApi()); + _webSocketApi->SetEventCallback(WebSocketApiEventCallback); + // Initialize the WebSocket server _webSocketServer = WebSocketServerPtr(new WebSocketServer()); + // Initialize the settings dialog obs_frontend_push_ui_translation(obs_module_get_string); - QMainWindow* mainWindow = reinterpret_cast(obs_frontend_get_main_window()); + QMainWindow* mainWindow = static_cast(obs_frontend_get_main_window()); _settingsDialog = new SettingsDialog(mainWindow); obs_frontend_pop_ui_translation(); + // Add the settings dialog to the tools menu const char* menuActionText = obs_module_text("OBSWebSocket.Settings.DialogTitle"); QAction* menuAction = (QAction*)obs_frontend_add_tools_menu_qaction(menuActionText); QObject::connect(menuAction, &QAction::triggered, [] { _settingsDialog->ToggleShowHide(); }); - _cpuUsageInfo = os_cpu_usage_info_start(); - - // Loading finished blog(LOG_INFO, "[obs_module_load] Module loaded."); - return true; } @@ -81,29 +86,46 @@ void obs_module_unload() { blog(LOG_INFO, "[obs_module_unload] Shutting down..."); + // Shutdown the WebSocket server if it is running if (_webSocketServer->IsListening()) { blog_debug("[obs_module_unload] WebSocket server is running. Stopping..."); _webSocketServer->Stop(); } + + // Destroy the WebSocket server _webSocketServer.reset(); - _eventHandler.reset(); - + // Destroy the plugin/script api _webSocketApi.reset(); + // Destroy the event handler + _eventHandler.reset(); + + // Save and destroy the config manager _config->Save(); _config.reset(); + // Destroy the cpu stats os_cpu_usage_info_destroy(_cpuUsageInfo); blog(LOG_INFO, "[obs_module_unload] Finished shutting down."); } +os_cpu_usage_info_t* GetCpuUsageInfo() +{ + return _cpuUsageInfo; +} + ConfigPtr GetConfig() { return _config; } +EventHandlerPtr GetEventHandler() +{ + return _eventHandler; +} + WebSocketApiPtr GetWebSocketApi() { return _webSocketApi; @@ -114,32 +136,11 @@ WebSocketServerPtr GetWebSocketServer() return _webSocketServer; } -EventHandlerPtr GetEventHandler() -{ - return _eventHandler; -} - -os_cpu_usage_info_t* GetCpuUsageInfo() -{ - return _cpuUsageInfo; -} - bool IsDebugEnabled() { return !_config || _config->DebugEnabled; } -void ___source_dummy_addref(obs_source_t*) {} -void ___weak_source_dummy_addref(obs_weak_source_t*) {} -void ___scene_dummy_addref(obs_scene_t*) {} -void ___sceneitem_dummy_addref(obs_sceneitem_t*) {} -void ___data_dummy_addref(obs_data_t*) {} -void ___data_array_dummy_addref(obs_data_array_t*) {} -void ___output_dummy_addref(obs_output_t*) {} -void ___data_item_dummy_addref(obs_data_item_t*) {} -void ___data_item_release(obs_data_item_t* dataItem){ obs_data_item_release(&dataItem); } -void ___properties_dummy_addref(obs_properties_t*) {} - /** * An event has been emitted from a vendor. * diff --git a/src/obs-websocket.h b/src/obs-websocket.h index 46943c5c..bec761be 100644 --- a/src/obs-websocket.h +++ b/src/obs-websocket.h @@ -21,58 +21,31 @@ with this program. If not, see #include #include -#ifdef _MSC_VER - #pragma push_macro("strtoll") -#endif #include -#ifdef _MSC_VER - #pragma pop_macro("strtoll") -#endif +#include "utils/Obs.h" #include "plugin-macros.generated.h" -// Autorelease object definitions -void ___source_dummy_addref(obs_source_t*); -void ___weak_source_dummy_addref(obs_weak_source_t*); -void ___scene_dummy_addref(obs_scene_t*); -void ___sceneitem_dummy_addref(obs_sceneitem_t*); -void ___data_dummy_addref(obs_data_t*); -void ___data_array_dummy_addref(obs_data_array_t*); -void ___output_dummy_addref(obs_output_t*); -void ___data_item_dummy_addref(obs_data_item_t*); -void ___data_item_release(obs_data_item_t*); -void ___properties_dummy_addref(obs_properties_t*); - -using OBSSourceAutoRelease = OBSRef; -using OBSWeakSourceAutoRelease = OBSRef; -using OBSSceneAutoRelease = OBSRef; -using OBSSceneItemAutoRelease = OBSRef; -using OBSDataAutoRelease = OBSRef; -using OBSDataArrayAutoRelease = OBSRef; -using OBSOutputAutoRelease = OBSRef; -using OBSDataItemAutoRelease = OBSRef; -using OBSPropertiesAutoDestroy = OBSRef; - class Config; typedef std::shared_ptr ConfigPtr; +class EventHandler; +typedef std::shared_ptr EventHandlerPtr; + class WebSocketApi; typedef std::shared_ptr WebSocketApiPtr; class WebSocketServer; typedef std::shared_ptr WebSocketServerPtr; -class EventHandler; -typedef std::shared_ptr EventHandlerPtr; +os_cpu_usage_info_t* GetCpuUsageInfo(); ConfigPtr GetConfig(); +EventHandlerPtr GetEventHandler(); + WebSocketApiPtr GetWebSocketApi(); WebSocketServerPtr GetWebSocketServer(); -EventHandlerPtr GetEventHandler(); - -os_cpu_usage_info_t* GetCpuUsageInfo(); - bool IsDebugEnabled(); diff --git a/src/requesthandler/RequestBatchHandler.cpp b/src/requesthandler/RequestBatchHandler.cpp index 82f8453a..4d3f3383 100644 --- a/src/requesthandler/RequestBatchHandler.cpp +++ b/src/requesthandler/RequestBatchHandler.cpp @@ -109,7 +109,7 @@ static void ObsTickCallback(void *param, float) { ScopeProfiler prof{"obs_websocket_request_batch_frame_tick"}; - auto serialFrameBatch = reinterpret_cast(param); + auto serialFrameBatch = static_cast(param); // Increment frame count serialFrameBatch->frameCount++; diff --git a/src/requesthandler/RequestHandler.cpp b/src/requesthandler/RequestHandler.cpp index ee747307..9df48aa4 100644 --- a/src/requesthandler/RequestHandler.cpp +++ b/src/requesthandler/RequestHandler.cpp @@ -17,9 +17,13 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ +#ifdef PLUGIN_TESTS +#include +#endif + #include "RequestHandler.h" -const std::map RequestHandler::_handlerMap +const std::unordered_map RequestHandler::_handlerMap { // General {"GetVersion", &RequestHandler::GetVersion}, @@ -29,8 +33,6 @@ const std::map RequestHandler::_handlerMap {"GetHotkeyList", &RequestHandler::GetHotkeyList}, {"TriggerHotkeyByName", &RequestHandler::TriggerHotkeyByName}, {"TriggerHotkeyByKeySequence", &RequestHandler::TriggerHotkeyByKeySequence}, - {"GetStudioModeEnabled", &RequestHandler::GetStudioModeEnabled}, - {"SetStudioModeEnabled", &RequestHandler::SetStudioModeEnabled}, {"Sleep", &RequestHandler::Sleep}, // Config @@ -49,14 +51,18 @@ const std::map RequestHandler::_handlerMap {"SetVideoSettings", &RequestHandler::SetVideoSettings}, {"GetStreamServiceSettings", &RequestHandler::GetStreamServiceSettings}, {"SetStreamServiceSettings", &RequestHandler::SetStreamServiceSettings}, + {"GetRecordDirectory", &RequestHandler::GetRecordDirectory}, // Sources {"GetSourceActive", &RequestHandler::GetSourceActive}, {"GetSourceScreenshot", &RequestHandler::GetSourceScreenshot}, {"SaveSourceScreenshot", &RequestHandler::SaveSourceScreenshot}, + {"GetSourcePrivateSettings", &RequestHandler::GetSourcePrivateSettings}, + {"SetSourcePrivateSettings", &RequestHandler::SetSourcePrivateSettings}, // Scenes {"GetSceneList", &RequestHandler::GetSceneList}, + {"GetGroupList", &RequestHandler::GetGroupList}, {"GetCurrentProgramScene", &RequestHandler::GetCurrentProgramScene}, {"SetCurrentProgramScene", &RequestHandler::SetCurrentProgramScene}, {"GetCurrentPreviewScene", &RequestHandler::GetCurrentPreviewScene}, @@ -64,12 +70,15 @@ const std::map RequestHandler::_handlerMap {"CreateScene", &RequestHandler::CreateScene}, {"RemoveScene", &RequestHandler::RemoveScene}, {"SetSceneName", &RequestHandler::SetSceneName}, + {"GetSceneSceneTransitionOverride", &RequestHandler::GetSceneSceneTransitionOverride}, + {"SetSceneSceneTransitionOverride", &RequestHandler::SetSceneSceneTransitionOverride}, // Inputs {"GetInputList", &RequestHandler::GetInputList}, {"GetInputKindList", &RequestHandler::GetInputKindList}, + {"GetSpecialInputs", &RequestHandler::GetSpecialInputs}, {"CreateInput", &RequestHandler::CreateInput}, - //{"RemoveInput", &RequestHandler::RemoveInput}, // Disabled for now. Pending obs-studio#5276 + {"RemoveInput", &RequestHandler::RemoveInput}, {"SetInputName", &RequestHandler::SetInputName}, {"GetInputDefaultSettings", &RequestHandler::GetInputDefaultSettings}, {"GetInputSettings", &RequestHandler::GetInputSettings}, @@ -79,13 +88,39 @@ const std::map RequestHandler::_handlerMap {"ToggleInputMute", &RequestHandler::ToggleInputMute}, {"GetInputVolume", &RequestHandler::GetInputVolume}, {"SetInputVolume", &RequestHandler::SetInputVolume}, + {"GetInputAudioBalance", &RequestHandler::GetInputAudioBalance}, + {"SetInputAudioBalance", &RequestHandler::SetInputAudioBalance}, {"GetInputAudioSyncOffset", &RequestHandler::GetInputAudioSyncOffset}, {"SetInputAudioSyncOffset", &RequestHandler::SetInputAudioSyncOffset}, {"GetInputAudioMonitorType", &RequestHandler::GetInputAudioMonitorType}, {"SetInputAudioMonitorType", &RequestHandler::SetInputAudioMonitorType}, + {"GetInputAudioTracks", &RequestHandler::GetInputAudioTracks}, + {"SetInputAudioTracks", &RequestHandler::SetInputAudioTracks}, {"GetInputPropertiesListPropertyItems", &RequestHandler::GetInputPropertiesListPropertyItems}, {"PressInputPropertiesButton", &RequestHandler::PressInputPropertiesButton}, + // Transitions + {"GetTransitionKindList", &RequestHandler::GetTransitionKindList}, + {"GetSceneTransitionList", &RequestHandler::GetSceneTransitionList}, + {"GetCurrentSceneTransition", &RequestHandler::GetCurrentSceneTransition}, + {"SetCurrentSceneTransition", &RequestHandler::SetCurrentSceneTransition}, + {"SetCurrentSceneTransitionDuration", &RequestHandler::SetCurrentSceneTransitionDuration}, + {"SetCurrentSceneTransitionSettings", &RequestHandler::SetCurrentSceneTransitionSettings}, + {"GetCurrentSceneTransitionCursor", &RequestHandler::GetCurrentSceneTransitionCursor}, + {"TriggerStudioModeTransition", &RequestHandler::TriggerStudioModeTransition}, + {"SetTBarPosition", &RequestHandler::SetTBarPosition}, + + // Filters + {"GetSourceFilterList", &RequestHandler::GetSourceFilterList}, + {"GetSourceFilterDefaultSettings", &RequestHandler::GetSourceFilterDefaultSettings}, + {"CreateSourceFilter", &RequestHandler::CreateSourceFilter}, + {"RemoveSourceFilter", &RequestHandler::RemoveSourceFilter}, + {"SetSourceFilterName", &RequestHandler::SetSourceFilterName}, + {"GetSourceFilter", &RequestHandler::GetSourceFilter}, + {"SetSourceFilterIndex", &RequestHandler::SetSourceFilterIndex}, + {"SetSourceFilterSettings", &RequestHandler::SetSourceFilterSettings}, + {"SetSourceFilterEnabled", &RequestHandler::SetSourceFilterEnabled}, + // Scene Items {"GetSceneItemList", &RequestHandler::GetSceneItemList}, {"GetGroupSceneItemList", &RequestHandler::GetGroupSceneItemList}, @@ -101,12 +136,27 @@ const std::map RequestHandler::_handlerMap {"SetSceneItemLocked", &RequestHandler::SetSceneItemLocked}, {"GetSceneItemIndex", &RequestHandler::GetSceneItemIndex}, {"SetSceneItemIndex", &RequestHandler::SetSceneItemIndex}, + {"GetSceneItemBlendMode", &RequestHandler::GetSceneItemBlendMode}, + {"SetSceneItemBlendMode", &RequestHandler::SetSceneItemBlendMode}, + + // Outputs + {"GetVirtualCamStatus", &RequestHandler::GetVirtualCamStatus}, + {"ToggleVirtualCam", &RequestHandler::ToggleVirtualCam}, + {"StartVirtualCam", &RequestHandler::StartVirtualCam}, + {"StopVirtualCam", &RequestHandler::StopVirtualCam}, + {"GetReplayBufferStatus", &RequestHandler::GetReplayBufferStatus}, + {"ToggleReplayBuffer", &RequestHandler::ToggleReplayBuffer}, + {"StartReplayBuffer", &RequestHandler::StartReplayBuffer}, + {"StopReplayBuffer", &RequestHandler::StopReplayBuffer}, + {"SaveReplayBuffer", &RequestHandler::SaveReplayBuffer}, + {"GetLastReplayBufferReplay", &RequestHandler::GetLastReplayBufferReplay}, // Stream {"GetStreamStatus", &RequestHandler::GetStreamStatus}, {"ToggleStream", &RequestHandler::ToggleStream}, {"StartStream", &RequestHandler::StartStream}, {"StopStream", &RequestHandler::StopStream}, + {"SendStreamCaption", &RequestHandler::SendStreamCaption}, // Record {"GetRecordStatus", &RequestHandler::GetRecordStatus}, @@ -116,13 +166,19 @@ const std::map RequestHandler::_handlerMap {"ToggleRecordPause", &RequestHandler::ToggleRecordPause}, {"PauseRecord", &RequestHandler::PauseRecord}, {"ResumeRecord", &RequestHandler::ResumeRecord}, - //{"GetRecordDirectory", &RequestHandler::GetRecordDirectory}, // Media Inputs {"GetMediaInputStatus", &RequestHandler::GetMediaInputStatus}, {"SetMediaInputCursor", &RequestHandler::SetMediaInputCursor}, {"OffsetMediaInputCursor", &RequestHandler::OffsetMediaInputCursor}, {"TriggerMediaInputAction", &RequestHandler::TriggerMediaInputAction}, + + // Ui + {"GetStudioModeEnabled", &RequestHandler::GetStudioModeEnabled}, + {"SetStudioModeEnabled", &RequestHandler::SetStudioModeEnabled}, + {"OpenInputPropertiesDialog", &RequestHandler::OpenInputPropertiesDialog}, + {"OpenInputFiltersDialog", &RequestHandler::OpenInputFiltersDialog}, + {"OpenInputInteractDialog", &RequestHandler::OpenInputInteractDialog}, }; RequestHandler::RequestHandler(SessionPtr session) : @@ -132,6 +188,10 @@ RequestHandler::RequestHandler(SessionPtr session) : RequestResult RequestHandler::ProcessRequest(const Request& request) { +#ifdef PLUGIN_TESTS + ScopeProfiler prof{"obs_websocket_request_processing"}; +#endif + if (!request.RequestData.is_object() && !request.RequestData.is_null()) return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "Your request data is not an object."); diff --git a/src/requesthandler/RequestHandler.h b/src/requesthandler/RequestHandler.h index 4594a442..e198a304 100644 --- a/src/requesthandler/RequestHandler.h +++ b/src/requesthandler/RequestHandler.h @@ -19,7 +19,7 @@ with this program. If not, see #pragma once -#include +#include #include #include @@ -51,8 +51,6 @@ class RequestHandler { RequestResult GetHotkeyList(const Request&); RequestResult TriggerHotkeyByName(const Request&); RequestResult TriggerHotkeyByKeySequence(const Request&); - RequestResult GetStudioModeEnabled(const Request&); - RequestResult SetStudioModeEnabled(const Request&); RequestResult Sleep(const Request&); // Config @@ -71,14 +69,18 @@ class RequestHandler { RequestResult SetVideoSettings(const Request&); RequestResult GetStreamServiceSettings(const Request&); RequestResult SetStreamServiceSettings(const Request&); + RequestResult GetRecordDirectory(const Request&); // Sources RequestResult GetSourceActive(const Request&); RequestResult GetSourceScreenshot(const Request&); RequestResult SaveSourceScreenshot(const Request&); + RequestResult GetSourcePrivateSettings(const Request&); + RequestResult SetSourcePrivateSettings(const Request&); // Scenes RequestResult GetSceneList(const Request&); + RequestResult GetGroupList(const Request&); RequestResult GetCurrentProgramScene(const Request&); RequestResult SetCurrentProgramScene(const Request&); RequestResult GetCurrentPreviewScene(const Request&); @@ -86,10 +88,13 @@ class RequestHandler { RequestResult CreateScene(const Request&); RequestResult RemoveScene(const Request&); RequestResult SetSceneName(const Request&); + RequestResult GetSceneSceneTransitionOverride(const Request&); + RequestResult SetSceneSceneTransitionOverride(const Request&); // Inputs RequestResult GetInputList(const Request&); RequestResult GetInputKindList(const Request&); + RequestResult GetSpecialInputs(const Request&); RequestResult CreateInput(const Request&); RequestResult RemoveInput(const Request&); RequestResult SetInputName(const Request&); @@ -101,13 +106,39 @@ class RequestHandler { RequestResult ToggleInputMute(const Request&); RequestResult GetInputVolume(const Request&); RequestResult SetInputVolume(const Request&); + RequestResult GetInputAudioBalance(const Request&); + RequestResult SetInputAudioBalance(const Request&); RequestResult GetInputAudioSyncOffset(const Request&); RequestResult SetInputAudioSyncOffset(const Request&); RequestResult GetInputAudioMonitorType(const Request&); RequestResult SetInputAudioMonitorType(const Request&); + RequestResult GetInputAudioTracks(const Request&); + RequestResult SetInputAudioTracks(const Request&); RequestResult GetInputPropertiesListPropertyItems(const Request&); RequestResult PressInputPropertiesButton(const Request&); + // Transitions + RequestResult GetTransitionKindList(const Request&); + RequestResult GetSceneTransitionList(const Request&); + RequestResult GetCurrentSceneTransition(const Request&); + RequestResult SetCurrentSceneTransition(const Request&); + RequestResult SetCurrentSceneTransitionDuration(const Request&); + RequestResult SetCurrentSceneTransitionSettings(const Request&); + RequestResult GetCurrentSceneTransitionCursor(const Request&); + RequestResult TriggerStudioModeTransition(const Request&); + RequestResult SetTBarPosition(const Request&); + + // Filters + RequestResult GetSourceFilterList(const Request&); + RequestResult GetSourceFilterDefaultSettings(const Request&); + RequestResult CreateSourceFilter(const Request&); + RequestResult RemoveSourceFilter(const Request&); + RequestResult SetSourceFilterName(const Request&); + RequestResult GetSourceFilter(const Request&); + RequestResult SetSourceFilterIndex(const Request&); + RequestResult SetSourceFilterSettings(const Request&); + RequestResult SetSourceFilterEnabled(const Request&); + // Scene Items RequestResult GetSceneItemList(const Request&); RequestResult GetGroupSceneItemList(const Request&); @@ -123,12 +154,27 @@ class RequestHandler { RequestResult SetSceneItemLocked(const Request&); RequestResult GetSceneItemIndex(const Request&); RequestResult SetSceneItemIndex(const Request&); + RequestResult GetSceneItemBlendMode(const Request&); + RequestResult SetSceneItemBlendMode(const Request&); + + // Outputs + RequestResult GetVirtualCamStatus(const Request&); + RequestResult ToggleVirtualCam(const Request&); + RequestResult StartVirtualCam(const Request&); + RequestResult StopVirtualCam(const Request&); + RequestResult GetReplayBufferStatus(const Request&); + RequestResult ToggleReplayBuffer(const Request&); + RequestResult StartReplayBuffer(const Request&); + RequestResult StopReplayBuffer(const Request&); + RequestResult SaveReplayBuffer(const Request&); + RequestResult GetLastReplayBufferReplay(const Request&); // Stream RequestResult GetStreamStatus(const Request&); RequestResult ToggleStream(const Request&); RequestResult StartStream(const Request&); RequestResult StopStream(const Request&); + RequestResult SendStreamCaption(const Request&); // Record RequestResult GetRecordStatus(const Request&); @@ -138,7 +184,6 @@ class RequestHandler { RequestResult ToggleRecordPause(const Request&); RequestResult PauseRecord(const Request&); RequestResult ResumeRecord(const Request&); - RequestResult GetRecordDirectory(const Request&); // Media Inputs RequestResult GetMediaInputStatus(const Request&); @@ -146,6 +191,13 @@ class RequestHandler { RequestResult OffsetMediaInputCursor(const Request&); RequestResult TriggerMediaInputAction(const Request&); + // Ui + RequestResult GetStudioModeEnabled(const Request&); + RequestResult SetStudioModeEnabled(const Request&); + RequestResult OpenInputPropertiesDialog(const Request&); + RequestResult OpenInputFiltersDialog(const Request&); + RequestResult OpenInputInteractDialog(const Request&); + SessionPtr _session; - static const std::map _handlerMap; + static const std::unordered_map _handlerMap; }; diff --git a/src/requesthandler/RequestHandler_Config.cpp b/src/requesthandler/RequestHandler_Config.cpp index 0cf7fe87..ab3612ec 100644 --- a/src/requesthandler/RequestHandler_Config.cpp +++ b/src/requesthandler/RequestHandler_Config.cpp @@ -124,7 +124,7 @@ RequestResult RequestHandler::GetSceneCollectionList(const Request&) { json responseData; responseData["currentSceneCollectionName"] = Utils::Obs::StringHelper::GetCurrentSceneCollection(); - responseData["sceneCollections"] = Utils::Obs::ListHelper::GetSceneCollectionList(); + responseData["sceneCollections"] = Utils::Obs::ArrayHelper::GetSceneCollectionList(); return RequestResult::Success(responseData); } @@ -151,7 +151,7 @@ RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request) std::string sceneCollectionName = request.RequestData["sceneCollectionName"]; - auto sceneCollections = Utils::Obs::ListHelper::GetSceneCollectionList(); + auto sceneCollections = Utils::Obs::ArrayHelper::GetSceneCollectionList(); if (std::find(sceneCollections.begin(), sceneCollections.end(), sceneCollectionName) == sceneCollections.end()) return RequestResult::Error(RequestStatus::ResourceNotFound); @@ -159,7 +159,7 @@ RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request) // Avoid queueing tasks if nothing will change if (currentSceneCollectionName != sceneCollectionName) { obs_queue_task(OBS_TASK_UI, [](void* param) { - obs_frontend_set_current_scene_collection(reinterpret_cast(param)); + obs_frontend_set_current_scene_collection(static_cast(param)); }, (void*)sceneCollectionName.c_str(), true); } @@ -189,11 +189,11 @@ RequestResult RequestHandler::CreateSceneCollection(const Request& request) std::string sceneCollectionName = request.RequestData["sceneCollectionName"]; - auto sceneCollections = Utils::Obs::ListHelper::GetSceneCollectionList(); + auto sceneCollections = Utils::Obs::ArrayHelper::GetSceneCollectionList(); if (std::find(sceneCollections.begin(), sceneCollections.end(), sceneCollectionName) != sceneCollections.end()) return RequestResult::Error(RequestStatus::ResourceAlreadyExists); - QMainWindow* mainWindow = reinterpret_cast(obs_frontend_get_main_window()); + QMainWindow* mainWindow = static_cast(obs_frontend_get_main_window()); bool success = false; QMetaObject::invokeMethod(mainWindow, "AddSceneCollection", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, success), Q_ARG(bool, true), Q_ARG(QString, QString::fromStdString(sceneCollectionName))); if (!success) @@ -219,7 +219,7 @@ RequestResult RequestHandler::GetProfileList(const Request&) { json responseData; responseData["currentProfileName"] = Utils::Obs::StringHelper::GetCurrentProfile(); - responseData["profiles"] = Utils::Obs::ListHelper::GetProfileList(); + responseData["profiles"] = Utils::Obs::ArrayHelper::GetProfileList(); return RequestResult::Success(responseData); } @@ -244,7 +244,7 @@ RequestResult RequestHandler::SetCurrentProfile(const Request& request) std::string profileName = request.RequestData["profileName"]; - auto profiles = Utils::Obs::ListHelper::GetProfileList(); + auto profiles = Utils::Obs::ArrayHelper::GetProfileList(); if (std::find(profiles.begin(), profiles.end(), profileName) == profiles.end()) return RequestResult::Error(RequestStatus::ResourceNotFound); @@ -252,7 +252,7 @@ RequestResult RequestHandler::SetCurrentProfile(const Request& request) // Avoid queueing tasks if nothing will change if (currentProfileName != profileName) { obs_queue_task(OBS_TASK_UI, [](void* param) { - obs_frontend_set_current_profile(reinterpret_cast(param)); + obs_frontend_set_current_profile(static_cast(param)); }, (void*)profileName.c_str(), true); } @@ -280,11 +280,11 @@ RequestResult RequestHandler::CreateProfile(const Request& request) std::string profileName = request.RequestData["profileName"]; - auto profiles = Utils::Obs::ListHelper::GetProfileList(); + auto profiles = Utils::Obs::ArrayHelper::GetProfileList(); if (std::find(profiles.begin(), profiles.end(), profileName) != profiles.end()) return RequestResult::Error(RequestStatus::ResourceAlreadyExists); - QMainWindow* mainWindow = reinterpret_cast(obs_frontend_get_main_window()); + QMainWindow* mainWindow = static_cast(obs_frontend_get_main_window()); QMetaObject::invokeMethod(mainWindow, "NewProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName))); return RequestResult::Success(); @@ -311,14 +311,14 @@ RequestResult RequestHandler::RemoveProfile(const Request& request) std::string profileName = request.RequestData["profileName"]; - auto profiles = Utils::Obs::ListHelper::GetProfileList(); + auto profiles = Utils::Obs::ArrayHelper::GetProfileList(); if (std::find(profiles.begin(), profiles.end(), profileName) == profiles.end()) return RequestResult::Error(RequestStatus::ResourceNotFound); if (profiles.size() < 2) return RequestResult::Error(RequestStatus::NotEnoughResources); - QMainWindow* mainWindow = reinterpret_cast(obs_frontend_get_main_window()); + QMainWindow* mainWindow = static_cast(obs_frontend_get_main_window()); QMetaObject::invokeMethod(mainWindow, "DeleteProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName))); return RequestResult::Success(); @@ -334,7 +334,7 @@ RequestResult RequestHandler::RemoveProfile(const Request& request) * @responseField defaultParameterValue | String | Default value associated with the parameter. `null` if no default * * @requestType GetProfileParameter - * @complexity 3 + * @complexity 4 * @rpcVersion -1 * @initialVersion 5.0.0 * @category config @@ -378,7 +378,7 @@ RequestResult RequestHandler::GetProfileParameter(const Request& request) * @requestField parameterValue | String | Value of the parameter to set. Use `null` to delete * * @requestType SetProfileParameter - * @complexity 3 + * @complexity 4 * @rpcVersion -1 * @initialVersion 5.0.0 * @category config @@ -408,6 +408,8 @@ RequestResult RequestHandler::SetProfileParameter(const Request& request) return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "The field `parameterValue` must be a string."); } + config_save(profile); + return RequestResult::Success(); } @@ -592,3 +594,23 @@ RequestResult RequestHandler::SetStreamServiceSettings(const Request& request) return RequestResult::Success(); } + +/** + * Gets the current directory that the record output is set to. + * + * @responseField recordDirectory | String | Output directory + * + * @requestType GetRecordDirectory + * @complexity 1 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category rconfig + */ +RequestResult RequestHandler::GetRecordDirectory(const Request&) +{ + json responseData; + responseData["recordDirectory"] = Utils::Obs::StringHelper::GetCurrentRecordOutputPath(); + + return RequestResult::Success(responseData); +} diff --git a/src/requesthandler/RequestHandler_Filters.cpp b/src/requesthandler/RequestHandler_Filters.cpp new file mode 100644 index 00000000..2e6229eb --- /dev/null +++ b/src/requesthandler/RequestHandler_Filters.cpp @@ -0,0 +1,334 @@ +/* +obs-websocket +Copyright (C) 2016-2021 Stephane Lepin +Copyright (C) 2020-2021 Kyle Manning + +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 +*/ + +#include "RequestHandler.h" + +/** + * Gets an array of all of a source's filters. + * + * @requestField sourceName | String | Name of the source + * + * @responseField filters | Array | 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(); +} diff --git a/src/requesthandler/RequestHandler_General.cpp b/src/requesthandler/RequestHandler_General.cpp index 9e4238ef..97dd1483 100644 --- a/src/requesthandler/RequestHandler_General.cpp +++ b/src/requesthandler/RequestHandler_General.cpp @@ -84,7 +84,7 @@ RequestResult RequestHandler::GetVersion(const Request&) */ RequestResult RequestHandler::GetStats(const Request&) { - json responseData = Utils::Obs::DataHelper::GetStats(); + json responseData = Utils::Obs::ObjectHelper::GetStats(); responseData["webSocketSessionIncomingMessages"] = _session->IncomingMessages(); responseData["webSocketSessionOutgoingMessages"] = _session->OutgoingMessages(); @@ -195,7 +195,7 @@ RequestResult RequestHandler::CallVendorRequest(const Request& request) RequestResult RequestHandler::GetHotkeyList(const Request&) { json responseData; - responseData["hotkeys"] = Utils::Obs::ListHelper::GetHotkeyNameList(); + responseData["hotkeys"] = Utils::Obs::ArrayHelper::GetHotkeyNameList(); return RequestResult::Success(responseData); } @@ -288,58 +288,6 @@ RequestResult RequestHandler::TriggerHotkeyByKeySequence(const Request& request) return RequestResult::Success(); } -/** - * Gets whether studio is enabled. - * - * @responseField studioModeEnabled | Boolean | Whether studio mode is enabled - * - * @requestType GetStudioModeEnabled - * @complexity 1 - * @rpcVersion -1 - * @initialVersion 5.0.0 - * @category general - * @api requests - */ -RequestResult RequestHandler::GetStudioModeEnabled(const Request&) -{ - json responseData; - responseData["studioModeEnabled"] = obs_frontend_preview_program_mode_active(); - return RequestResult::Success(responseData); -} - -/** - * Enables or disables studio mode - * - * @requestField studioModeEnabled | Boolean | True == Enabled, False == Disabled - * - * @requestType SetStudioModeEnabled - * @complexity 1 - * @rpcVersion -1 - * @initialVersion 5.0.0 - * @category general - * @api requests - */ -RequestResult RequestHandler::SetStudioModeEnabled(const Request& request) -{ - RequestStatus::RequestStatus statusCode; - std::string comment; - if (!request.ValidateBoolean("studioModeEnabled", statusCode, comment)) - return RequestResult::Error(statusCode, comment); - - // Avoid queueing tasks if nothing will change - if (obs_frontend_preview_program_mode_active() != request.RequestData["studioModeEnabled"]) { - // (Bad) Create a boolean then pass it as a reference to the task. Requires `wait` in obs_queue_task() to be true, else undefined behavior - bool studioModeEnabled = request.RequestData["studioModeEnabled"]; - // Queue the task inside of the UI thread to prevent race conditions - obs_queue_task(OBS_TASK_UI, [](void* param) { - auto studioModeEnabled = (bool*)param; - obs_frontend_set_preview_program_mode(*studioModeEnabled); - }, &studioModeEnabled, true); - } - - return RequestResult::Success(); -} - /** * Sleeps for a time duration or number of frames. Only available in request batches with types `SERIAL_REALTIME` or `SERIAL_FRAME`. * diff --git a/src/requesthandler/RequestHandler_Inputs.cpp b/src/requesthandler/RequestHandler_Inputs.cpp index f36340c6..fe1611c6 100644 --- a/src/requesthandler/RequestHandler_Inputs.cpp +++ b/src/requesthandler/RequestHandler_Inputs.cpp @@ -47,7 +47,7 @@ RequestResult RequestHandler::GetInputList(const Request& request) } json responseData; - responseData["inputs"] = Utils::Obs::ListHelper::GetInputList(inputKind); + responseData["inputs"] = Utils::Obs::ArrayHelper::GetInputList(inputKind); return RequestResult::Success(responseData); } @@ -79,7 +79,44 @@ RequestResult RequestHandler::GetInputKindList(const Request& request) } json responseData; - responseData["inputKinds"] = Utils::Obs::ListHelper::GetInputKindList(unversioned); + responseData["inputKinds"] = Utils::Obs::ArrayHelper::GetInputKindList(unversioned); + return RequestResult::Success(responseData); +} + +/** + * Gets the names of all special inputs. + * + * @responseField desktop1 | String | Name of the Desktop Audio input + * @responseField desktop2 | String | Name of the Desktop Audio 2 input + * @responseField mic1 | String | Name of the Mic/Auxiliary Audio input + * @responseField mic2 | String | Name of the Mic/Auxiliary Audio 2 input + * @responseField mic3 | String | Name of the Mic/Auxiliary Audio 3 input + * @responseField mic4 | String | Name of the Mic/Auxiliary Audio 4 input + * + * @requestType GetSpecialInputs + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category inputs + */ +RequestResult RequestHandler::GetSpecialInputs(const Request&) +{ + json responseData; + + std::vector channels = {"desktop1", "desktop2", "mic1", "mic2", "mic3", "mic4"}; + + size_t channelId = 1; + for (auto &channel : channels) { + OBSSourceAutoRelease input = obs_get_output_source(channelId); + if (!input) + responseData[channel] = nullptr; + else + responseData[channel] = obs_source_get_name(input); + + channelId++; + } + return RequestResult::Success(responseData); } @@ -115,7 +152,7 @@ RequestResult RequestHandler::CreateInput(const Request& request) return RequestResult::Error(RequestStatus::ResourceAlreadyExists, "A source already exists by that input name."); std::string inputKind = request.RequestData["inputKind"]; - auto kinds = Utils::Obs::ListHelper::GetInputKindList(); + auto kinds = Utils::Obs::ArrayHelper::GetInputKindList(); if (std::find(kinds.begin(), kinds.end(), inputKind) == kinds.end()) return RequestResult::Error(RequestStatus::InvalidInputKind, "Your specified input kind is not supported by OBS. Check that your specified kind is properly versioned and that any necessary plugins are loaded."); @@ -232,6 +269,9 @@ RequestResult RequestHandler::GetInputDefaultSettings(const Request& request) return RequestResult::Error(statusCode, comment); std::string inputKind = request.RequestData["inputKind"]; + auto kinds = Utils::Obs::ArrayHelper::GetInputKindList(); + if (std::find(kinds.begin(), kinds.end(), inputKind) == kinds.end()) + return RequestResult::Error(RequestStatus::InvalidInputKind); OBSDataAutoRelease defaultSettings = obs_get_source_defaults(inputKind.c_str()); if (!defaultSettings) @@ -278,8 +318,9 @@ RequestResult RequestHandler::GetInputSettings(const Request& request) /** * Sets the settings of an input. * - * @requestField inputName | String | Name of the input to set the settings of - * @requestField inputSettings | Object | Object of settings to apply + * @requestField inputName | String | Name of the input to set the settings of + * @requestField inputSettings | Object | Object of settings to apply + * @requestField ?overlay | Boolean | True == apply the settings on top of existing ones, False == reset the input to its defaults, then apply settings. | true * * @requestType SetInputSettings * @complexity 3 @@ -345,6 +386,9 @@ RequestResult RequestHandler::GetInputMute(const Request& request) if (!input) return RequestResult::Error(statusCode, comment); + if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) + return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); + json responseData; responseData["inputMuted"] = obs_source_muted(input); return RequestResult::Success(responseData); @@ -371,6 +415,9 @@ RequestResult RequestHandler::SetInputMute(const Request& request) if (!(input && request.ValidateBoolean("inputMuted", statusCode, comment))) return RequestResult::Error(statusCode, comment); + if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) + return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); + obs_source_set_muted(input, request.RequestData["inputMuted"]); return RequestResult::Success(); @@ -398,6 +445,9 @@ RequestResult RequestHandler::ToggleInputMute(const Request& request) if (!input) return RequestResult::Error(statusCode, comment); + if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) + return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); + bool inputMuted = !obs_source_muted(input); obs_source_set_muted(input, inputMuted); @@ -429,6 +479,9 @@ RequestResult RequestHandler::GetInputVolume(const Request& request) if (!input) return RequestResult::Error(statusCode, comment); + if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) + return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); + float inputVolumeMul = obs_source_get_volume(input); float inputVolumeDb = obs_mul_to_db(inputVolumeMul); if (inputVolumeDb == -INFINITY) @@ -445,7 +498,7 @@ RequestResult RequestHandler::GetInputVolume(const Request& request) * * @requestField inputName | String | Name of the input to set the volume of * @requestField ?inputVolumeMul | Number | Volume setting in mul | >= 0, <= 20 | `inputVolumeDb` should be specified - * @requestField ?inputVolumeDb | Number | Volume setting in dB | >= -100, <= -26 | `inputVolumeMul` should be specified + * @requestField ?inputVolumeDb | Number | Volume setting in dB | >= -100, <= 26 | `inputVolumeMul` should be specified * * @requestType SetInputVolume * @complexity 3 @@ -462,6 +515,9 @@ RequestResult RequestHandler::SetInputVolume(const Request& request) if (!input) return RequestResult::Error(statusCode, comment); + if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) + return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); + bool hasMul = request.Contains("inputVolumeMul"); if (hasMul && !request.ValidateOptionalNumber("inputVolumeMul", statusCode, comment, 0, 20)) return RequestResult::Error(statusCode, comment); @@ -487,6 +543,67 @@ RequestResult RequestHandler::SetInputVolume(const Request& request) return RequestResult::Success(); } +/** + * Gets the audio balance of an input. + * + * @requestField inputName | String | Name of the input to get the audio balance of + * + * @responseField inputAudioBalance | Number | Audio balance value from 0.0-1.0 + * + * @requestType GetInputAudioBalance + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category inputs + */ +RequestResult RequestHandler::GetInputAudioBalance(const Request& request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); + if (!input) + return RequestResult::Error(statusCode, comment); + + if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) + return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); + + json responseData; + responseData["inputAudioBalance"] = obs_source_get_balance_value(input); + + return RequestResult::Success(responseData); +} + +/** + * Sets the audio balance of an input. + * + * @requestField inputName | String | Name of the input to set the audio balance of + * @requestField inputAudioBalance | Number | New audio balance value | >= 0.0, <= 1.0 + * + * @requestType SetInputAudioBalance + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category inputs + */ +RequestResult RequestHandler::SetInputAudioBalance(const Request& request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); + if (!(input && request.ValidateNumber("inputAudioBalance", statusCode, comment, 0.0, 1.0))) + return RequestResult::Error(statusCode, comment); + + if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) + return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); + + float inputAudioBalance = request.RequestData["inputAudioBalance"]; + obs_source_set_balance_value(input, inputAudioBalance); + + return RequestResult::Success(); +} + /** * Gets the audio sync offset of an input. * @@ -511,6 +628,9 @@ RequestResult RequestHandler::GetInputAudioSyncOffset(const Request& request) if (!input) return RequestResult::Error(statusCode, comment); + if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) + return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); + json responseData; // Offset is stored in nanoseconds in OBS. responseData["inputAudioSyncOffset"] = obs_source_get_sync_offset(input) / 1000000; @@ -539,6 +659,9 @@ RequestResult RequestHandler::SetInputAudioSyncOffset(const Request& request) if (!(input && request.ValidateNumber("inputAudioSyncOffset", statusCode, comment, -950, 20000))) return RequestResult::Error(statusCode, comment); + if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) + return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); + int64_t syncOffset = request.RequestData["inputAudioSyncOffset"]; obs_source_set_sync_offset(input, syncOffset * 1000000); @@ -573,6 +696,9 @@ RequestResult RequestHandler::GetInputAudioMonitorType(const Request& request) if (!input) return RequestResult::Error(statusCode, comment); + if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) + return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); + json responseData; responseData["monitorType"] = Utils::Obs::StringHelper::GetInputMonitorType(input); @@ -600,6 +726,12 @@ RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request) if (!(input && request.ValidateString("monitorType", statusCode, comment))) return RequestResult::Error(statusCode, comment); + if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) + return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); + + if (!obs_audio_monitoring_available()) + return RequestResult::Error(RequestStatus::InvalidResourceState, "Audio monitoring is not available on this platform."); + enum obs_monitoring_type monitorType; std::string monitorTypeString = request.RequestData["monitorType"]; if (monitorTypeString == "OBS_MONITORING_TYPE_NONE") @@ -616,30 +748,93 @@ RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request) return RequestResult::Success(); } -std::vector 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 ret; + RequestStatus::RequestStatus statusCode; + std::string comment; + OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); + if (!input) + return RequestResult::Error(statusCode, comment); - enum obs_combo_format itemFormat = obs_property_list_format(property); - size_t itemCount = obs_property_list_item_count(property); + if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) + return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); - for (size_t i = 0; i < itemCount; i++) { - json itemData; - itemData["itemName"] = obs_property_list_item_name(property, i); - itemData["itemEnabled"] = !obs_property_list_item_disabled(property, i); - if (itemFormat == OBS_COMBO_FORMAT_INT) { - itemData["itemValue"] = obs_property_list_item_int(property, i); - } else if (itemFormat == OBS_COMBO_FORMAT_FLOAT) { - itemData["itemValue"] = obs_property_list_item_float(property, i); - } else if (itemFormat == OBS_COMBO_FORMAT_STRING) { - itemData["itemValue"] = obs_property_list_item_string(property, i); - } else { - itemData["itemValue"] = nullptr; - } - ret.push_back(itemData); + long long tracks = obs_source_get_audio_mixers(input); + + json inputAudioTracks; + for (long long i = 0; i < MAX_AUDIO_MIXES; i++) { + inputAudioTracks[std::to_string(i + 1)] = (bool)((tracks >> i) & 1); } - return ret; + json responseData; + responseData["inputAudioTracks"] = inputAudioTracks; + + return RequestResult::Success(responseData); +} + +/** + * Sets the enable state of audio tracks of an input. + * + * @requestField inputName | String | Name of the input + * @requestField inputAudioTracks | Object | Track settings to apply + * + * @requestType SetInputAudioTracks + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category inputs + */ +RequestResult RequestHandler::SetInputAudioTracks(const Request& request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment); + if (!input || !request.ValidateObject("inputAudioTracks", statusCode, comment)) + return RequestResult::Error(statusCode, comment); + + if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO)) + return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio."); + + json inputAudioTracks = request.RequestData["inputAudioTracks"]; + + long long mixers = obs_source_get_audio_mixers(input); + + for (long long i = 0; i < MAX_AUDIO_MIXES; i++) { + std::string track = std::to_string(i + 1); + + if (!Utils::Json::Contains(inputAudioTracks, track)) + continue; + + if (!inputAudioTracks[track].is_boolean()) + return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "The value of one of your tracks is not a boolean."); + + bool enabled = inputAudioTracks[track]; + + if (enabled) + mixers |= (1 << i); + else + mixers &= ~(1 << i); + } + + // Decided that checking if tracks have actually changed is unnecessary + obs_source_set_audio_mixers(input, mixers); + + return RequestResult::Success(); } /** @@ -677,7 +872,7 @@ RequestResult RequestHandler::GetInputPropertiesListPropertyItems(const Request& return RequestResult::Error(RequestStatus::InvalidResourceType, "The property found is not a list."); json responseData; - responseData["propertyItems"] = GetListPropertyItems(property); + responseData["propertyItems"] = Utils::Obs::ArrayHelper::GetListPropertyItems(property); return RequestResult::Success(responseData); } diff --git a/src/requesthandler/RequestHandler_MediaInputs.cpp b/src/requesthandler/RequestHandler_MediaInputs.cpp index e651ddf7..8df95162 100644 --- a/src/requesthandler/RequestHandler_MediaInputs.cpp +++ b/src/requesthandler/RequestHandler_MediaInputs.cpp @@ -25,6 +25,32 @@ bool IsMediaTimeValid(obs_source_t *input) return mediaState == OBS_MEDIA_STATE_PLAYING || mediaState == OBS_MEDIA_STATE_PAUSED; } +/** + * Gets the status of a media input. + * + * Media States: + * - `OBS_MEDIA_STATE_NONE` + * - `OBS_MEDIA_STATE_PLAYING` + * - `OBS_MEDIA_STATE_OPENING` + * - `OBS_MEDIA_STATE_BUFFERING` + * - `OBS_MEDIA_STATE_PAUSED` + * - `OBS_MEDIA_STATE_STOPPED` + * - `OBS_MEDIA_STATE_ENDED` + * - `OBS_MEDIA_STATE_ERROR` + * + * @requestField inputName | String | Name of the media input + * + * @responseField mediaState | String | State of the media input + * @responseField mediaDuration | Number | Total duration of the playing media in milliseconds. `null` if not playing + * @responseField mediaCursor | Number | Position of the cursor in milliseconds. `null` if not playing + * + * @requestType GetMediaInputStatus + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category media inputs + */ RequestResult RequestHandler::GetMediaInputStatus(const Request& request) { RequestStatus::RequestStatus statusCode; @@ -47,6 +73,21 @@ RequestResult RequestHandler::GetMediaInputStatus(const Request& request) return RequestResult::Success(responseData); } +/** + * Sets the cursor position of a media input. + * + * This request does not perform bounds checking of the cursor position. + * + * @requestField inputName | String | Name of the media input + * @requestField mediaCursor | Number | New cursor position to set | >= 0 + * + * @requestType SetMediaInputCursor + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category media inputs + */ RequestResult RequestHandler::SetMediaInputCursor(const Request& request) { RequestStatus::RequestStatus statusCode; @@ -66,6 +107,21 @@ RequestResult RequestHandler::SetMediaInputCursor(const Request& request) return RequestResult::Success(); } +/** + * Offsets the current cursor position of a media input by the specified value. + * + * This request does not perform bounds checking of the cursor position. + * + * @requestField inputName | String | Name of the media input + * @requestField mediaCursorOffset | Number | Value to offset the current cursor position by | None + * + * @requestType OffsetMediaInputCursor + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category media inputs + */ RequestResult RequestHandler::OffsetMediaInputCursor(const Request& request) { RequestStatus::RequestStatus statusCode; @@ -88,6 +144,19 @@ RequestResult RequestHandler::OffsetMediaInputCursor(const Request& request) return RequestResult::Success(); } +/** + * Triggers an action on a media input. + * + * @requestField inputName | String | Name of the media input + * @requestField mediaAction | String | Identifier of the `ObsMediaInputAction` enum + * + * @requestType TriggerMediaInputAction + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category media inputs + */ RequestResult RequestHandler::TriggerMediaInputAction(const Request& request) { RequestStatus::RequestStatus statusCode; diff --git a/src/requesthandler/RequestHandler_Outputs.cpp b/src/requesthandler/RequestHandler_Outputs.cpp new file mode 100644 index 00000000..d85f839a --- /dev/null +++ b/src/requesthandler/RequestHandler_Outputs.cpp @@ -0,0 +1,277 @@ +/* +obs-websocket +Copyright (C) 2016-2021 Stephane Lepin +Copyright (C) 2020-2021 Kyle Manning + +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 +*/ + +#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); +} diff --git a/src/requesthandler/RequestHandler_Record.cpp b/src/requesthandler/RequestHandler_Record.cpp index 19a2c14a..9eb248ea 100644 --- a/src/requesthandler/RequestHandler_Record.cpp +++ b/src/requesthandler/RequestHandler_Record.cpp @@ -19,9 +19,25 @@ with this program. If not, see #include "RequestHandler.h" +/** + * Gets the status of the record output. + * + * @responseField outputActive | Boolean | Whether the output is active + * @responseField ouputPaused | Boolean | Whether the output is paused + * @responseField outputTimecode | String | Current formatted timecode string for the output + * @responseField outputDuration | Number | Current duration in milliseconds for the output + * @responseField outputBytes | Number | Number of bytes sent by the output + * + * @requestType GetRecordStatus + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category record + */ RequestResult RequestHandler::GetRecordStatus(const Request&) { - OBSOutputAutoRelease recordOutput = obs_frontend_get_streaming_output(); + OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output(); uint64_t outputDuration = Utils::Obs::NumberHelper::GetOutputDuration(recordOutput); @@ -35,6 +51,16 @@ RequestResult RequestHandler::GetRecordStatus(const Request&) return RequestResult::Success(responseData); } +/** + * Toggles the status of the record output. + * + * @requestType ToggleRecord + * @complexity 1 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category record + */ RequestResult RequestHandler::ToggleRecord(const Request&) { json responseData; @@ -49,6 +75,16 @@ RequestResult RequestHandler::ToggleRecord(const Request&) return RequestResult::Success(responseData); } +/** + * Starts the record output. + * + * @requestType StartRecord + * @complexity 1 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category record + */ RequestResult RequestHandler::StartRecord(const Request&) { if (obs_frontend_recording_active()) @@ -60,6 +96,16 @@ RequestResult RequestHandler::StartRecord(const Request&) return RequestResult::Success(); } +/** + * Stops the record output. + * + * @requestType StopRecord + * @complexity 1 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category record + */ RequestResult RequestHandler::StopRecord(const Request&) { if (!obs_frontend_recording_active()) @@ -71,6 +117,16 @@ RequestResult RequestHandler::StopRecord(const Request&) return RequestResult::Success(); } +/** + * Toggles pause on the record output. + * + * @requestType ToggleRecordPause + * @complexity 1 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category record + */ RequestResult RequestHandler::ToggleRecordPause(const Request&) { json responseData; @@ -85,6 +141,16 @@ RequestResult RequestHandler::ToggleRecordPause(const Request&) return RequestResult::Success(responseData); } +/** + * Pauses the record output. + * + * @requestType PauseRecord + * @complexity 1 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category record + */ RequestResult RequestHandler::PauseRecord(const Request&) { if (obs_frontend_recording_paused()) @@ -96,6 +162,16 @@ RequestResult RequestHandler::PauseRecord(const Request&) return RequestResult::Success(); } +/** + * Resumes the record output. + * + * @requestType ResumeRecord + * @complexity 1 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category record + */ RequestResult RequestHandler::ResumeRecord(const Request&) { if (!obs_frontend_recording_paused()) @@ -106,11 +182,3 @@ RequestResult RequestHandler::ResumeRecord(const Request&) return RequestResult::Success(); } - -RequestResult RequestHandler::GetRecordDirectory(const Request&) -{ - json responseData; - responseData["recordDirectory"] = Utils::Obs::StringHelper::GetCurrentRecordOutputPath(); - - return RequestResult::Success(responseData); -} diff --git a/src/requesthandler/RequestHandler_SceneItems.cpp b/src/requesthandler/RequestHandler_SceneItems.cpp index a1f6fcb4..78e72f59 100644 --- a/src/requesthandler/RequestHandler_SceneItems.cpp +++ b/src/requesthandler/RequestHandler_SceneItems.cpp @@ -19,6 +19,22 @@ with this program. If not, see #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 | Array of scene items in the scene + * + * @requestType GetSceneItemList + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scene items + */ RequestResult RequestHandler::GetSceneItemList(const Request& request) { RequestStatus::RequestStatus statusCode; @@ -28,11 +44,29 @@ RequestResult RequestHandler::GetSceneItemList(const Request& request) return RequestResult::Error(statusCode, comment); json responseData; - responseData["sceneItems"] = Utils::Obs::ListHelper::GetSceneItemList(obs_scene_from_source(scene)); + responseData["sceneItems"] = Utils::Obs::ArrayHelper::GetSceneItemList(obs_scene_from_source(scene)); return RequestResult::Success(responseData); } +/** + * Basically GetSceneItemList, but for groups. + * + * Using groups at all in OBS is discouraged, as they are very broken under the hood. + * + * Groups only + * + * @requestField sceneName | String | Name of the group to get the items of + * + * @responseField sceneItems | Array | Array of scene items in the group + * + * @requestType GetGroupItemList + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scene items + */ RequestResult RequestHandler::GetGroupSceneItemList(const Request& request) { RequestStatus::RequestStatus statusCode; @@ -42,26 +76,36 @@ RequestResult RequestHandler::GetGroupSceneItemList(const Request& request) return RequestResult::Error(statusCode, comment); json responseData; - responseData["sceneItems"] = Utils::Obs::ListHelper::GetSceneItemList(obs_group_from_source(scene)); + responseData["sceneItems"] = Utils::Obs::ArrayHelper::GetSceneItemList(obs_group_from_source(scene)); return RequestResult::Success(responseData); } +/** + * Searches a scene for a source, and returns its id. + * + * Scenes and Groups + * + * @requestField sceneName | String | Name of the scene or group to search in + * @requestField sourceName | String | Name of the source to find + * + * @responseField sceneItemId | Number | Numeric ID of the scene item + * + * @requestType GetSceneItemId + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scene items + */ RequestResult RequestHandler::GetSceneItemId(const Request& request) { RequestStatus::RequestStatus statusCode; std::string comment; - OBSSourceAutoRelease sceneSource = request.ValidateScene("sceneName", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); - if (!(sceneSource && request.ValidateString("sourceName", statusCode, comment))) + OBSSceneAutoRelease scene = request.ValidateScene2("sceneName", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); + if (!(scene && request.ValidateString("sourceName", statusCode, comment))) return RequestResult::Error(statusCode, comment); - OBSScene scene = obs_scene_from_source(sceneSource); - if (!scene) { - scene = obs_group_from_source(sceneSource); - if (!scene) // This should never happen - return RequestResult::Error(RequestStatus::GenericError, "Somehow the scene was found but the scene object could not be fetched. Please report this to the obs-websocket developers."); - } - std::string sourceName = request.RequestData["sourceName"]; OBSSceneItemAutoRelease item = Utils::Obs::SearchHelper::GetSceneItemByName(scene, sourceName); @@ -74,6 +118,24 @@ RequestResult RequestHandler::GetSceneItemId(const Request& request) return RequestResult::Success(responseData); } +/** + * Creates a new scene item using a source. + * + * Scenes only + * + * @requestField sceneName | String | Name of the scene to create the new item in + * @requestField sourceName | String | Name of the source to add to the scene + * @requestField ?sceneItemEnabled | Boolean | Enable state to apply to the scene item on creation | True + * + * @responseField sceneItemId | Number | Numeric ID of the scene item + * + * @requestType CreateSceneItem + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scene items + */ RequestResult RequestHandler::CreateSceneItem(const Request& request) { RequestStatus::RequestStatus statusCode; @@ -108,6 +170,21 @@ RequestResult RequestHandler::CreateSceneItem(const Request& request) return RequestResult::Success(responseData); } +/** + * Removes a scene item from a scene. + * + * Scenes only + * + * @requestField sceneName | String | Name of the scene the item is in + * @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0 + * + * @requestType RemoveSceneItem + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scene items + */ RequestResult RequestHandler::RemoveSceneItem(const Request& request) { RequestStatus::RequestStatus statusCode; @@ -116,11 +193,30 @@ RequestResult RequestHandler::RemoveSceneItem(const Request& request) if (!sceneItem) return RequestResult::Error(statusCode, comment); + // Makes the UI log `User Removed source '[source]' from scene '(null)'`. This is not a problem, just a side effect. obs_sceneitem_remove(sceneItem); return RequestResult::Success(); } +/** + * Duplicates a scene item, copying all transform and crop info. + * + * Scenes only + * + * @requestField sceneName | String | Name of the scene the item is in + * @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0 + * @requestField ?destinationSceneName | String | Name of the scene to create the duplicated item in | `sceneName` is assumed + * + * @responseField sceneItemId | Number | Numeric ID of the duplicated scene item + * + * @requestType DuplicateSceneItem + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scene items + */ RequestResult RequestHandler::DuplicateSceneItem(const Request& request) { RequestStatus::RequestStatus statusCode; @@ -136,8 +232,9 @@ RequestResult RequestHandler::DuplicateSceneItem(const Request& request) if (!destinationScene) return RequestResult::Error(statusCode, comment); } else { - destinationScene = obs_sceneitem_get_scene(sceneItem); - obs_scene_addref(destinationScene); + destinationScene = obs_scene_get_ref(obs_sceneitem_get_scene(sceneItem)); + if (!destinationScene) + return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Internal error: Failed to get ref for scene of scene item."); } if (obs_sceneitem_is_group(sceneItem) && obs_sceneitem_get_scene(sceneItem) == destinationScene) { @@ -165,6 +262,23 @@ RequestResult RequestHandler::DuplicateSceneItem(const Request& request) return RequestResult::Success(responseData); } +/** + * Gets the transform and crop info of a scene item. + * + * Scenes and Groups + * + * @requestField sceneName | String | Name of the scene the item is in + * @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0 + * + * @responseField sceneItemTransform | Object | Object containing scene item transform info + * + * @requestType GetSceneItemTransform + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scene items + */ RequestResult RequestHandler::GetSceneItemTransform(const Request& request) { RequestStatus::RequestStatus statusCode; @@ -174,11 +288,25 @@ RequestResult RequestHandler::GetSceneItemTransform(const Request& request) return RequestResult::Error(statusCode, comment); json responseData; - responseData["sceneItemTransform"] = Utils::Obs::DataHelper::GetSceneItemTransform(sceneItem); + responseData["sceneItemTransform"] = Utils::Obs::ObjectHelper::GetSceneItemTransform(sceneItem); return RequestResult::Success(responseData); } +/** + * Sets the transform and crop info of a scene item. + * + * @requestField sceneName | String | Name of the scene the item is in + * @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0 + * @requestField sceneItemTransform | Object | Object containing scene item transform info to update + * + * @requestType SetSceneItemTransform + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scene items + */ RequestResult RequestHandler::SetSceneItemTransform(const Request& request) { RequestStatus::RequestStatus statusCode; @@ -317,6 +445,23 @@ RequestResult RequestHandler::SetSceneItemTransform(const Request& request) return RequestResult::Success(); } +/** + * Gets the enable state of a scene item. + * + * Scenes and Groups + * + * @requestField sceneName | String | Name of the scene the item is in + * @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0 + * + * @responseField sceneItemEnabled | Boolean | Whether the scene item is enabled. `true` for enabled, `false` for disabled + * + * @requestType GetSceneItemEnabled + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scene items + */ RequestResult RequestHandler::GetSceneItemEnabled(const Request& request) { RequestStatus::RequestStatus statusCode; @@ -331,6 +476,22 @@ RequestResult RequestHandler::GetSceneItemEnabled(const Request& request) return RequestResult::Success(responseData); } +/** + * Sets the enable state of a scene item. + * + * Scenes and Groups + * + * @requestField sceneName | String | Name of the scene the item is in + * @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0 + * @requestField sceneItemEnabled | Boolean | New enable state of the scene item + * + * @requestType SetSceneItemEnabled + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scene items + */ RequestResult RequestHandler::SetSceneItemEnabled(const Request& request) { RequestStatus::RequestStatus statusCode; @@ -346,6 +507,23 @@ RequestResult RequestHandler::SetSceneItemEnabled(const Request& request) return RequestResult::Success(); } +/** + * Gets the lock state of a scene item. + * + * Scenes and Groups + * + * @requestField sceneName | String | Name of the scene the item is in + * @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0 + * + * @responseField sceneItemLocked | Boolean | Whether the scene item is locked. `true` for locked, `false` for unlocked + * + * @requestType GetSceneItemLocked + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scene items + */ RequestResult RequestHandler::GetSceneItemLocked(const Request& request) { RequestStatus::RequestStatus statusCode; @@ -360,6 +538,22 @@ RequestResult RequestHandler::GetSceneItemLocked(const Request& request) return RequestResult::Success(responseData); } +/** + * Sets the lock state of a scene item. + * + * Scenes and Group + * + * @requestField sceneName | String | Name of the scene the item is in + * @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0 + * @requestField sceneItemLocked | Boolean | New lock state of the scene item + * + * @requestType SetSceneItemLocked + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scene items + */ RequestResult RequestHandler::SetSceneItemLocked(const Request& request) { RequestStatus::RequestStatus statusCode; @@ -375,6 +569,25 @@ RequestResult RequestHandler::SetSceneItemLocked(const Request& request) return RequestResult::Success(); } +/** + * Gets the index position of a scene item in a scene. + * + * An index of 0 is at the bottom of the source list in the UI. + * + * Scenes and Groups + * + * @requestField sceneName | String | Name of the scene the item is in + * @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0 + * + * @responseField sceneItemIndex | Number | Index position of the scene item + * + * @requestType GetSceneItemIndex + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scene items + */ RequestResult RequestHandler::GetSceneItemIndex(const Request& request) { RequestStatus::RequestStatus statusCode; @@ -389,6 +602,22 @@ RequestResult RequestHandler::GetSceneItemIndex(const Request& request) return RequestResult::Success(responseData); } +/** + * Sets the index position of a scene item in a scene. + * + * Scenes and Groups + * + * @requestField sceneName | String | Name of the scene the item is in + * @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0 + * @requestField sceneItemIndex | Number | New index position of the scene item | >= 0 + * + * @requestType SetSceneItemIndex + * @complexity 3 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scene items + */ RequestResult RequestHandler::SetSceneItemIndex(const Request& request) { RequestStatus::RequestStatus statusCode; @@ -403,3 +632,81 @@ RequestResult RequestHandler::SetSceneItemIndex(const Request& request) return RequestResult::Success(); } + +/** + * Gets the blend mode of a scene item. + * + * Blend modes: + * + * - `OBS_BLEND_NORMAL` + * - `OBS_BLEND_ADDITIVE` + * - `OBS_BLEND_SUBTRACT` + * - `OBS_BLEND_SCREEN` + * - `OBS_BLEND_MULTIPLY` + * - `OBS_BLEND_LIGHTEN` + * - `OBS_BLEND_DARKEN` + * + * Scenes and Groups + * + * @requestField sceneName | String | Name of the scene the item is in + * @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0 + * + * @responseField sceneItemBlendMode | String | Current blend mode + * + * @requestType GetSceneItemBlendMode + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scene items + */ +RequestResult RequestHandler::GetSceneItemBlendMode(const Request& request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); + if (!sceneItem) + return RequestResult::Error(statusCode, comment); + + auto blendMode = obs_sceneitem_get_blending_mode(sceneItem); + + json responseData; + responseData["sceneItemBlendMode"] = Utils::Obs::StringHelper::GetSceneItemBlendMode(blendMode); + + return RequestResult::Success(responseData); +} + +/** + * Sets the blend mode of a scene item. + * + * Scenes and Groups + * + * @requestField sceneName | String | Name of the scene the item is in + * @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0 + * @requestField sceneItemBlendMode | String | New blend mode + * + * @requestType SetSceneItemBlendMode + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scene items + */ +RequestResult RequestHandler::SetSceneItemBlendMode(const Request& request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP); + if (!(sceneItem && request.ValidateString("sceneItemBlendMode", statusCode, comment))) + return RequestResult::Error(statusCode, comment); + + std::string blendModeString = request.RequestData["sceneItemBlendMode"]; + + auto blendMode = Utils::Obs::EnumHelper::GetSceneItemBlendMode(blendModeString); + if (blendMode == OBS_BLEND_NORMAL && blendModeString != "OBS_BLEND_NORMAL") + return RequestResult::Error(RequestStatus::InvalidRequestField, "The field sceneItemBlendMode has an invalid value."); + + obs_sceneitem_set_blending_mode(sceneItem, blendMode); + + return RequestResult::Success(); +} diff --git a/src/requesthandler/RequestHandler_Scenes.cpp b/src/requesthandler/RequestHandler_Scenes.cpp index b9bf4d33..d4d376fd 100644 --- a/src/requesthandler/RequestHandler_Scenes.cpp +++ b/src/requesthandler/RequestHandler_Scenes.cpp @@ -22,9 +22,9 @@ with this program. If not, see /** * Gets an array of all scenes in OBS. * - * @responseField scenes | Array | Array of scenes in OBS * @responseField currentProgramSceneName | String | Current program scene * @responseField currentPreviewSceneName | String | Current preview scene. `null` if not in studio mode + * @responseField scenes | Array | Array of scenes * * @requestType GetSceneList * @complexity 2 @@ -49,7 +49,30 @@ RequestResult RequestHandler::GetSceneList(const Request&) else responseData["currentPreviewSceneName"] = nullptr; - responseData["scenes"] = Utils::Obs::ListHelper::GetSceneList(); + responseData["scenes"] = Utils::Obs::ArrayHelper::GetSceneList(); + + return RequestResult::Success(responseData); +} + +/** + * Gets an array of all groups in OBS. + * + * Groups in OBS are actually scenes, but renamed and modified. In obs-websocket, we treat them as scenes where we can. + * + * @responseField groups | Array | Array of group names + * + * @requestType GetGroupList + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scenes + */ +RequestResult RequestHandler::GetGroupList(const Request&) +{ + json responseData; + + responseData["groups"] = Utils::Obs::ArrayHelper::GetGroupList(); return RequestResult::Success(responseData); } @@ -250,3 +273,105 @@ RequestResult RequestHandler::SetSceneName(const Request& request) return RequestResult::Success(); } + +/** + * Gets the scene transition overridden for a scene. + * + * @requestField sceneName | String | Name of the scene + * + * @responseField transitionName | String | Name of the overridden scene transition, else `null` + * @responseField transitionDuration | Number | Duration of the overridden scene transition, else `null` + * + * @requestType GetSceneSceneTransitionOverride + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scenes + */ +RequestResult RequestHandler::GetSceneSceneTransitionOverride(const Request& request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + OBSSourceAutoRelease scene = request.ValidateScene("sceneName", statusCode, comment); + if (!scene) + return RequestResult::Error(statusCode, comment); + + OBSDataAutoRelease privateSettings = obs_source_get_private_settings(scene); + + json responseData; + const char *transitionName = obs_data_get_string(privateSettings, "transition"); + if (transitionName && strlen(transitionName)) + responseData["transitionName"] = transitionName; + else + responseData["transitionName"] = nullptr; + + if (obs_data_has_user_value(privateSettings, "transition_duration")) + responseData["transitionDuration"] = obs_data_get_int(privateSettings, "transition_duration"); + else + responseData["transitionDuration"] = nullptr; + + return RequestResult::Success(responseData); +} + +/** + * Gets the scene transition overridden for a scene. + * + * @requestField sceneName | String | Name of the scene + * @requestField ?transitionName | String | Name of the scene transition to use as override. Specify `null` to remove | Unchanged + * @requestField ?transitionDuration | Number | Duration to use for any overridden transition. Specify `null` to remove | >= 50, <= 20000 | Unchanged + * + * @requestType SetSceneSceneTransitionOverride + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category scenes + */ +RequestResult RequestHandler::SetSceneSceneTransitionOverride(const Request& request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + OBSSourceAutoRelease scene = request.ValidateScene("sceneName", statusCode, comment); + if (!scene) + return RequestResult::Error(statusCode, comment); + + OBSDataAutoRelease privateSettings = obs_source_get_private_settings(scene); + + bool hasName = request.RequestData.contains("transitionName"); + if (hasName && !request.RequestData["transitionName"].is_null()) { + if (!request.ValidateOptionalString("transitionName", statusCode, comment)) + return RequestResult::Error(statusCode, comment); + OBSSourceAutoRelease transition = Utils::Obs::SearchHelper::GetSceneTransitionByName(request.RequestData["transitionName"]); + if (!transition) + return RequestResult::Error(RequestStatus::ResourceNotFound, "No scene transition was found by that name."); + } + + bool hasDuration = request.RequestData.contains("transitionDuration"); + if (hasDuration && !request.RequestData["transitionDuration"].is_null()) { + if (!request.ValidateOptionalNumber("transitionDuration", statusCode, comment, 50, 20000)) + return RequestResult::Error(statusCode, comment); + } + + if (!hasName && !hasDuration) + return RequestResult::Error(RequestStatus::MissingRequestField, "Your request data must include either `transitionName` or `transitionDuration`."); + + if (hasName) { + if (request.RequestData["transitionName"].is_null()) { + obs_data_erase(privateSettings, "transition"); + } else { + std::string transitionName = request.RequestData["transitionName"]; + obs_data_set_string(privateSettings, "transition", transitionName.c_str()); + } + } + + if (hasDuration) { + if (request.RequestData["transitionDuration"].is_null()) { + obs_data_erase(privateSettings, "transition_duration"); + } else { + obs_data_set_int(privateSettings, "transition_duration", request.RequestData["transitionDuration"]); + } + } + + return RequestResult::Success(); +} diff --git a/src/requesthandler/RequestHandler_Sources.cpp b/src/requesthandler/RequestHandler_Sources.cpp index 53bee7ef..584a4143 100644 --- a/src/requesthandler/RequestHandler_Sources.cpp +++ b/src/requesthandler/RequestHandler_Sources.cpp @@ -312,3 +312,39 @@ RequestResult RequestHandler::SaveSourceScreenshot(const Request& request) return RequestResult::Success(); } + +// Intentionally undocumented +RequestResult RequestHandler::GetSourcePrivateSettings(const Request& request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment); + if (!source) + return RequestResult::Error(statusCode, comment); + + OBSDataAutoRelease privateSettings = obs_source_get_private_settings(source); + + json responseData; + responseData["sourceSettings"] = Utils::Json::ObsDataToJson(privateSettings); + + return RequestResult::Success(responseData); +} + +// Intentionally undocumented +RequestResult RequestHandler::SetSourcePrivateSettings(const Request& request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment); + if (!source || !request.ValidateObject("sourceSettings", statusCode, comment)) + return RequestResult::Error(statusCode, comment); + + OBSDataAutoRelease privateSettings = obs_source_get_private_settings(source); + + OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["sourceSettings"]); + + // Always overlays to prevent destroying internal source data unintentionally + obs_data_apply(privateSettings, newSettings); + + return RequestResult::Success(); +} diff --git a/src/requesthandler/RequestHandler_Stream.cpp b/src/requesthandler/RequestHandler_Stream.cpp index 8136dcfb..367ebd48 100644 --- a/src/requesthandler/RequestHandler_Stream.cpp +++ b/src/requesthandler/RequestHandler_Stream.cpp @@ -19,6 +19,24 @@ with this program. If not, see #include "RequestHandler.h" +/** + * Gets the status of the stream output. + * + * @responseField outputActive | Boolean | Whether the output is active + * @responseField outputReconnecting | Boolean | Whether the output is currently reconnecting + * @responseField outputTimecode | String | Current formatted timecode string for the output + * @responseField outputDuration | Number | Current duration in milliseconds for the output + * @responseField outputBytes | Number | Number of bytes sent by the output + * @responseField outputSkippedFrames | Number | Number of frames skipped by the output's process + * @responseField outputTotalFrames | Number | Total number of frames delivered by the output's process + * + * @requestType GetStreamStatus + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category stream + */ RequestResult RequestHandler::GetStreamStatus(const Request&) { OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output(); @@ -37,6 +55,18 @@ RequestResult RequestHandler::GetStreamStatus(const Request&) return RequestResult::Success(responseData); } +/** + * Toggles the status of the stream output. + * + * @responseField outputActive | Boolean | New state of the stream output + * + * @requestType ToggleStream + * @complexity 1 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category stream + */ RequestResult RequestHandler::ToggleStream(const Request&) { json responseData; @@ -51,6 +81,16 @@ RequestResult RequestHandler::ToggleStream(const Request&) return RequestResult::Success(responseData); } +/** + * Starts the stream output. + * + * @requestType StartStream + * @complexity 1 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category stream + */ RequestResult RequestHandler::StartStream(const Request&) { if (obs_frontend_streaming_active()) @@ -62,6 +102,16 @@ RequestResult RequestHandler::StartStream(const Request&) return RequestResult::Success(); } +/** + * Stops the stream output. + * + * @requestType StopStream + * @complexity 1 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api requests + * @category stream + */ RequestResult RequestHandler::StopStream(const Request&) { if (!obs_frontend_streaming_active()) @@ -72,3 +122,35 @@ RequestResult RequestHandler::StopStream(const Request&) return RequestResult::Success(); } + +/** + * Sends CEA-608 caption text over the stream output. + * + * @requestField captionText | String | Caption text + * + * @requestType SendStreamCaption + * @complexity 2 + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @category stream + * @api requests + */ +RequestResult RequestHandler::SendStreamCaption(const Request& request) +{ + RequestStatus::RequestStatus statusCode; + std::string comment; + if (!request.ValidateString("captionText", statusCode, comment, true)) + return RequestResult::Error(statusCode, comment); + + if (!obs_frontend_streaming_active()) + return RequestResult::Error(RequestStatus::OutputNotRunning); + + std::string captionText = request.RequestData["captionText"]; + + OBSOutputAutoRelease output = obs_frontend_get_streaming_output(); + + // 0.0 means no delay until the next caption can be sent + obs_output_output_caption_text2(output, captionText.c_str(), 0.0); + + return RequestResult::Success(); +} diff --git a/src/requesthandler/RequestHandler_Transitions.cpp b/src/requesthandler/RequestHandler_Transitions.cpp new file mode 100644 index 00000000..a1ebabe8 --- /dev/null +++ b/src/requesthandler/RequestHandler_Transitions.cpp @@ -0,0 +1,322 @@ +/* +obs-websocket +Copyright (C) 2016-2021 Stephane Lepin +Copyright (C) 2020-2021 Kyle Manning + +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 +*/ + +#include + +#include "RequestHandler.h" + +/** + * Gets an array of all available transition kinds. + * + * Similar to `GetInputKindList` + * + * @responseField transitionKinds | Array | 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 | 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(); +} diff --git a/src/requesthandler/RequestHandler_Ui.cpp b/src/requesthandler/RequestHandler_Ui.cpp new file mode 100644 index 00000000..12fa5d04 --- /dev/null +++ b/src/requesthandler/RequestHandler_Ui.cpp @@ -0,0 +1,150 @@ +/* +obs-websocket +Copyright (C) 2016-2021 Stephane Lepin +Copyright (C) 2020-2021 Kyle Manning + +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 +*/ + +#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(); +} diff --git a/src/requesthandler/rpc/Request.cpp b/src/requesthandler/rpc/Request.cpp index 669a7954..3458347b 100644 --- a/src/requesthandler/rpc/Request.cpp +++ b/src/requesthandler/rpc/Request.cpp @@ -264,18 +264,14 @@ obs_scene_t *Request::ValidateScene2(const std::string &keyName, RequestStatus:: comment = "The specified source is not a scene. (Is group)"; return nullptr; } - OBSScene ret = obs_group_from_source(sceneSource); - obs_scene_addref(ret); - return ret; + return obs_scene_get_ref(obs_group_from_source(sceneSource)); } else { if (filter == OBS_WEBSOCKET_SCENE_FILTER_GROUP_ONLY) { statusCode = RequestStatus::InvalidResourceType; comment = "The specified source is not a group. (Is scene)"; return nullptr; } - OBSScene ret = obs_scene_from_source(sceneSource); - obs_scene_addref(ret); - return ret; + return obs_scene_get_ref(obs_scene_from_source(sceneSource)); } } @@ -295,24 +291,35 @@ obs_source_t *Request::ValidateInput(const std::string &keyName, RequestStatus:: return ret; } +FilterPair Request::ValidateFilter(const std::string &sourceKeyName, const std::string &filterKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const +{ + obs_source_t *source = ValidateSource(sourceKeyName, statusCode, comment); + if (!source) + return FilterPair{source, nullptr}; + + if (!ValidateString(filterKeyName, statusCode, comment)) + return FilterPair{source, nullptr}; + + std::string filterName = RequestData[filterKeyName]; + + obs_source_t *filter = obs_source_get_filter_by_name(source, filterName.c_str()); + if (!filter) { + statusCode = RequestStatus::ResourceNotFound; + comment = std::string("No filter was found in the source `") + RequestData[sourceKeyName].get() + "` with the name `" + filterName + "`."; + return FilterPair{source, nullptr}; + } + + return FilterPair{source, filter}; +} + obs_sceneitem_t *Request::ValidateSceneItem(const std::string &sceneKeyName, const std::string &sceneItemIdKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter) const { - OBSSourceAutoRelease sceneSource = ValidateScene(sceneKeyName, statusCode, comment, filter); - if (!sceneSource) + OBSSceneAutoRelease scene = ValidateScene2(sceneKeyName, statusCode, comment, filter); + if (!scene) return nullptr; if (!ValidateNumber(sceneItemIdKeyName, statusCode, comment, 0)) return nullptr; - - OBSScene scene = obs_scene_from_source(sceneSource); - if (!scene) { - scene = obs_group_from_source(sceneSource); - if (!scene) { // This should never happen - statusCode = RequestStatus::GenericError; - comment = "Somehow the scene was found but the scene object could not be fetched. Please report this to the obs-websocket developers."; - return nullptr; - } - } int64_t sceneItemId = RequestData[sceneItemIdKeyName]; diff --git a/src/requesthandler/rpc/Request.h b/src/requesthandler/rpc/Request.h index 03f6fb21..0f44308b 100644 --- a/src/requesthandler/rpc/Request.h +++ b/src/requesthandler/rpc/Request.h @@ -29,6 +29,12 @@ enum ObsWebSocketSceneFilter { OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP, }; +// We return filters as a pair because `obs_filter_get_parent()` is apparently volatile +struct FilterPair { + OBSSourceAutoRelease source; + OBSSourceAutoRelease filter; +}; + struct Request { Request(const std::string &requestType, const json &requestData = nullptr, const RequestBatchExecutionType::RequestBatchExecutionType executionType = RequestBatchExecutionType::None); @@ -53,6 +59,7 @@ struct Request obs_source_t *ValidateScene(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const; obs_scene_t *ValidateScene2(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const; obs_source_t *ValidateInput(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const; + FilterPair ValidateFilter(const std::string &sourceKeyName, const std::string &filterKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const; obs_sceneitem_t *ValidateSceneItem(const std::string &sceneKeyName, const std::string &sceneItemIdKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const; std::string RequestType; diff --git a/src/requesthandler/types/RequestBatchExecutionType.h b/src/requesthandler/types/RequestBatchExecutionType.h index 9033387f..df158ffe 100644 --- a/src/requesthandler/types/RequestBatchExecutionType.h +++ b/src/requesthandler/types/RequestBatchExecutionType.h @@ -22,7 +22,7 @@ with this program. If not, see #include namespace RequestBatchExecutionType { - enum RequestBatchExecutionType { + enum RequestBatchExecutionType: int8_t { /** * Not a request batch. * @@ -77,7 +77,7 @@ namespace RequestBatchExecutionType { Parallel = 2, }; - inline bool IsValid(uint8_t executionType) + inline bool IsValid(int8_t executionType) { return executionType >= None && executionType <= Parallel; } diff --git a/src/requesthandler/types/RequestStatus.h b/src/requesthandler/types/RequestStatus.h index a9537854..45d7e04e 100644 --- a/src/requesthandler/types/RequestStatus.h +++ b/src/requesthandler/types/RequestStatus.h @@ -332,6 +332,30 @@ namespace RequestStatus { * @api enums */ InvalidInputKind = 605, + /** + * The resource does not support being configured. + * + * This is particularly relevant to transitions, where they do not always have changeable settings. + * + * @enumIdentifier ResourceNotConfigurable + * @enumValue 606 + * @enumType RequestStatus + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api enums + */ + ResourceNotConfigurable = 606, + /** + * The specified filter (obs_source_t-OBS_SOURCE_TYPE_FILTER) had the wrong kind. + * + * @enumIdentifier InvalidFilterKind + * @enumValue 607 + * @enumType RequestStatus + * @rpcVersion -1 + * @initialVersion 5.0.0 + * @api enums + */ + InvalidFilterKind = 607, /** * Creating the resource failed. diff --git a/src/utils/Json.cpp b/src/utils/Json.cpp index 65a0c7c0..114f3afe 100644 --- a/src/utils/Json.cpp +++ b/src/utils/Json.cpp @@ -21,7 +21,7 @@ with this program. If not, see #include "Platform.h" #include "../plugin-macros.generated.h" -bool Utils::Json::JsonArrayIsValidObsArray(json j) +bool Utils::Json::JsonArrayIsValidObsArray(const json &j) { for (auto it : j) { if (!it.is_object()) @@ -191,7 +191,7 @@ bool Utils::Json::GetJsonFileContent(std::string fileName, json &content) return true; } -bool Utils::Json::SetJsonFileContent(std::string fileName, json content, bool createNew) +bool Utils::Json::SetJsonFileContent(std::string fileName, const json &content, bool createNew) { std::string textContent = content.dump(2); return Utils::Platform::SetTextFileContent(fileName, textContent, createNew); diff --git a/src/utils/Json.h b/src/utils/Json.h index 1f6aaef8..f2b88f2b 100644 --- a/src/utils/Json.h +++ b/src/utils/Json.h @@ -27,10 +27,11 @@ using json = nlohmann::json; namespace Utils { namespace Json { - bool JsonArrayIsValidObsArray(json j); + bool JsonArrayIsValidObsArray(const json &j); obs_data_t *JsonToObsData(json j); json ObsDataToJson(obs_data_t *d, bool includeDefault = false); bool GetJsonFileContent(std::string fileName, json &content); - bool SetJsonFileContent(std::string fileName, json content, bool createNew = true); + bool SetJsonFileContent(std::string fileName, const json &content, bool createNew = true); + static inline bool Contains(const json &j, std::string key) { return j.contains(key) && !j[key].is_null(); } } } diff --git a/src/utils/Obs.cpp b/src/utils/Obs.cpp index 3b11ebe9..131e8d22 100644 --- a/src/utils/Obs.cpp +++ b/src/utils/Obs.cpp @@ -17,562 +17,5 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ -#include -#include -#include -#include -#include -#include - #include "Obs.h" -#include "../obs-websocket.h" #include "../plugin-macros.generated.h" - -#define CASE(x) case x: return #x; - -#define RET_COMPARE(str, x) if (str == #x) return x; - -std::vector ConvertStringArray(char **array) -{ - std::vector 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(param); - - if (obs_source_is_group(scene)) - return true; - - (*ret)++; - return true; - }; - - obs_enum_scenes(sceneEnumProc, &ret); - - return ret; -} - -std::vector Utils::Obs::ListHelper::GetSceneCollectionList() -{ - char** sceneCollections = obs_frontend_get_scene_collections(); - auto ret = ConvertStringArray(sceneCollections); - bfree(sceneCollections); - return ret; -} - -std::vector Utils::Obs::ListHelper::GetProfileList() -{ - char** profiles = obs_frontend_get_profiles(); - auto ret = ConvertStringArray(profiles); - bfree(profiles); - return ret; -} - -std::vector Utils::Obs::ListHelper::GetHotkeyList() -{ - std::vector ret; - - obs_enum_hotkeys([](void* data, obs_hotkey_id, obs_hotkey_t* hotkey) { - auto ret = reinterpret_cast *>(data); - - ret->push_back(hotkey); - - return true; - }, &ret); - - return ret; -} - -std::vector Utils::Obs::ListHelper::GetHotkeyNameList() -{ - auto hotkeys = GetHotkeyList(); - - std::vector ret; - for (auto hotkey : hotkeys) { - ret.emplace_back(obs_hotkey_get_name(hotkey)); - } - - return ret; -} - -std::vector Utils::Obs::ListHelper::GetSceneList() -{ - obs_frontend_source_list sceneList = {}; - obs_frontend_get_scenes(&sceneList); - - std::vector 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 Utils::Obs::ListHelper::GetSceneItemList(obs_scene_t *scene, bool basic) -{ - std::pair, bool> enumData; - enumData.second = basic; - - obs_scene_enum_items(scene, [](obs_scene_t*, obs_sceneitem_t* sceneItem, void* param) { - auto enumData = reinterpret_cast, 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 Utils::Obs::ListHelper::GetTransitionList() -{ - obs_frontend_source_list transitionList = {}; - obs_frontend_get_transitions(&transitionList); - - std::vector 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 inputs; -}; - -std::vector 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(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 Utils::Obs::ListHelper::GetInputKindList(bool unversioned, bool includeDisabled) -{ - std::vector 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(_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; -} diff --git a/src/utils/Obs.h b/src/utils/Obs.h index 6a5aaa59..4eda079a 100644 --- a/src/utils/Obs.h +++ b/src/utils/Obs.h @@ -21,33 +21,132 @@ with this program. If not, see #include #include +#include #include "Json.h" +// Autorelease object definitions +inline void ___properties_dummy_addref(obs_properties_t*){} +using OBSPropertiesAutoDestroy = OBSRef; + +#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; +using OBSSceneAutoRelease = OBSRef; +using OBSSceneItemAutoRelease = OBSRef; +using OBSDataAutoRelease = OBSRef; +using OBSDataArrayAutoRelease = OBSRef; +using OBSOutputAutoRelease = OBSRef; +using OBSEncoderAutoRelease = OBSRef; +using OBSServiceAutoRelease = OBSRef; + +using OBSWeakSourceAutoRelease = OBSRef; +using OBSWeakOutputAutoRelease = OBSRef; +using OBSWeakEncoderAutoRelease = OBSRef; +using OBSWeakServiceAutoRelease = OBSRef; +#endif + template T* GetCalldataPointer(const calldata_t *data, const char* name) { void *ptr = nullptr; calldata_get_ptr(data, name, &ptr); - return reinterpret_cast(ptr); + return static_cast(ptr); } enum ObsOutputState { + OBS_WEBSOCKET_OUTPUT_UNKNOWN, OBS_WEBSOCKET_OUTPUT_STARTING, OBS_WEBSOCKET_OUTPUT_STARTED, OBS_WEBSOCKET_OUTPUT_STOPPING, OBS_WEBSOCKET_OUTPUT_STOPPED, OBS_WEBSOCKET_OUTPUT_RECONNECTING, OBS_WEBSOCKET_OUTPUT_PAUSED, - OBS_WEBSOCKET_OUTPUT_RESUMED + OBS_WEBSOCKET_OUTPUT_RESUMED, }; enum ObsMediaInputAction { + /** + * No action. + * + * @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE + * @enumType ObsMediaInputAction + * @rpcVersion 1 + * @initialVersion 5.0.0 + * @api enums + */ OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE, + /** + * Play the media input. + * + * @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY + * @enumType ObsMediaInputAction + * @rpcVersion 1 + * @initialVersion 5.0.0 + * @api enums + */ OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY, + /** + * Pause the media input. + * + * @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE + * @enumType ObsMediaInputAction + * @rpcVersion 1 + * @initialVersion 5.0.0 + * @api enums + */ OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE, + /** + * Stop the media input. + * + * @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP + * @enumType ObsMediaInputAction + * @rpcVersion 1 + * @initialVersion 5.0.0 + * @api enums + */ OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP, + /** + * Restart the media input. + * + * @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART + * @enumType ObsMediaInputAction + * @rpcVersion 1 + * @initialVersion 5.0.0 + * @api enums + */ OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART, + /** + * Go to the next playlist item. + * + * @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT + * @enumType ObsMediaInputAction + * @rpcVersion 1 + * @initialVersion 5.0.0 + * @api enums + */ OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT, - OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS + /** + * Go to the previous playlist item. + * + * @enumIdentifier OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS + * @enumType ObsMediaInputAction + * @rpcVersion 1 + * @initialVersion 5.0.0 + * @api enums + */ + OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS, }; namespace Utils { @@ -59,48 +158,61 @@ namespace Utils { std::string GetCurrentProfilePath(); std::string GetCurrentRecordOutputPath(); std::string GetSourceType(obs_source_t *source); + std::string GetInputMonitorType(enum obs_monitoring_type monitorType); std::string GetInputMonitorType(obs_source_t *input); std::string GetMediaInputState(obs_source_t *input); std::string GetLastReplayBufferFilePath(); std::string GetSceneItemBoundsType(enum obs_bounds_type type); + std::string GetSceneItemBlendMode(enum obs_blending_type mode); std::string DurationToTimecode(uint64_t); + std::string GetOutputState(ObsOutputState state); } namespace EnumHelper { enum obs_bounds_type GetSceneItemBoundsType(std::string boundsType); enum ObsMediaInputAction GetMediaInputAction(std::string mediaAction); + enum obs_blending_type GetSceneItemBlendMode(std::string mode); } namespace NumberHelper { uint64_t GetOutputDuration(obs_output_t *output); size_t GetSceneCount(); + size_t GetSourceFilterIndex(obs_source_t *source, obs_source_t *filter); } - namespace ListHelper { + namespace ArrayHelper { std::vector GetSceneCollectionList(); std::vector GetProfileList(); std::vector GetHotkeyList(); std::vector GetHotkeyNameList(); std::vector GetSceneList(); + std::vector GetGroupList(); std::vector GetSceneItemList(obs_scene_t *scene, bool basic = false); - std::vector GetTransitionList(); std::vector GetInputList(std::string inputKind = ""); std::vector GetInputKindList(bool unversioned = false, bool includeDisabled = false); + std::vector GetListPropertyItems(obs_property_t *property); + std::vector GetTransitionKindList(); + std::vector GetSceneTransitionList(); + std::vector GetSourceFilterList(obs_source_t *source); + std::vector GetFilterKindList(); } - namespace DataHelper { + namespace ObjectHelper { json GetStats(); json GetSceneItemTransform(obs_sceneitem_t *item); } namespace SearchHelper { obs_hotkey_t *GetHotkeyByName(std::string name); + obs_source_t *GetSceneTransitionByName(std::string name); // Increments source ref. Use OBSSourceAutoRelease obs_sceneitem_t *GetSceneItemByName(obs_scene_t *scene, std::string name); // Increments ref. Use OBSSceneItemAutoRelease } namespace ActionHelper { obs_sceneitem_t *CreateSceneItem(obs_source_t *source, obs_scene_t *scene, bool sceneItemEnabled = true, obs_transform_info *sceneItemTransform = nullptr, obs_sceneitem_crop *sceneItemCrop = nullptr); // Increments ref. Use OBSSceneItemAutoRelease obs_sceneitem_t *CreateInput(std::string inputName, std::string inputKind, obs_data_t *inputSettings, obs_scene_t *scene, bool sceneItemEnabled = true); // Increments ref. Use OBSSceneItemAutoRelease + obs_source_t *CreateSourceFilter(obs_source_t *source, std::string filterName, std::string filterKind, obs_data_t *filterSettings); // Increments source ref. Use OBSSourceAutoRelease + void SetSourceFilterIndex(obs_source_t *source, obs_source_t *filter, size_t index); } } } diff --git a/src/utils/Obs_ActionHelper.cpp b/src/utils/Obs_ActionHelper.cpp new file mode 100644 index 00000000..8e981125 --- /dev/null +++ b/src/utils/Obs_ActionHelper.cpp @@ -0,0 +1,116 @@ +/* +obs-websocket +Copyright (C) 2020-2021 Kyle Manning + +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 +*/ + +#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(_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--; + } +} diff --git a/src/utils/Obs_ArrayHelper.cpp b/src/utils/Obs_ArrayHelper.cpp new file mode 100644 index 00000000..ecf26c16 --- /dev/null +++ b/src/utils/Obs_ArrayHelper.cpp @@ -0,0 +1,313 @@ +/* +obs-websocket +Copyright (C) 2016-2021 Stephane Lepin +Copyright (C) 2020-2021 Kyle Manning + +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 +*/ + +#include + +#include "Obs.h" +#include "../plugin-macros.generated.h" + +static std::vector ConvertStringArray(char **array) +{ + std::vector 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 Utils::Obs::ArrayHelper::GetSceneCollectionList() +{ + char** sceneCollections = obs_frontend_get_scene_collections(); + auto ret = ConvertStringArray(sceneCollections); + bfree(sceneCollections); + return ret; +} + +std::vector Utils::Obs::ArrayHelper::GetProfileList() +{ + char** profiles = obs_frontend_get_profiles(); + auto ret = ConvertStringArray(profiles); + bfree(profiles); + return ret; +} + +std::vector Utils::Obs::ArrayHelper::GetHotkeyList() +{ + std::vector ret; + + obs_enum_hotkeys([](void* data, obs_hotkey_id, obs_hotkey_t* hotkey) { + auto ret = static_cast *>(data); + + ret->push_back(hotkey); + + return true; + }, &ret); + + return ret; +} + +std::vector Utils::Obs::ArrayHelper::GetHotkeyNameList() +{ + auto hotkeys = GetHotkeyList(); + + std::vector ret; + for (auto hotkey : hotkeys) + ret.emplace_back(obs_hotkey_get_name(hotkey)); + + return ret; +} + +std::vector Utils::Obs::ArrayHelper::GetSceneList() +{ + obs_frontend_source_list sceneList = {}; + obs_frontend_get_scenes(&sceneList); + + std::vector 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 Utils::Obs::ArrayHelper::GetGroupList() +{ + std::vector ret; + + auto cb = [](void *priv_data, obs_source_t *scene) { + auto ret = static_cast*>(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 Utils::Obs::ArrayHelper::GetSceneItemList(obs_scene_t *scene, bool basic) +{ + std::pair, bool> enumData; + enumData.second = basic; + + obs_scene_enum_items(scene, [](obs_scene_t*, obs_sceneitem_t* sceneItem, void* param) { + auto enumData = static_cast, 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 inputs; +}; + +std::vector 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(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 Utils::Obs::ArrayHelper::GetInputKindList(bool unversioned, bool includeDisabled) +{ + std::vector 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 Utils::Obs::ArrayHelper::GetListPropertyItems(obs_property_t *property) +{ + std::vector 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 Utils::Obs::ArrayHelper::GetTransitionKindList() +{ + std::vector ret; + + size_t idx = 0; + const char *kind; + while (obs_enum_transition_types(idx++, &kind)) + ret.emplace_back(kind); + + return ret; +} + +std::vector Utils::Obs::ArrayHelper::GetSceneTransitionList() +{ + obs_frontend_source_list transitionList = {}; + obs_frontend_get_transitions(&transitionList); + + std::vector 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 Utils::Obs::ArrayHelper::GetFilterKindList() +{ + std::vector ret; + + size_t idx = 0; + const char *kind; + while(obs_enum_filter_types(idx++, &kind)) + ret.push_back(kind); + + return ret; +} + +std::vector Utils::Obs::ArrayHelper::GetSourceFilterList(obs_source_t *source) +{ + std::vector filters; + + auto enumFilters = [](obs_source_t *, obs_source_t *filter, void *param) { + auto filters = reinterpret_cast*>(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; +} diff --git a/src/utils/Obs_EnumHelper.cpp b/src/utils/Obs_EnumHelper.cpp new file mode 100644 index 00000000..887c33d1 --- /dev/null +++ b/src/utils/Obs_EnumHelper.cpp @@ -0,0 +1,60 @@ +/* +obs-websocket +Copyright (C) 2020-2021 Kyle Manning + +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 +*/ + +#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; +} diff --git a/src/utils/Obs_NumberHelper.cpp b/src/utils/Obs_NumberHelper.cpp new file mode 100644 index 00000000..a510c783 --- /dev/null +++ b/src/utils/Obs_NumberHelper.cpp @@ -0,0 +1,79 @@ +/* +obs-websocket +Copyright (C) 2016-2021 Stephane Lepin +Copyright (C) 2020-2021 Kyle Manning + +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 +*/ + +#include +#include + +#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(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(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; +} diff --git a/src/utils/Obs_ObjectHelper.cpp b/src/utils/Obs_ObjectHelper.cpp new file mode 100644 index 00000000..14714976 --- /dev/null +++ b/src/utils/Obs_ObjectHelper.cpp @@ -0,0 +1,87 @@ +/* +obs-websocket +Copyright (C) 2016-2021 Stephane Lepin +Copyright (C) 2020-2021 Kyle Manning + +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 +*/ + +#include + +#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; +} diff --git a/src/utils/Obs_SearchHelper.cpp b/src/utils/Obs_SearchHelper.cpp new file mode 100644 index 00000000..c39501f7 --- /dev/null +++ b/src/utils/Obs_SearchHelper.cpp @@ -0,0 +1,68 @@ +/* +obs-websocket +Copyright (C) 2020-2021 Kyle Manning + +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 +*/ + +#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; +} diff --git a/src/utils/Obs_StringHelper.cpp b/src/utils/Obs_StringHelper.cpp new file mode 100644 index 00000000..c5e1bc07 --- /dev/null +++ b/src/utils/Obs_StringHelper.cpp @@ -0,0 +1,192 @@ +/* +obs-websocket +Copyright (C) 2016-2021 Stephane Lepin +Copyright (C) 2020-2021 Kyle Manning + +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 +*/ + +#include +#include + +#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) + } +} diff --git a/src/utils/ObsVolumeMeter.cpp b/src/utils/Obs_VolumeMeter.cpp similarity index 95% rename from src/utils/ObsVolumeMeter.cpp rename to src/utils/Obs_VolumeMeter.cpp index 0447f312..9fa75eec 100644 --- a/src/utils/ObsVolumeMeter.cpp +++ b/src/utils/Obs_VolumeMeter.cpp @@ -1,355 +1,353 @@ -/* -obs-websocket -Copyright (C) 2014 by Leonhard Oelke -Copyright (C) 2016-2021 Stephane Lepin -Copyright (C) 2020-2021 Kyle Manning - -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 -*/ - -#include -#include - -#include "Obs.h" -#include "ObsVolumeMeter.h" -#include "ObsVolumeMeter_Helpers.h" - -Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input) : - PeakMeterType(SAMPLE_PEAK_METER), - _input(obs_source_get_weak_source(input)), - _channels(0), - _lastUpdate(0), - _volume(obs_source_get_volume(input)) -{ - signal_handler_t *sh = obs_source_get_signal_handler(input); - signal_handler_connect(sh, "volume", Meter::InputVolumeCallback, this); - - obs_source_add_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this); - - blog_debug("[Utils::Obs::VolumeMeter::Meter::Meter] Meter created for input: %s", obs_source_get_name(input)); -} - -Utils::Obs::VolumeMeter::Meter::~Meter() -{ - OBSSourceAutoRelease input = obs_weak_source_get_source(_input); - if (!input) { - blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::~Meter] Failed to get strong reference to input. Has it been destroyed?"); - return; - } - - signal_handler_t *sh = obs_source_get_signal_handler(input); - signal_handler_disconnect(sh, "volume", Meter::InputVolumeCallback, this); - - obs_source_remove_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this); - - blog_debug("[Utils::Obs::VolumeMeter::Meter::~Meter] Meter destroyed for input: %s", obs_source_get_name(input)); -} - -bool Utils::Obs::VolumeMeter::Meter::InputValid() -{ - // return !obs_weak_source_expired(_input); - return true; -} - -json Utils::Obs::VolumeMeter::Meter::GetMeterData() -{ - json ret; - - OBSSourceAutoRelease input = obs_weak_source_get_source(_input); - if (!input) { - blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::GetMeterData] Failed to get strong reference to input. Has it been destroyed?"); - return ret; - } - - std::vector> levels; - const float volume = _muted ? 0.0f : _volume.load(); - - std::unique_lock l(_mutex); - - if (_lastUpdate != 0 && (os_gettime_ns() - _lastUpdate) * 0.000000001 > 0.3) - ResetAudioLevels(); - - for (int channel = 0; channel < _channels; channel++) { - std::vector level; - level.push_back(_magnitude[channel] * volume); - level.push_back(_peak[channel] * volume); - level.push_back(_peak[channel]); - - levels.push_back(level); - } - l.unlock(); - - ret["inputName"] = obs_source_get_name(input); - ret["inputLevelsMul"] = levels; - - return ret; -} - -// MUST HOLD LOCK -void Utils::Obs::VolumeMeter::Meter::ResetAudioLevels() -{ - _lastUpdate = 0; - for (int channelNumber = 0; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++) { - _magnitude[channelNumber] = 0; - _peak[channelNumber] = 0; - } -} - -// MUST HOLD LOCK -void Utils::Obs::VolumeMeter::Meter::ProcessAudioChannels(const struct audio_data *data) -{ - int channels = 0; - for (int i = 0; i < MAX_AV_PLANES; i++) { - if (data->data[i]) - channels++; - } - - bool channelsChanged = _channels != channels; - _channels = std::clamp(channels, 0, MAX_AUDIO_CHANNELS); - - if (channelsChanged) - ResetAudioLevels(); -} - -// MUST HOLD LOCK -void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data) -{ - size_t sampleCount = data->frames; - int channelNumber = 0; - - for (int planeNumber = 0; channelNumber < _channels; planeNumber++) { - float *samples = (float*)data->data[planeNumber]; - if (!samples) - continue; - - if (((uintptr_t)samples & 0xf) > 0) { - _peak[channelNumber] = 1.0f; - channelNumber++; - continue; - } - - __m128 previousSamples = _mm_loadu_ps(_previousSamples[channelNumber]); - - float peak; - switch (PeakMeterType) { - default: - case SAMPLE_PEAK_METER: - peak = GetSamplePeak(previousSamples, samples, sampleCount); - break; - case TRUE_PEAK_METER: - peak = GetTruePeak(previousSamples, samples, sampleCount); - break; - } - - switch (sampleCount) { - case 0: - break; - case 1: - _previousSamples[channelNumber][0] = _previousSamples[channelNumber][1]; - _previousSamples[channelNumber][1] = _previousSamples[channelNumber][2]; - _previousSamples[channelNumber][2] = _previousSamples[channelNumber][3]; - _previousSamples[channelNumber][3] = samples[sampleCount - 1]; - break; - case 2: - _previousSamples[channelNumber][0] = _previousSamples[channelNumber][2]; - _previousSamples[channelNumber][1] = _previousSamples[channelNumber][3]; - _previousSamples[channelNumber][2] = samples[sampleCount - 2]; - _previousSamples[channelNumber][3] = samples[sampleCount - 1]; - break; - case 3: - _previousSamples[channelNumber][0] = _previousSamples[channelNumber][3]; - _previousSamples[channelNumber][1] = samples[sampleCount - 3]; - _previousSamples[channelNumber][2] = samples[sampleCount - 2]; - _previousSamples[channelNumber][3] = samples[sampleCount - 1]; - break; - default: - _previousSamples[channelNumber][0] = samples[sampleCount - 4]; - _previousSamples[channelNumber][1] = samples[sampleCount - 3]; - _previousSamples[channelNumber][2] = samples[sampleCount - 2]; - _previousSamples[channelNumber][3] = samples[sampleCount - 1]; - } - - _peak[channelNumber] = peak; - - channelNumber++; - } - - for (; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++) - _peak[channelNumber] = 0.0; -} - -// MUST HOLD LOCK -void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(const struct audio_data *data) -{ - size_t sampleCount = data->frames; - - int channelNumber = 0; - for (int planeNumber = 0; channelNumber < _channels; planeNumber++) { - float *samples = (float*)data->data[planeNumber]; - if (!samples) - continue; - - float sum = 0.0; - for (size_t i = 0; i < sampleCount; i++) { - float sample = samples[i]; - sum += sample * sample; - } - - _magnitude[channelNumber] = std::sqrt(sum / sampleCount); - - channelNumber++; - } -} - -void Utils::Obs::VolumeMeter::Meter::InputAudioCaptureCallback(void *priv_data, obs_source_t *, const struct audio_data *data, bool muted) -{ - auto c = static_cast(priv_data); - - std::unique_lock l(c->_mutex); - - c->_muted = muted; - c->ProcessAudioChannels(data); - c->ProcessPeak(data); - c->ProcessMagnitude(data); - - c->_lastUpdate = os_gettime_ns(); -} - -void Utils::Obs::VolumeMeter::Meter::InputVolumeCallback(void *priv_data, calldata_t *cd) -{ - auto c = static_cast(priv_data); - - c->_volume = (float)calldata_float(cd, "volume"); -} - -Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod) : - _updateCallback(cb), - _updatePeriod(updatePeriod), - _running(false) -{ - signal_handler_t *sh = obs_get_signal_handler(); - if (!sh) - return; - - auto enumProc = [](void *priv_data, obs_source_t *input) { - auto c = static_cast(priv_data); - - if (!obs_source_active(input)) - return true; - - uint32_t flags = obs_source_get_output_flags(input); - if ((flags & OBS_SOURCE_AUDIO) == 0) - return true; - - c->_meters.emplace_back(std::move(new Meter(input))); - - return true; - }; - obs_enum_sources(enumProc, this); - - signal_handler_connect(sh, "source_activate", Handler::InputActivateCallback, this); - signal_handler_connect(sh, "source_deactivate", Handler::InputDeactivateCallback, this); - - _running = true; - _updateThread = std::thread(&Handler::UpdateThread, this); - - blog_debug("[Utils::Obs::VolumeMeter::Handler::Handler] Handler created."); -} - -Utils::Obs::VolumeMeter::Handler::~Handler() -{ - signal_handler_t *sh = obs_get_signal_handler(); - if (!sh) - return; - - signal_handler_disconnect(sh, "source_activate", Handler::InputActivateCallback, this); - signal_handler_disconnect(sh, "source_deactivate", Handler::InputDeactivateCallback, this); - - if (_running) { - _mutex.lock(); - _running = false; - _mutex.unlock(); - _cond.notify_all(); - } - - if (_updateThread.joinable()) - _updateThread.join(); - - blog_debug("[Utils::Obs::VolumeMeter::Handler::~Handler] Handler destroyed."); -} - -void Utils::Obs::VolumeMeter::Handler::UpdateThread() -{ - blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread started."); - while (_running) { - { - std::unique_lock l(_mutex); - if (_cond.wait_for(l, std::chrono::milliseconds(_updatePeriod), [this]{ return !_running; })) - break; - } - - std::vector inputs; - std::unique_lock l(_meterMutex); - for (auto &meter : _meters) { - if (meter->InputValid()) - inputs.push_back(meter->GetMeterData()); - } - l.unlock(); - - if (_updateCallback) - _updateCallback(inputs); - } - blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread stopped."); -} - -void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data, calldata_t *cd) -{ - auto c = static_cast(priv_data); - - obs_source_t *input = GetCalldataPointer(cd, "source"); - if (!input) - return; - - if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT) - return; - - uint32_t flags = obs_source_get_output_flags(input); - if ((flags & OBS_SOURCE_AUDIO) == 0) - return; - - std::unique_lock l(c->_meterMutex); - c->_meters.emplace_back(std::move(new Meter(input))); -} - -void Utils::Obs::VolumeMeter::Handler::InputDeactivateCallback(void *priv_data, calldata_t *cd) -{ - auto c = static_cast(priv_data); - - obs_source_t *input = GetCalldataPointer(cd, "source"); - if (!input) - return; - - if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT) - return; - - // Don't ask me why, but using std::remove_if segfaults trying this. - std::unique_lock l(c->_meterMutex); - std::vector::iterator iter; - for (iter = c->_meters.begin(); iter != c->_meters.end();) { - if (obs_weak_source_references_source(iter->get()->GetWeakInput(), input)) - iter = c->_meters.erase(iter); - else - ++iter; - } -} +/* +obs-websocket +Copyright (C) 2014 by Leonhard Oelke +Copyright (C) 2016-2021 Stephane Lepin +Copyright (C) 2020-2021 Kyle Manning + +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 +*/ + +#include +#include + +#include "Obs.h" +#include "Obs_VolumeMeter.h" +#include "Obs_VolumeMeter_Helpers.h" +#include "../obs-websocket.h" + +Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input) : + PeakMeterType(SAMPLE_PEAK_METER), + _input(obs_source_get_weak_source(input)), + _channels(0), + _lastUpdate(0), + _volume(obs_source_get_volume(input)) +{ + signal_handler_t *sh = obs_source_get_signal_handler(input); + signal_handler_connect(sh, "volume", Meter::InputVolumeCallback, this); + + obs_source_add_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this); + + blog_debug("[Utils::Obs::VolumeMeter::Meter::Meter] Meter created for input: %s", obs_source_get_name(input)); +} + +Utils::Obs::VolumeMeter::Meter::~Meter() +{ + OBSSourceAutoRelease input = obs_weak_source_get_source(_input); + if (!input) { + blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::~Meter] Failed to get strong reference to input. Has it been destroyed?"); + return; + } + + signal_handler_t *sh = obs_source_get_signal_handler(input); + signal_handler_disconnect(sh, "volume", Meter::InputVolumeCallback, this); + + obs_source_remove_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this); + + blog_debug("[Utils::Obs::VolumeMeter::Meter::~Meter] Meter destroyed for input: %s", obs_source_get_name(input)); +} + +bool Utils::Obs::VolumeMeter::Meter::InputValid() +{ + return !obs_weak_source_expired(_input); +} + +json Utils::Obs::VolumeMeter::Meter::GetMeterData() +{ + json ret; + + OBSSourceAutoRelease input = obs_weak_source_get_source(_input); + if (!input) { + blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::GetMeterData] Failed to get strong reference to input. Has it been destroyed?"); + return ret; + } + + std::vector> levels; + const float volume = _muted ? 0.0f : _volume.load(); + + std::unique_lock l(_mutex); + + if (_lastUpdate != 0 && (os_gettime_ns() - _lastUpdate) * 0.000000001 > 0.3) + ResetAudioLevels(); + + for (int channel = 0; channel < _channels; channel++) { + std::vector level; + level.push_back(_magnitude[channel] * volume); + level.push_back(_peak[channel] * volume); + level.push_back(_peak[channel]); + + levels.push_back(level); + } + l.unlock(); + + ret["inputName"] = obs_source_get_name(input); + ret["inputLevelsMul"] = levels; + + return ret; +} + +// MUST HOLD LOCK +void Utils::Obs::VolumeMeter::Meter::ResetAudioLevels() +{ + _lastUpdate = 0; + for (int channelNumber = 0; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++) { + _magnitude[channelNumber] = 0; + _peak[channelNumber] = 0; + } +} + +// MUST HOLD LOCK +void Utils::Obs::VolumeMeter::Meter::ProcessAudioChannels(const struct audio_data *data) +{ + int channels = 0; + for (int i = 0; i < MAX_AV_PLANES; i++) { + if (data->data[i]) + channels++; + } + + bool channelsChanged = _channels != channels; + _channels = std::clamp(channels, 0, MAX_AUDIO_CHANNELS); + + if (channelsChanged) + ResetAudioLevels(); +} + +// MUST HOLD LOCK +void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data) +{ + size_t sampleCount = data->frames; + int channelNumber = 0; + + for (int planeNumber = 0; channelNumber < _channels; planeNumber++) { + float *samples = (float*)data->data[planeNumber]; + if (!samples) + continue; + + if (((uintptr_t)samples & 0xf) > 0) { + _peak[channelNumber] = 1.0f; + channelNumber++; + continue; + } + + __m128 previousSamples = _mm_loadu_ps(_previousSamples[channelNumber]); + + float peak; + switch (PeakMeterType) { + default: + case SAMPLE_PEAK_METER: + peak = GetSamplePeak(previousSamples, samples, sampleCount); + break; + case TRUE_PEAK_METER: + peak = GetTruePeak(previousSamples, samples, sampleCount); + break; + } + + switch (sampleCount) { + case 0: + break; + case 1: + _previousSamples[channelNumber][0] = _previousSamples[channelNumber][1]; + _previousSamples[channelNumber][1] = _previousSamples[channelNumber][2]; + _previousSamples[channelNumber][2] = _previousSamples[channelNumber][3]; + _previousSamples[channelNumber][3] = samples[sampleCount - 1]; + break; + case 2: + _previousSamples[channelNumber][0] = _previousSamples[channelNumber][2]; + _previousSamples[channelNumber][1] = _previousSamples[channelNumber][3]; + _previousSamples[channelNumber][2] = samples[sampleCount - 2]; + _previousSamples[channelNumber][3] = samples[sampleCount - 1]; + break; + case 3: + _previousSamples[channelNumber][0] = _previousSamples[channelNumber][3]; + _previousSamples[channelNumber][1] = samples[sampleCount - 3]; + _previousSamples[channelNumber][2] = samples[sampleCount - 2]; + _previousSamples[channelNumber][3] = samples[sampleCount - 1]; + break; + default: + _previousSamples[channelNumber][0] = samples[sampleCount - 4]; + _previousSamples[channelNumber][1] = samples[sampleCount - 3]; + _previousSamples[channelNumber][2] = samples[sampleCount - 2]; + _previousSamples[channelNumber][3] = samples[sampleCount - 1]; + } + + _peak[channelNumber] = peak; + + channelNumber++; + } + + for (; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++) + _peak[channelNumber] = 0.0; +} + +// MUST HOLD LOCK +void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(const struct audio_data *data) +{ + size_t sampleCount = data->frames; + + int channelNumber = 0; + for (int planeNumber = 0; channelNumber < _channels; planeNumber++) { + float *samples = (float*)data->data[planeNumber]; + if (!samples) + continue; + + float sum = 0.0; + for (size_t i = 0; i < sampleCount; i++) { + float sample = samples[i]; + sum += sample * sample; + } + + _magnitude[channelNumber] = std::sqrt(sum / sampleCount); + + channelNumber++; + } +} + +void Utils::Obs::VolumeMeter::Meter::InputAudioCaptureCallback(void *priv_data, obs_source_t *, const struct audio_data *data, bool muted) +{ + auto c = static_cast(priv_data); + + std::unique_lock l(c->_mutex); + + c->_muted = muted; + c->ProcessAudioChannels(data); + c->ProcessPeak(data); + c->ProcessMagnitude(data); + + c->_lastUpdate = os_gettime_ns(); +} + +void Utils::Obs::VolumeMeter::Meter::InputVolumeCallback(void *priv_data, calldata_t *cd) +{ + auto c = static_cast(priv_data); + + c->_volume = (float)calldata_float(cd, "volume"); +} + +Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod) : + _updateCallback(cb), + _updatePeriod(updatePeriod), + _running(false) +{ + signal_handler_t *sh = obs_get_signal_handler(); + if (!sh) + return; + + auto enumProc = [](void *priv_data, obs_source_t *input) { + auto c = static_cast(priv_data); + + if (!obs_source_active(input)) + return true; + + uint32_t flags = obs_source_get_output_flags(input); + if ((flags & OBS_SOURCE_AUDIO) == 0) + return true; + + c->_meters.emplace_back(std::move(new Meter(input))); + + return true; + }; + obs_enum_sources(enumProc, this); + + signal_handler_connect(sh, "source_activate", Handler::InputActivateCallback, this); + signal_handler_connect(sh, "source_deactivate", Handler::InputDeactivateCallback, this); + + _running = true; + _updateThread = std::thread(&Handler::UpdateThread, this); + + blog_debug("[Utils::Obs::VolumeMeter::Handler::Handler] Handler created."); +} + +Utils::Obs::VolumeMeter::Handler::~Handler() +{ + signal_handler_t *sh = obs_get_signal_handler(); + if (!sh) + return; + + signal_handler_disconnect(sh, "source_activate", Handler::InputActivateCallback, this); + signal_handler_disconnect(sh, "source_deactivate", Handler::InputDeactivateCallback, this); + + if (_running) { + _running = false; + _cond.notify_all(); + } + + if (_updateThread.joinable()) + _updateThread.join(); + + blog_debug("[Utils::Obs::VolumeMeter::Handler::~Handler] Handler destroyed."); +} + +void Utils::Obs::VolumeMeter::Handler::UpdateThread() +{ + blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread started."); + while (_running) { + { + std::unique_lock l(_mutex); + if (_cond.wait_for(l, std::chrono::milliseconds(_updatePeriod), [this]{ return !_running; })) + break; + } + + std::vector inputs; + std::unique_lock l(_meterMutex); + for (auto &meter : _meters) { + if (meter->InputValid()) + inputs.push_back(meter->GetMeterData()); + } + l.unlock(); + + if (_updateCallback) + _updateCallback(inputs); + } + blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread stopped."); +} + +void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data, calldata_t *cd) +{ + auto c = static_cast(priv_data); + + obs_source_t *input = GetCalldataPointer(cd, "source"); + if (!input) + return; + + if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT) + return; + + uint32_t flags = obs_source_get_output_flags(input); + if ((flags & OBS_SOURCE_AUDIO) == 0) + return; + + std::unique_lock l(c->_meterMutex); + c->_meters.emplace_back(std::move(new Meter(input))); +} + +void Utils::Obs::VolumeMeter::Handler::InputDeactivateCallback(void *priv_data, calldata_t *cd) +{ + auto c = static_cast(priv_data); + + obs_source_t *input = GetCalldataPointer(cd, "source"); + if (!input) + return; + + if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT) + return; + + // Don't ask me why, but using std::remove_if segfaults trying this. + std::unique_lock l(c->_meterMutex); + std::vector::iterator iter; + for (iter = c->_meters.begin(); iter != c->_meters.end();) { + if (obs_weak_source_references_source(iter->get()->GetWeakInput(), input)) + iter = c->_meters.erase(iter); + else + ++iter; + } +} diff --git a/src/utils/ObsVolumeMeter.h b/src/utils/Obs_VolumeMeter.h similarity index 95% rename from src/utils/ObsVolumeMeter.h rename to src/utils/Obs_VolumeMeter.h index 49d99fc8..e2b20a6e 100644 --- a/src/utils/ObsVolumeMeter.h +++ b/src/utils/Obs_VolumeMeter.h @@ -1,99 +1,99 @@ -/* -obs-websocket -Copyright (C) 2016-2021 Stephane Lepin -Copyright (C) 2020-2021 Kyle Manning - -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 -*/ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include "../obs-websocket.h" -#include "Json.h" - -namespace Utils { - namespace Obs { - namespace VolumeMeter { - // Some code copied from https://github.com/obsproject/obs-studio/blob/master/libobs/obs-audio-controls.c - // Keeps a running tally of the current audio levels, for a specific input - class Meter { - public: - Meter(obs_source_t *input); - ~Meter(); - - bool InputValid(); - obs_weak_source_t *GetWeakInput() { return _input; } - json GetMeterData(); - - std::atomic PeakMeterType; - - private: - OBSWeakSourceAutoRelease _input; - - // All values in mul - std::mutex _mutex; - bool _muted; - int _channels; - float _magnitude[MAX_AUDIO_CHANNELS]; - float _peak[MAX_AUDIO_CHANNELS]; - float _previousSamples[MAX_AUDIO_CHANNELS][4]; - - std::atomic _lastUpdate; - std::atomic _volume; - - void ResetAudioLevels(); - void ProcessAudioChannels(const struct audio_data *data); - void ProcessPeak(const struct audio_data *data); - void ProcessMagnitude(const struct audio_data *data); - - static void InputAudioCaptureCallback(void *priv_data, obs_source_t *source, const struct audio_data *data, bool muted); - static void InputVolumeCallback(void *priv_data, calldata_t *cd); - }; - - // Maintains an array of active inputs - class Handler { - typedef std::function)> UpdateCallback; - typedef std::unique_ptr MeterPtr; - - public: - Handler(UpdateCallback cb, uint64_t updatePeriod = 50); - ~Handler(); - - private: - UpdateCallback _updateCallback; - - std::mutex _meterMutex; - std::vector _meters; - uint64_t _updatePeriod; - - std::mutex _mutex; - std::condition_variable _cond; - bool _running; - std::thread _updateThread; - - void UpdateThread(); - static void InputActivateCallback(void *priv_data, calldata_t *cd); - static void InputDeactivateCallback(void *priv_data, calldata_t *cd); - }; - } - } -} +/* +obs-websocket +Copyright (C) 2016-2021 Stephane Lepin +Copyright (C) 2020-2021 Kyle Manning + +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 +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "Obs.h" +#include "Json.h" + +namespace Utils { + namespace Obs { + namespace VolumeMeter { + // Some code copied from https://github.com/obsproject/obs-studio/blob/master/libobs/obs-audio-controls.c + // Keeps a running tally of the current audio levels, for a specific input + class Meter { + public: + Meter(obs_source_t *input); + ~Meter(); + + bool InputValid(); + obs_weak_source_t *GetWeakInput() { return _input; } + json GetMeterData(); + + std::atomic PeakMeterType; + + private: + OBSWeakSourceAutoRelease _input; + + // All values in mul + std::mutex _mutex; + bool _muted; + int _channels; + float _magnitude[MAX_AUDIO_CHANNELS]; + float _peak[MAX_AUDIO_CHANNELS]; + float _previousSamples[MAX_AUDIO_CHANNELS][4]; + + std::atomic _lastUpdate; + std::atomic _volume; + + void ResetAudioLevels(); + void ProcessAudioChannels(const struct audio_data *data); + void ProcessPeak(const struct audio_data *data); + void ProcessMagnitude(const struct audio_data *data); + + static void InputAudioCaptureCallback(void *priv_data, obs_source_t *source, const struct audio_data *data, bool muted); + static void InputVolumeCallback(void *priv_data, calldata_t *cd); + }; + + // Maintains an array of active inputs + class Handler { + typedef std::function)> UpdateCallback; + typedef std::unique_ptr MeterPtr; + + public: + Handler(UpdateCallback cb, uint64_t updatePeriod = 50); + ~Handler(); + + private: + UpdateCallback _updateCallback; + + std::mutex _meterMutex; + std::vector _meters; + uint64_t _updatePeriod; + + std::mutex _mutex; + std::condition_variable _cond; + std::atomic _running; + std::thread _updateThread; + + void UpdateThread(); + static void InputActivateCallback(void *priv_data, calldata_t *cd); + static void InputDeactivateCallback(void *priv_data, calldata_t *cd); + }; + } + } +} diff --git a/src/utils/ObsVolumeMeter_Helpers.h b/src/utils/Obs_VolumeMeter_Helpers.h similarity index 95% rename from src/utils/ObsVolumeMeter_Helpers.h rename to src/utils/Obs_VolumeMeter_Helpers.h index e69bfc6f..7af42ac3 100644 --- a/src/utils/ObsVolumeMeter_Helpers.h +++ b/src/utils/Obs_VolumeMeter_Helpers.h @@ -56,7 +56,7 @@ with this program. If not, see r = fmaxf(r, x4_mem[3]); \ } while (false) -float GetSamplePeak(__m128 previousSamples, const float *samples, size_t sampleCount) +static float GetSamplePeak(__m128 previousSamples, const float *samples, size_t sampleCount) { __m128 peak = previousSamples; for (size_t i = 0; (i + 3) < sampleCount; i += 4) { @@ -69,7 +69,7 @@ float GetSamplePeak(__m128 previousSamples, const float *samples, size_t sampleC return ret; } -float GetTruePeak(__m128 previousSamples, const float *samples, size_t sampleCount) +static float GetTruePeak(__m128 previousSamples, const float *samples, size_t sampleCount) { const __m128 m3 = _mm_set_ps(-0.155915f, 0.935489f, 0.233872f, -0.103943f); const __m128 m1 = _mm_set_ps(-0.216236f, 0.756827f, 0.504551f, -0.189207f); diff --git a/src/utils/Platform.cpp b/src/utils/Platform.cpp index 6f01fb26..41becdf4 100644 --- a/src/utils/Platform.cpp +++ b/src/utils/Platform.cpp @@ -111,9 +111,9 @@ void Utils::Platform::SendTrayNotification(QSystemTrayIcon::MessageIcon icon, QS obs_queue_task(OBS_TASK_UI, [](void* param) { void *systemTrayPtr = obs_frontend_get_system_tray(); - auto systemTray = reinterpret_cast(systemTrayPtr); + auto systemTray = static_cast(systemTrayPtr); - auto notification = reinterpret_cast(param); + auto notification = static_cast(param); systemTray->showMessage(notification->title, notification->body, notification->icon); delete notification; }, (void*)notification, false); diff --git a/src/utils/Utils.h b/src/utils/Utils.h index 9a06728c..00a88f3d 100644 --- a/src/utils/Utils.h +++ b/src/utils/Utils.h @@ -22,6 +22,6 @@ with this program. If not, see #include "Crypto.h" #include "Json.h" #include "Obs.h" -#include "ObsVolumeMeter.h" +#include "Obs_VolumeMeter.h" #include "Platform.h" #include "Compat.h" diff --git a/src/websocketserver/WebSocketServer.h b/src/websocketserver/WebSocketServer.h index a8a2feec..9052c793 100644 --- a/src/websocketserver/WebSocketServer.h +++ b/src/websocketserver/WebSocketServer.h @@ -23,6 +23,7 @@ with this program. If not, see #include #include #include +#include #include #include diff --git a/src/websocketserver/WebSocketServer_Protocol.cpp b/src/websocketserver/WebSocketServer_Protocol.cpp index 14c60eec..67c082c3 100644 --- a/src/websocketserver/WebSocketServer_Protocol.cpp +++ b/src/websocketserver/WebSocketServer_Protocol.cpp @@ -233,7 +233,7 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces return; } - uint8_t requestedExecutionType = payloadData["executionType"]; + int8_t requestedExecutionType = payloadData["executionType"]; if (!RequestBatchExecutionType::IsValid(requestedExecutionType) || requestedExecutionType == RequestBatchExecutionType::None) { ret.closeCode = WebSocketCloseCode::InvalidDataFieldValue; ret.closeReason = "Your `executionType` has an invalid value."; @@ -296,7 +296,7 @@ void WebSocketServer::ProcessMessage(SessionPtr session, WebSocketServer::Proces } return; default: ret.closeCode = WebSocketCloseCode::UnknownOpCode; - ret.closeReason = std::string("Unknown OpCode: %s") + std::to_string(opCode); + ret.closeReason = std::string("Unknown OpCode: ") + std::to_string(opCode); return; } }