diff --git a/.editorconfig b/.editorconfig index 9d1d51a1..14b0f7bd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,6 +3,7 @@ insert_final_newline = true [*.{c,cpp,h,hpp}] indent_style = tab +indent_size = 4 [*.{yml,yaml}] indent_style = space diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index ea45e450..00000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,22 +0,0 @@ -## Contributing to obs-websocket - -### Translating obs-websocket to your language -Localization happens on Crowdin: https://crowdin.com/project/obs-websocket - -### Writing code for obs-websocket -#### Coding Guidelines -- Function and variable names: snake_case for C names, CamelCase for C++ names -- Tabs are 8 columns wide -- 80 columns max. - -#### Commit Guidelines -- Commits follow the 50/72 standard: - - 50 characters max for the title - - One empty line after the title - - Description wrapped to 72 columns max per line. -- Commit titles: - - Use present tense - - Prefix the title with a "scope" name - - e.g: "CI: fix wrong behaviour when packaging for OS X" - - Typical scopes: CI, General, Request, Event, Server - - Look at existing commits for more examples diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..69945f88 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +open_collective: obs-websocket +github: Palakis +custom: https://www.paypal.me/stephanelepin diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 8c118039..215d0d54 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,16 +1,20 @@ ##### Issue type -Bug report? Feature request? Other? + + + + ##### Description -*Replace this with a description of the bug encountered or feature requested.* + ##### Steps to reproduce and other useful info -*If it's a bug, please describe the steps to reproduce it and PLEASE include an OBS log file. Otherwise, remove this section.* + ##### Technical information - **Operating System** : - **OBS Studio version** : +- **obs-websocket version** : ##### Development Environment -*If you're trying to compile obs-websocket, please describe your compiler type and version (e.g: GCC 4.7, VC2013, ...), and the CMake settings used. -Remove this section if it doesn't apply to your case.* + + diff --git a/.github/images/obsws_logo.png b/.github/images/obsws_logo.png new file mode 100644 index 00000000..2bcee839 Binary files /dev/null and b/.github/images/obsws_logo.png differ diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..d6fe377d --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,35 @@ + + + + +### Description + + +### Motivation and Context + + + + +### How Has This Been Tested? + +Tested OS(s): + +### Types of changes + + + + + + + + + +### Checklist: + + +- [ ] I have read the [**contributing** document](https://github.com/Palakis/obs-websocket/blob/4.x-current/CONTRIBUTING.md). +- [ ] My code is not on the master branch. +- [ ] The code has been tested. +- [ ] All commit messages are properly formatted and commits squashed where appropriate. +- [ ] I have included updates to all appropriate documentation. + diff --git a/.github/workflows/pr_push.yml b/.github/workflows/pr_push.yml new file mode 100644 index 00000000..d828521d --- /dev/null +++ b/.github/workflows/pr_push.yml @@ -0,0 +1,412 @@ +name: 'CI Multiplatform Build' + +on: + push: + paths-ignore: + - 'docs/**' + branches: + - 4.x-current + pull_request: + paths-ignore: + - '**.md' + branches: + - 4.x-current + +jobs: + windows: + name: 'Windows 32+64bit' + runs-on: [windows-latest] + if: contains(github.event.head_commit.message, '[skip ci]') != true + env: + QT_VERSION: '5.15.2' + WINDOWS_DEPS_VERSION: '2019' + CMAKE_GENERATOR: "Visual Studio 16 2019" + CMAKE_SYSTEM_VERSION: "10.0" + steps: + - name: Add msbuild to PATH + uses: microsoft/setup-msbuild@v1.0.0 + - name: 'Checkout' + uses: actions/checkout@v2 + with: + path: ${{ github.workspace }}/obs-websocket + submodules: 'recursive' + - name: 'Checkout OBS' + uses: actions/checkout@v2 + with: + repository: obsproject/obs-studio + path: ${{ github.workspace }}/obs-studio + submodules: 'recursive' + - name: 'Get OBS-Studio git info' + shell: bash + working-directory: ${{ github.workspace }}/obs-studio + run: | + git fetch --prune --unshallow + echo ::set-env name=OBS_GIT_BRANCH::$(git rev-parse --abbrev-ref HEAD) + echo ::set-env name=OBS_GIT_HASH::$(git rev-parse --short HEAD) + echo ::set-env name=OBS_GIT_TAG::$(git describe --tags --abbrev=0) + - 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 + echo ::set-env name=GIT_BRANCH::${{ github.event.pull_request.head.ref }} + echo ::set-env name=GIT_HASH::$(git rev-parse --short HEAD) + echo ::set-env name=GIT_TAG::$(git describe --tags --abbrev=0) + - name: 'Install prerequisite: QT' + run: | + curl -kLO https://cdn-fastly.obsproject.com/downloads/Qt_${{ env.QT_VERSION }}.7z -f --retry 5 -C - + 7z x Qt_${{ env.QT_VERSION }}.7z -o"${{ github.workspace }}\cmbuild\QT" + - name: 'Install prerequisite: Pre-built OBS dependencies' + 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 32-bit build v${{ env.OBS_GIT_TAG }} from cache' + id: build-cache-obs-32 + uses: actions/cache@v1 + env: + CACHE_NAME: 'build-cache-obs-32' + 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 32-bit' + if: steps.build-cache-obs-32.outputs.cache-hit != 'true' + working-directory: ${{ github.workspace }}/obs-studio + run: | + mkdir .\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 }}\msvc2017" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win32" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES .. + - 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 64-bit build v${{ env.OBS_GIT_TAG }} from cache' + id: build-cache-obs-64 + uses: actions/cache@v1 + env: + CACHE_NAME: 'build-cache-obs-64' + 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 64-bit' + if: steps.build-cache-obs-64.outputs.cache-hit != 'true' + working-directory: ${{ github.workspace }}/obs-studio + run: | + mkdir .\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 }}\msvc2017_64" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win64" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES .. + - name: 'Build OBS-Studio 64-bit' + if: steps.build-cache-obs-64.outputs.cache-hit != 'true' + working-directory: ${{ github.workspace }}/obs-studio + run: | + msbuild /m /p:Configuration=RelWithDebInfo .\build64\libobs\libobs.vcxproj + msbuild /m /p:Configuration=RelWithDebInfo .\build64\UI\obs-frontend-api\obs-frontend-api.vcxproj + - name: 'Configure obs-websocket 64-bit' + working-directory: ${{ github.workspace }}/obs-websocket + run: | + mkdir .\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 }}\msvc2017_64" -DLibObs_DIR="${{ github.workspace }}\obs-studio\build64\libobs" -DLIBOBS_INCLUDE_DIR="${{ github.workspace }}\obs-studio\libobs" -DLIBOBS_LIB="${{ github.workspace }}\obs-studio\build64\libobs\RelWithDebInfo\obs.lib" -DOBS_FRONTEND_LIB="${{ github.workspace }}\obs-studio\build64\UI\obs-frontend-api\RelWithDebInfo\obs-frontend-api.lib" .. + - name: 'Configure obs-websocket 32-bit' + working-directory: ${{ github.workspace }}/obs-websocket + run: | + mkdir .\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 }}\msvc2017" -DLibObs_DIR="${{ github.workspace }}\obs-studio\build32\libobs" -DLIBOBS_INCLUDE_DIR="${{ github.workspace }}\obs-studio\libobs" -DLIBOBS_LIB="${{ github.workspace }}\obs-studio\build32\libobs\RelWithDebInfo\obs.lib" -DOBS_FRONTEND_LIB="${{ github.workspace }}\obs-studio\build32\UI\obs-frontend-api\RelWithDebInfo\obs-frontend-api.lib" .. + - name: 'Build obs-websocket 64-bit' + working-directory: ${{ github.workspace }}/obs-websocket + run: msbuild /m /p:Configuration=RelWithDebInfo .\build64\obs-websocket.sln + - name: 'Build obs-websocket 32-bit' + working-directory: ${{ github.workspace }}/obs-websocket + run: msbuild /m /p:Configuration=RelWithDebInfo .\build32\obs-websocket.sln + - name: 'Set PR artifact filename' + shell: bash + run: | + FILENAME="obs-websocket-${{ env.GIT_HASH }}-Windows" + echo "::set-env name=FILENAME::$FILENAME" + - name: 'Package obs-websocket' + working-directory: ${{ github.workspace }}/obs-websocket + run: | + mkdir package + cd package + 7z a "${{ env.WIN_FILENAME }}.zip" "..\release\*" + iscc ..\installer\installer.iss /O. /F"${{ env.WIN_FILENAME }}-Installer" + - name: 'Publish ${{ env.WIN_FILENAME }}.zip' + if: success() + uses: actions/upload-artifact@v2-preview + with: + name: '${{ env.GIT_HASH }}-Windows' + path: ${{ github.workspace }}/obs-websocket/package/*.zip + - name: 'Publish ${{ env.WIN_FILENAME }}-Installer.exe' + if: success() + uses: actions/upload-artifact@v2-preview + with: + name: '${{ env.GIT_HASH }}-Windows-Installer' + path: ${{ github.workspace }}/obs-websocket/package/*.exe + ubuntu64: + name: "Linux/Ubuntu 64-bit" + runs-on: [ubuntu-latest] + if: contains(github.event.head_commit.message, '[skip ci]') != true + steps: + - name: 'Checkout' + uses: actions/checkout@v2 + with: + path: ${{ github.workspace }}/obs-websocket + submodules: 'recursive' + - name: 'Checkout OBS' + uses: actions/checkout@v2 + with: + repository: obsproject/obs-studio + path: ${{ github.workspace }}/obs-studio + submodules: 'recursive' + - name: 'Get OBS-Studio git info' + shell: bash + working-directory: ${{ github.workspace }}/obs-studio + run: | + git fetch --prune --unshallow + echo ::set-env name=OBS_GIT_BRANCH::$(git rev-parse --abbrev-ref HEAD) + echo ::set-env name=OBS_GIT_HASH::$(git rev-parse --short HEAD) + echo ::set-env name=OBS_GIT_TAG::$(git describe --tags --abbrev=0) + - 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' + working-directory: ${{ github.workspace }}/obs-websocket + run: | + git fetch --prune --unshallow + echo ::set-env name=GIT_BRANCH::${{ github.event.pull_request.head.ref }} + echo ::set-env name=GIT_HASH::$(git rev-parse --short HEAD) + echo ::set-env name=GIT_TAG::$(git describe --tags --abbrev=0) + - name: 'Install prerequisites (Apt)' + shell: bash + run: | + sudo dpkg --add-architecture amd64 + sudo apt-get -qq update + sudo apt-get install -y \ + build-essential \ + checkinstall \ + cmake \ + libasound2-dev \ + libavcodec-dev \ + libavdevice-dev \ + libavfilter-dev \ + libavformat-dev \ + libavutil-dev \ + libcurl4-openssl-dev \ + libfdk-aac-dev \ + libfontconfig-dev \ + libfreetype6-dev \ + libgl1-mesa-dev \ + libjack-jackd2-dev \ + libjansson-dev \ + libluajit-5.1-dev \ + libpulse-dev \ + libqt5x11extras5-dev \ + libspeexdsp-dev \ + libswresample-dev \ + libswscale-dev \ + libudev-dev \ + libv4l-dev \ + libva-dev \ + libvlc-dev \ + libx11-dev \ + libx264-dev \ + libxcb-randr0-dev \ + libxcb-shm0-dev \ + libxcb-xinerama0-dev \ + libxcomposite-dev \ + libxinerama-dev \ + libmbedtls-dev \ + pkg-config \ + python3-dev \ + qtbase5-dev \ + libqt5svg5-dev \ + swig + - 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' + working-directory: ${{ github.workspace }}/obs-studio + shell: bash + run: | + set -e + cd ./build + make -j4 libobs obs-frontend-api + - name: 'Install OBS-Studio' + working-directory: ${{ github.workspace }}/obs-studio + shell: bash + run: | + cd ./build + sudo cp ./libobs/libobs.so /usr/lib + sudo cp ./UI/obs-frontend-api/libobs-frontend-api.so /usr/lib + sudo mkdir -p /usr/include/obs + sudo cp ../UI/obs-frontend-api/obs-frontend-api.h /usr/include/obs/obs-frontend-api.h + - name: 'Configure obs-websocket' + working-directory: ${{ github.workspace }}/obs-websocket + shell: bash + run: | + mkdir ./build + cd ./build + cmake -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DCMAKE_INSTALL_PREFIX=/usr .. + - name: 'Build obs-websocket' + working-directory: ${{ github.workspace }}/obs-websocket + shell: bash + run: | + set -e + cd ./build + make -j4 + - name: 'Set PR artifact filename' + shell: bash + run: | + FILENAME="obs-websocket-1-${{ env.GIT_HASH }}-1_amd64.deb" + echo "::set-env name=FILENAME::$FILENAME" + - name: 'Package ${{ env.FILENAME }}' + if: success() + working-directory: ${{ github.workspace }}/obs-websocket + shell: bash + run: | + VERSION="1-${{ env.GIT_HASH }}-git" + cd ./build + sudo checkinstall -y --type=debian --fstrans=no -nodoc \ + --backup=no --deldoc=yes --install=no --pkgname=obs-websocket --pkgversion=$VERSION \ + --pkglicense="GPLv2.0" --maintainer="${{ github.event.pusher.email }}" --pkggroup="video" \ + --pkgsource="${{ github.event.repository.html_url }}" \ + --requires="obs-studio,libqt5core5a,libqt5widgets5,qt5-image-formats-plugins" \ + --pakdir="../package" + sudo chmod ao+r ../package/* + cd - + - name: 'Publish ${{ env.FILENAME }}' + if: success() + uses: actions/upload-artifact@v2-preview + with: + name: '${{ env.GIT_HASH }}-linux' + path: '${{ github.workspace }}/obs-websocket/package/*.deb' + macos64: + name: "macOS 64-bit" + runs-on: [macos-latest] + if: contains(github.event.head_commit.message, '[skip ci]') != true + env: + MACOS_DEPS_VERSION: '2020-04-18' + QT_VERSION: '5.14.1' + steps: + - name: 'Checkout' + uses: actions/checkout@v2 + with: + path: ${{ github.workspace }}/obs-websocket + submodules: 'recursive' + - name: 'Checkout OBS' + uses: actions/checkout@v2 + with: + repository: obsproject/obs-studio + path: ${{ github.workspace }}/obs-studio + submodules: 'recursive' + - name: 'Get OBS-Studio git info' + shell: bash + working-directory: ${{ github.workspace }}/obs-studio + run: | + git fetch --prune --unshallow + echo ::set-env name=OBS_GIT_BRANCH::$(git rev-parse --abbrev-ref HEAD) + echo ::set-env name=OBS_GIT_HASH::$(git rev-parse --short HEAD) + echo ::set-env name=OBS_GIT_TAG::$(git describe --tags --abbrev=0) + - 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' + working-directory: ${{ github.workspace }}/obs-websocket + run: | + git fetch --prune --unshallow + echo ::set-env name=GIT_BRANCH::${{ github.event.pull_request.head.ref }} + echo ::set-env name=GIT_HASH::$(git rev-parse --short HEAD) + echo ::set-env name=GIT_TAG::$(git describe --tags --abbrev=0) + - name: 'Install prerequisites (Homebrew)' + shell: bash + run: | + brew bundle --file ${{ github.workspace }}/obs-websocket/CI/macos/Brewfile + - name: 'Install prerequisite: Pre-built OBS dependencies' + shell: bash + run: | + curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.MACOS_DEPS_VERSION }}/osx-deps-${{ env.MACOS_DEPS_VERSION }}.tar.gz + tar -xf ${{ github.workspace }}/osx-deps-${{ env.MACOS_DEPS_VERSION }}.tar.gz -C "/tmp" + - name: 'Configure OBS Studio' + shell: bash + working-directory: ${{ github.workspace }}/obs-studio + run: | + mkdir build + cd build + cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 -DENABLE_SCRIPTING=NO -DDepsPath=/tmp/obsdeps -DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt/${{ env.QT_VERSION }}/lib/cmake .. + - name: 'Build OBS Studio libraries' + working-directory: ${{ github.workspace }}/obs-studio + shell: bash + run: | + set -e + cd ./build + make -j4 libobs obs-frontend-api + - name: 'Configure obs-websocket' + working-directory: ${{ github.workspace }}/obs-websocket + shell: bash + run: | + mkdir build + cd build + cmake -DQTDIR=/usr/local/Cellar/qt/${{ env.QT_VERSION }} -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 + shell: bash + run: | + set -e + cd ./build + make -j4 + - name: 'Install prerequisite: Packages app' + if: success() + shell: bash + run: | + curl -L -O https://s3-us-west-2.amazonaws.com/obs-nightly/Packages.pkg + sudo installer -pkg ${{ github.workspace }}/Packages.pkg -target / + - name: 'Set PR artifact filename' + shell: bash + run: | + FILENAME_UNSIGNED="obs-websocket-${{ env.GIT_HASH }}-macOS-Unsigned.pkg" + echo "::set-env name=FILENAME_UNSIGNED::$FILENAME_UNSIGNED" + - name: 'Fix linked dynamic library paths' + if: success() + working-directory: ${{ github.workspace }}/obs-websocket + shell: bash + run: | + install_name_tool -change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets ./build/obs-websocket.so + install_name_tool -change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui ./build/obs-websocket.so + install_name_tool -change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore ./build/obs-websocket.so + echo "Dependencies for obs-websocket" + otool -L ./build/obs-websocket.so + - name: 'Package ${{ env.FILENAME }}' + if: success() + working-directory: ./obs-websocket + shell: bash + run: | + packagesbuild ./CI/macos/obs-websocket.pkgproj + mv ./release/obs-websocket.pkg ./release/${{ env.FILENAME_UNSIGNED }} + - name: 'Publish ${{ env.FILENAME_UNSIGNED }} artifact' + if: success() + uses: actions/upload-artifact@v2-preview + with: + name: '${{ env.GIT_HASH }}-macOS' + path: ${{ github.workspace }}/obs-websocket/release/*.pkg diff --git a/.github/workflows/tag_release.yml b/.github/workflows/tag_release.yml new file mode 100644 index 00000000..51d6813a --- /dev/null +++ b/.github/workflows/tag_release.yml @@ -0,0 +1,484 @@ +name: 'CI Multiplatform Release' + +on: + push: + paths-ignore: + - 'docs/**' + tags: + - '[45].[0-9]+.[0-9]+' + +jobs: + windows: + name: 'Windows 32+64bit' + runs-on: [windows-latest] + env: + QT_VERSION: '5.15.2' + WINDOWS_DEPS_VERSION: '2019' + CMAKE_GENERATOR: "Visual Studio 16 2019" + CMAKE_SYSTEM_VERSION: "10.0" + steps: + - name: 'Add msbuild to PATH' + uses: microsoft/setup-msbuild@v1.0.0 + - name: 'Checkout' + uses: actions/checkout@v2 + with: + path: ${{ github.workspace }}/obs-websocket + submodules: 'recursive' + - name: 'Checkout OBS' + uses: actions/checkout@v2 + with: + repository: obsproject/obs-studio + path: ${{ github.workspace }}/obs-studio + submodules: 'recursive' + - name: 'Get OBS-Studio git info' + shell: bash + working-directory: ${{ github.workspace }}/obs-studio + run: | + git fetch --prune --unshallow + echo ::set-env name=OBS_GIT_BRANCH::$(git rev-parse --abbrev-ref HEAD) + echo ::set-env name=OBS_GIT_HASH::$(git rev-parse --short HEAD) + echo ::set-env name=OBS_GIT_TAG::$(git describe --tags --abbrev=0) + - 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 + echo ::set-env name=GIT_BRANCH::${{ github.event.pull_request.head.ref }} + echo ::set-env name=GIT_HASH::$(git rev-parse --short HEAD) + echo ::set-env name=GIT_TAG::$(git describe --tags --abbrev=0) + - name: 'Install prerequisite: QT' + run: | + curl -kLO https://cdn-fastly.obsproject.com/downloads/Qt_${{ env.QT_VERSION }}.7z -f --retry 5 -C - + 7z x Qt_${{ env.QT_VERSION }}.7z -o"${{ github.workspace }}\cmbuild\QT" + - name: 'Install prerequisite: Pre-built OBS dependencies' + 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 32-bit build v${{ env.OBS_GIT_TAG }} from cache' + id: build-cache-obs-32 + uses: actions/cache@v1 + env: + CACHE_NAME: 'build-cache-obs-32' + 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 32-bit' + if: steps.build-cache-obs-32.outputs.cache-hit != 'true' + working-directory: ${{ github.workspace }}/obs-studio + run: | + mkdir .\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 }}\msvc2017" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win32" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES .. + - 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 64-bit build v${{ env.OBS_GIT_TAG }} from cache' + id: build-cache-obs-64 + uses: actions/cache@v1 + env: + CACHE_NAME: 'build-cache-obs-64' + 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 64-bit' + if: steps.build-cache-obs-64.outputs.cache-hit != 'true' + working-directory: ${{ github.workspace }}/obs-studio + run: | + mkdir .\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 }}\msvc2017_64" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win64" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES .. + - name: 'Build OBS-Studio 64-bit' + if: steps.build-cache-obs-64.outputs.cache-hit != 'true' + working-directory: ${{ github.workspace }}/obs-studio + run: | + msbuild /m /p:Configuration=RelWithDebInfo .\build64\libobs\libobs.vcxproj + msbuild /m /p:Configuration=RelWithDebInfo .\build64\UI\obs-frontend-api\obs-frontend-api.vcxproj + - name: 'Configure obs-websocket 64-bit' + working-directory: ${{ github.workspace }}/obs-websocket + run: | + mkdir .\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 }}\msvc2017_64" -DLibObs_DIR="${{ github.workspace }}\obs-studio\build64\libobs" -DLIBOBS_INCLUDE_DIR="${{ github.workspace }}\obs-studio\libobs" -DLIBOBS_LIB="${{ github.workspace }}\obs-studio\build64\libobs\RelWithDebInfo\obs.lib" -DOBS_FRONTEND_LIB="${{ github.workspace }}\obs-studio\build64\UI\obs-frontend-api\RelWithDebInfo\obs-frontend-api.lib" .. + - name: 'Configure obs-websocket 32-bit' + working-directory: ${{ github.workspace }}/obs-websocket + run: | + mkdir .\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 }}\msvc2017" -DLibObs_DIR="${{ github.workspace }}\obs-studio\build32\libobs" -DLIBOBS_INCLUDE_DIR="${{ github.workspace }}\obs-studio\libobs" -DLIBOBS_LIB="${{ github.workspace }}\obs-studio\build32\libobs\RelWithDebInfo\obs.lib" -DOBS_FRONTEND_LIB="${{ github.workspace }}\obs-studio\build32\UI\obs-frontend-api\RelWithDebInfo\obs-frontend-api.lib" .. + - name: 'Build obs-websocket 64-bit' + working-directory: ${{ github.workspace }}/obs-websocket + run: msbuild /m /p:Configuration=RelWithDebInfo .\build64\obs-websocket.sln + - name: 'Build obs-websocket 32-bit' + working-directory: ${{ github.workspace }}/obs-websocket + run: msbuild /m /p:Configuration=RelWithDebInfo .\build32\obs-websocket.sln + - name: 'Set release filename' + shell: bash + run: | + FILENAME="obs-websocket-${{ env.GIT_TAG }}-Windows" + echo "::set-env name=WIN_FILENAME::$FILENAME" + - name: 'Package obs-websocket' + working-directory: ${{ github.workspace }}/obs-websocket + run: | + mkdir package + cd package + 7z a "${{ env.WIN_FILENAME }}.zip" "..\release\*" + iscc ..\installer\installer.iss /O. /F"${{ env.WIN_FILENAME }}-Installer" + - name: 'Publish ${{ env.WIN_FILENAME }}.zip' + if: success() + uses: actions/upload-artifact@v2-preview + with: + name: '${{ env.GIT_TAG }}-Windows' + path: ${{ github.workspace }}/obs-websocket/package/*.zip + - name: 'Publish ${{ env.WIN_FILENAME }}-Installer.exe' + if: success() + uses: actions/upload-artifact@v2-preview + with: + name: '${{ env.GIT_TAG }}-Windows-Installer' + path: ${{ github.workspace }}/obs-websocket/package/*.exe + ubuntu64: + name: "Linux/Ubuntu 64-bit" + runs-on: [ubuntu-latest] + steps: + - name: 'Checkout' + uses: actions/checkout@v2 + with: + path: ${{ github.workspace }}/obs-websocket + submodules: 'recursive' + - name: 'Checkout OBS' + uses: actions/checkout@v2 + with: + repository: obsproject/obs-studio + path: ${{ github.workspace }}/obs-studio + submodules: 'recursive' + - name: 'Get OBS-Studio git info' + shell: bash + working-directory: ${{ github.workspace }}/obs-studio + run: | + git fetch --prune --unshallow + echo ::set-env name=OBS_GIT_BRANCH::$(git rev-parse --abbrev-ref HEAD) + echo ::set-env name=OBS_GIT_HASH::$(git rev-parse --short HEAD) + echo ::set-env name=OBS_GIT_TAG::$(git describe --tags --abbrev=0) + - 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' + working-directory: ${{ github.workspace }}/obs-websocket + run: | + git fetch --prune --unshallow + echo ::set-env name=GIT_BRANCH::${{ github.event.pull_request.head.ref }} + echo ::set-env name=GIT_HASH::$(git rev-parse --short HEAD) + echo ::set-env name=GIT_TAG::$(git describe --tags --abbrev=0) + - name: 'Install prerequisites (Apt)' + shell: bash + run: | + sudo dpkg --add-architecture amd64 + sudo apt-get -qq update + sudo apt-get install -y \ + build-essential \ + checkinstall \ + cmake \ + libasound2-dev \ + libavcodec-dev \ + libavdevice-dev \ + libavfilter-dev \ + libavformat-dev \ + libavutil-dev \ + libcurl4-openssl-dev \ + libfdk-aac-dev \ + libfontconfig-dev \ + libfreetype6-dev \ + libgl1-mesa-dev \ + libjack-jackd2-dev \ + libjansson-dev \ + libluajit-5.1-dev \ + libpulse-dev \ + libqt5x11extras5-dev \ + libspeexdsp-dev \ + libswresample-dev \ + libswscale-dev \ + libudev-dev \ + libv4l-dev \ + libva-dev \ + libvlc-dev \ + libx11-dev \ + libx264-dev \ + libxcb-randr0-dev \ + libxcb-shm0-dev \ + libxcb-xinerama0-dev \ + libxcomposite-dev \ + libxinerama-dev \ + libmbedtls-dev \ + pkg-config \ + python3-dev \ + qtbase5-dev \ + libqt5svg5-dev \ + swig + - 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' + working-directory: ${{ github.workspace }}/obs-studio + shell: bash + run: | + set -e + cd ./build + make -j4 libobs obs-frontend-api + - name: 'Install OBS-Studio' + working-directory: ${{ github.workspace }}/obs-studio + shell: bash + run: | + cd ./build + sudo cp ./libobs/libobs.so /usr/lib + sudo cp ./UI/obs-frontend-api/libobs-frontend-api.so /usr/lib + sudo mkdir -p /usr/include/obs + sudo cp ../UI/obs-frontend-api/obs-frontend-api.h /usr/include/obs/obs-frontend-api.h + - name: 'Configure obs-websocket' + working-directory: ${{ github.workspace }}/obs-websocket + shell: bash + run: | + mkdir ./build + cd ./build + cmake -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DCMAKE_INSTALL_PREFIX=/usr .. + - name: 'Build obs-websocket' + working-directory: ${{ github.workspace }}/obs-websocket + shell: bash + run: | + set -e + cd ./build + make -j4 + - name: 'Set release filename' + shell: bash + run: | + FILENAME="obs-websocket-${{ env.GIT_TAG }}-1_amd64.deb" + echo "::set-env name=LINUX_FILENAME::$FILENAME" + - name: 'Package ${{ env.LINUX_FILENAME }}' + if: success() + working-directory: ${{ github.workspace }}/obs-websocket + shell: bash + run: | + VERSION="${{ env.GIT_TAG }}" + cd ./build + sudo checkinstall -y --type=debian --fstrans=no -nodoc \ + --backup=no --deldoc=yes --install=no --pkgname=obs-websocket --pkgversion=$VERSION \ + --pkglicense="GPLv2.0" --maintainer="${{ github.event.pusher.email }}" --pkggroup="video" \ + --pkgsource="${{ github.event.repository.html_url }}" \ + --requires="obs-studio,libqt5core5a,libqt5widgets5,qt5-image-formats-plugins" \ + --pakdir="../package" + sudo chmod ao+r ../package/* + cd - + - name: 'Publish ${{ env.LINUX_FILENAME }}' + if: success() + uses: actions/upload-artifact@v2-preview + with: + name: '${{ env.GIT_TAG }}-linux' + path: '${{ github.workspace }}/obs-websocket/package/*.deb' + macos64: + name: "macOS 64-bit" + runs-on: [macos-latest] + env: + MACOS_DEPS_VERSION: '2020-04-18' + QT_VERSION: '5.14.1' + steps: + - name: 'Checkout' + uses: actions/checkout@v2 + with: + path: ${{ github.workspace }}/obs-websocket + submodules: 'recursive' + - name: 'Checkout OBS' + uses: actions/checkout@v2 + with: + repository: obsproject/obs-studio + path: ${{ github.workspace }}/obs-studio + submodules: 'recursive' + - name: 'Get OBS-Studio git info' + shell: bash + working-directory: ${{ github.workspace }}/obs-studio + run: | + git fetch --prune --unshallow + echo ::set-env name=OBS_GIT_BRANCH::$(git rev-parse --abbrev-ref HEAD) + echo ::set-env name=OBS_GIT_HASH::$(git rev-parse --short HEAD) + echo ::set-env name=OBS_GIT_TAG::$(git describe --tags --abbrev=0) + - 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' + working-directory: ${{ github.workspace }}/obs-websocket + run: | + git fetch --prune --unshallow + echo ::set-env name=GIT_BRANCH::${{ github.event.pull_request.head.ref }} + echo ::set-env name=GIT_HASH::$(git rev-parse --short HEAD) + echo ::set-env name=GIT_TAG::$(git describe --tags --abbrev=0) + - name: 'Install prerequisites (Homebrew)' + shell: bash + run: | + brew bundle --file ${{ github.workspace }}/obs-websocket/CI/macos/Brewfile + - name: 'Install prerequisite: Pre-built OBS dependencies' + shell: bash + run: | + curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.MACOS_DEPS_VERSION }}/osx-deps-${{ env.MACOS_DEPS_VERSION }}.tar.gz + tar -xf ${{ github.workspace }}/osx-deps-${{ env.MACOS_DEPS_VERSION }}.tar.gz -C "/tmp" + - name: 'Configure OBS Studio' + shell: bash + working-directory: ${{ github.workspace }}/obs-studio + run: | + mkdir build + cd build + cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 -DENABLE_SCRIPTING=NO -DDepsPath=/tmp/obsdeps -DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt/${{ env.QT_VERSION }}/lib/cmake .. + - name: 'Build OBS Studio libraries' + working-directory: ${{ github.workspace }}/obs-studio + shell: bash + run: | + set -e + cd ./build + make -j4 libobs obs-frontend-api + - name: 'Configure obs-websocket' + working-directory: ${{ github.workspace }}/obs-websocket + shell: bash + run: | + mkdir build + cd build + cmake -DQTDIR=/usr/local/Cellar/qt/${{ env.QT_VERSION }} -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 + shell: bash + run: | + set -e + cd ./build + make -j4 + - name: 'Install prerequisite: Packages app' + if: success() + shell: bash + run: | + curl -L -O https://s3-us-west-2.amazonaws.com/obs-nightly/Packages.pkg + sudo installer -pkg ${{ github.workspace }}/Packages.pkg -target / + - name: 'Set release filename' + if: success() && startsWith(github.ref, 'refs/tags') + shell: bash + run: | + FILENAME_UNSIGNED="obs-websocket-${{ env.GIT_TAG }}-macOS-Unsigned.pkg" + FILENAME="obs-websocket-${{ env.GIT_TAG }}-macOS.pkg" + echo "::set-env name=MAC_FILENAME_UNSIGNED::$FILENAME_UNSIGNED" + echo "::set-env name=MAC_FILENAME::$FILENAME" + - name: 'Fix linked dynamic library paths' + if: success() + working-directory: ${{ github.workspace }}/obs-websocket + shell: bash + run: | + install_name_tool -change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets ./build/obs-websocket.so + install_name_tool -change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui ./build/obs-websocket.so + install_name_tool -change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore ./build/obs-websocket.so + echo "Dependencies for obs-websocket" + otool -L ./build/obs-websocket.so + - name: 'Install Apple Developer Certificate' + if: success() + uses: apple-actions/import-codesign-certs@253ddeeac23f2bdad1646faac5c8c2832e800071 + with: + p12-file-base64: ${{ secrets.MACOS_CERT_CODESIGN }} + p12-password: ${{ secrets.MACOS_CERT_PASS }} + - name: 'Code signing' + if: success() + working-directory: ./obs-websocket + shell: bash + run: | + set -e + codesign --sign "${{ secrets.MACOS_IDENT_CODESIGN }}" ./build/obs-websocket.so + packagesbuild ./CI/macos/obs-websocket.pkgproj + mv ./release/obs-websocket.pkg ./release/${{ env.MAC_FILENAME_UNSIGNED }} + productsign --sign "${{ secrets.MACOS_IDENT_INSTALLER }}" ./release/${{ env.MAC_FILENAME_UNSIGNED }} ./release/${{ env.MAC_FILENAME }} + rm ./release/${{ env.MAC_FILENAME_UNSIGNED }} + - name: 'Notarization' + if: success() + working-directory: ./obs-websocket + shell: bash + run: | + set -e + xcrun altool --store-password-in-keychain-item "AC_PASSWORD" -u "${{ secrets.MACOS_IDENT_USER }}" -p "${{ secrets.MACOS_IDENT_PASS }}" + xcnotary precheck ./release/${{ env.MAC_FILENAME }} + if [ "$?" -eq 0 ]; then xcnotary notarize ./release/${{ env.MAC_FILENAME }} --developer-account "${{ secrets.MACOS_IDENT_USER }}" --developer-password-keychain-item "AC_PASSWORD" --provider "${{ secrets.MACOS_IDENT_PROVIDER }}"; fi + - name: 'Publish ${{ env.MAC_FILENAME }} artifact' + if: success() && startsWith(github.ref, 'refs/tags') + uses: actions/upload-artifact@v2-preview + with: + name: '${{ env.GIT_TAG }}-macOS' + path: ${{ github.workspace }}/obs-websocket/release/*.pkg + make-release: + name: 'Create and upload release' + runs-on: [ubuntu-latest] + needs: [windows, ubuntu64, macos64] + steps: + - name: 'Get the version' + shell: bash + id: get_version + run: | + echo ::set-env name=TAG_VERSION::${GITHUB_REF/refs\/tags\//} + - name: 'Create Release' + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ env.TAG_VERSION }} + release_name: obs-websocket ${{ env.TAG_VERSION }} + draft: false + prerelease: false + - name: 'Download release artifacts' + uses: actions/download-artifact@v2-preview + - name: 'Upload Windows .zip artifact to release' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ${{ github.workspace }}/${{ env.TAG_VERSION }}-Windows/obs-websocket-${{ env.TAG_VERSION }}-Windows.zip + asset_name: obs-websocket-${{ env.TAG_VERSION }}-Windows.zip + asset_content_type: application/zip + - name: 'Upload Windows .exe artifact to release' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ${{ github.workspace }}/${{ env.TAG_VERSION }}-Windows-Installer/obs-websocket-${{ env.TAG_VERSION }}-Windows-Installer.exe + asset_name: obs-websocket-${{ env.TAG_VERSION }}-Windows-Installer.exe + asset_content_type: application/zip + - name: 'Upload Linux artifact to release' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ${{ github.workspace }}/${{ env.TAG_VERSION }}-linux/obs-websocket_${{ env.TAG_VERSION }}-1_amd64.deb + asset_name: obs-websocket-${{ env.TAG_VERSION }}-1_amd64.deb + asset_content_type: application/octet-stream + - name: 'Upload macOS artifact to release' + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ${{ github.workspace }}/${{ env.TAG_VERSION }}-macOS/obs-websocket-${{ env.TAG_VERSION }}-macOS.pkg + asset_name: obs-websocket-${{ env.TAG_VERSION }}-macOS.pkg + asset_content_type: application/octet-stream diff --git a/BUILDING.md b/BUILDING.md index 83f93a99..f3a5ca54 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -2,7 +2,7 @@ ## Prerequisites -You'll need [Qt 5.10.x](https://download.qt.io/official_releases/qt/5.10/), +You'll need [Qt 5.15.2](https://download.qt.io/official_releases/qt/5.15/5.15.2/), [CMake](https://cmake.org/download/) and a working [OBS Studio development environment](https://obsproject.com/wiki/install-instructions) installed on your computer. @@ -24,17 +24,23 @@ sudo apt-get install libboost-all-dev git clone --recursive https://github.com/Palakis/obs-websocket.git cd obs-websocket mkdir build && cd build -cmake -DLIBOBS_INCLUDE_DIR="" -DCMAKE_INSTALL_PREFIX=/usr .. +cmake -DLIBOBS_INCLUDE_DIR="" -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=true .. make -j4 sudo make install ``` +On other linux OS's, use this cmake command instead: + +```shell +cmake -DLIBOBS_INCLUDE_DIR="" -DCMAKE_INSTALL_PREFIX=/usr .. +``` + ## OS X As a prerequisite, you will need Xcode for your current OSX version, the Xcode command line tools, and [Homebrew](https://brew.sh/). Homebrew's setup will guide you in getting your system set up, you should be good to go once Homebrew is successfully up and running. -Use of the Travis macOS CI scripts is recommended. Please note that these +Use of the macOS CI scripts is recommended. Please note that these scripts install new software and can change several settings on your system. An existing obs-studio development environment is not required, as `install-build-obs-macos.sh` will install it for you. If you already have a @@ -47,16 +53,14 @@ look for issues or specificities. ```shell git clone --recursive https://github.com/Palakis/obs-websocket.git cd obs-websocket -./CI/install-dependencies-macos.sh -./CI/install-build-obs-macos.sh -./CI/build-macos.sh -./CI/package-macos.sh +./CI/macos/install-dependencies-macos.sh +./CI/macos/install-build-obs-macos.sh +./CI/macos/build-plugin-macos.sh +./CI/macos/package-plugin-macos.sh ``` This will result in a ready-to-use `obs-websocket.pkg` installer in the `release` subfolder. ## Automated Builds -- Windows: [![Automated Build status for Windows](https://ci.appveyor.com/api/projects/status/github/Palakis/obs-websocket)](https://ci.appveyor.com/project/Palakis/obs-websocket/history) -- Linux: [![Automated Build status for Linux](https://travis-ci.org/Palakis/obs-websocket.svg?branch=master)](https://travis-ci.org/Palakis/obs-websocket) -- macOS: [![Automated Build status for macOS](https://img.shields.io/azure-devops/build/Palakis/obs-websocket/Palakis.obs-websocket.svg)](https://dev.azure.com/Palakis/obs-websocket/_build) +[![Build Status](https://dev.azure.com/Palakis/obs-websocket/_apis/build/status/Palakis.obs-websocket?branchName=4.x-current)](https://dev.azure.com/Palakis/obs-websocket/_build/latest?definitionId=2&branchName=4.x-current) diff --git a/CI/build-ubuntu.sh b/CI/build-ubuntu.sh deleted file mode 100755 index b19158ae..00000000 --- a/CI/build-ubuntu.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh -set -ex - -mkdir build && cd build -cmake -DCMAKE_INSTALL_PREFIX=/usr .. -make -j4 diff --git a/CI/checkout-cmake-obs-windows.cmd b/CI/checkout-cmake-obs-windows.cmd deleted file mode 100644 index 0fc6c12c..00000000 --- a/CI/checkout-cmake-obs-windows.cmd +++ /dev/null @@ -1,137 +0,0 @@ -@echo off -SETLOCAL EnableDelayedExpansion - -REM Check if obs-studio build exists. -REM If the obs-studio directory does exist, check if the last OBS tag built -REM matches the latest OBS tag. -REM If the tags match, do not build obs-studio. -REM If the tags do not match, build obs-studio. -REM If the obs-studio directory doesn't exist, build obs-studio. -echo Checking for obs-studio build... - -set OBSLatestTagPrePull=0 -set OBSLatestTagPostPull=0 -echo Latest tag pre-pull: %OBSLatestTagPrePull% -echo Latest tag post-pull: %OBSLatestTagPostPull% - -REM Set up the build flag as undefined. -set "BuildOBS=" - -REM Check the last tag successfully built by CI. -if exist "%OBSPath%\obs-studio-last-tag-built.txt" ( - set /p OBSLastTagBuilt=<"%OBSPath%\obs-studio-last-tag-built.txt" -) else ( - set OBSLastTagBuilt=0 -) - -REM If obs-studio directory exists, run git pull and get the latest tag number. -if exist %OBSPath% ( - echo obs-studio directory exists - echo Updating tag info - cd /D %OBSPath% - git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBSPath%\latest-obs-studio-tag-pre-pull.txt" - set /p OBSLatestTagPrePull=<"%OBSPath%\latest-obs-studio-tag-pre-pull.txt" - git checkout master - git pull - git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBSPath%\latest-obs-studio-tag-post-pull.txt" - set /p OBSLatestTagPostPull=<"%OBSPath%\latest-obs-studio-tag-post-pull.txt" - set /p OBSLatestTag=<"%OBSPath%\latest-obs-studio-tag-post-pull.txt" - echo %OBSLatestTagPostPull%> "%OBSPath%\latest-obs-studio-tag.txt" -) - -REM Check the obs-studio tags for mismatches. -REM If a new tag was pulled, set the build flag. -if not %OBSLatestTagPrePull%==%OBSLatestTagPostPull% ( - echo Latest tag pre-pull: %OBSLatestTagPrePull% - echo Latest tag post-pull: %OBSLatestTagPostPull% - echo Tags do not match. Need to rebuild OBS. - set BuildOBS=true -) - -REM If the latest git tag doesn't match the last built tag, set the build flag. -if not %OBSLatestTagPostPull%==%OBSLastTagBuilt% ( - echo Last built OBS tag: %OBSLastTagBuilt% - echo Latest tag post-pull: %OBSLatestTagPostPull% - echo Tags do not match. Need to rebuild OBS. - set BuildOBS=true -) - -REM If obs-studio directory does not exist, clone the git repo, get the latest -REM tag number, and set the build flag. -if not exist %OBSPath% ( - echo obs-studio directory does not exist - git clone https://github.com/obsproject/obs-studio %OBSPath% - cd /D %OBSPath%\ - git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBSPath%\obs-studio-latest-tag.txt" - set /p OBSLatestTag=<"%OBSPath%\obs-studio-latest-tag.txt" - set BuildOBS=true -) - -REM If the needed obs-studio libs for this build_config do not exist, -REM set the build flag. -if not exist %OBSPath%\build32\libobs\%build_config%\obs.lib ( - echo obs-studio\build32\libobs\%build_config%\obs.lib does not exist - set BuildOBS=true -) -if not exist %OBSPath%\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib ( - echo obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib does not exist - set BuildOBS=true -) - -REM Some debug info -echo: -echo Latest tag pre-pull: %OBSLatestTagPrePull% -echo Latest tag post-pull: %OBSLatestTagPostPull% -echo Latest tag: %OBSLatestTag% -echo Last built OBS tag: %OBSLastTagBuilt% - -if defined BuildOBS ( - echo BuildOBS: true -) else ( - echo BuildOBS: false -) -echo: - -REM If the build flag is set, build obs-studio. -if defined BuildOBS ( - echo Building obs-studio... - cd /D %OBSPath% - echo git checkout %OBSLatestTag% - git checkout %OBSLatestTag% - echo: - - echo Removing previous build dirs... - if exist build32 rmdir /s /q "%OBSPath%\build32" - if exist build64 rmdir /s /q "%OBSPath%\build64" - - echo Making new build dirs... - mkdir build32 - mkdir build64 - - echo Running cmake for obs-studio %OBSLatestTag% 32-bit... - cd build32 - cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR32%" -DDepsPath="%DepsPath32%" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true .. - echo: - echo: - - echo Running cmake for obs-studio %OBSLatestTag% 64-bit... - cd ..\build64 - cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR64%" -DDepsPath="%DepsPath64%" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true .. - echo: - echo: - - REM echo Building obs-studio %OBSLatestTag% 32-bit ^(Build Config: %build_config%^)... - REM call msbuild /m /p:Configuration=%build_config% %OBSPath%\build32\obs-studio.sln - - REM echo Building obs-studio %OBSLatestTag% 64-bit ^(Build Config: %build_config%^)... - REM call msbuild /m /p:Configuration=%build_config% %OBSPath%\build64\obs-studio.sln - - cd .. - git describe --tags --abbrev=0 > "%OBSPath%\obs-studio-last-tag-built.txt" - set /p OBSLastTagBuilt=<"%OBSPath%\obs-studio-last-tag-built.txt" -) else ( - echo Last OBS tag built is: %OBSLastTagBuilt% - echo No need to rebuild OBS. -) - -dir "%OBSPath%\libobs" diff --git a/CI/download-obs-deps.cmd b/CI/download-obs-deps.cmd deleted file mode 100644 index ff4ffd57..00000000 --- a/CI/download-obs-deps.cmd +++ /dev/null @@ -1,6 +0,0 @@ -if not exist %DepsBasePath% ( - curl -o %DepsBasePath%.zip -kLO https://obsproject.com/downloads/dependencies2017.zip -f --retry 5 -C - - 7z x %DepsBasePath%.zip -o%DepsBasePath% -) else ( - echo "OBS dependencies are already there. Download skipped." -) diff --git a/CI/install-build-obs-macos.sh b/CI/install-build-obs-macos.sh deleted file mode 100755 index 0dc610aa..00000000 --- a/CI/install-build-obs-macos.sh +++ /dev/null @@ -1,43 +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 - -echo "[obs-websocket] Downloading and unpacking OBS dependencies" -wget --quiet --retry-connrefused --waitretry=1 https://obs-nightly.s3.amazonaws.com/osx-deps-2018-08-09.tar.gz -tar -xf ./osx-deps-2018-08-09.tar.gz -C /tmp - -# 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 .. \ - -DBUILD_CAPTIONS=true \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=10.11 \ - -DDISABLE_PLUGINS=true \ - -DENABLE_SCRIPTING=0 \ - -DDepsPath=/tmp/obsdeps \ - -DCMAKE_PREFIX_PATH=/usr/local/opt/qt/lib/cmake \ -&& make -j4 diff --git a/CI/install-dependencies-macos.sh b/CI/install-dependencies-macos.sh deleted file mode 100755 index 0adf464b..00000000 --- a/CI/install-dependencies-macos.sh +++ /dev/null @@ -1,61 +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 deps -echo "[obs-websocket] Updating Homebrew.." -brew update >/dev/null -echo "[obs-websocket] Checking installed Homebrew formulas.." -BREW_PACKAGES=$(brew list) -BREW_DEPENDENCIES="jack speexdsp ccache swig mbedtls" - -for DEPENDENCY in ${BREW_DEPENDENCIES}; do - if echo "${BREW_PACKAGES}" | grep -q "^${DEPENDENCY}\$"; then - echo "[obs-websocket] Upgrading OBS-Studio dependency '${DEPENDENCY}'.." - brew upgrade ${DEPENDENCY} 2>/dev/null - else - echo "[obs-websocket] Installing OBS-Studio dependency '${DEPENDENCY}'.." - brew install ${DEPENDENCY} 2>/dev/null - fi -done - -# qtwebsockets deps -echo "[obs-websocket] Installing obs-websocket dependency 'QT 5.10.1'.." -# =!= NOTICE =!= -# When building QT5 from sources on macOS 10.13+, use local qt5 formula: -# brew install ./CI/macos/qt.rb -# Pouring from the bottle is much quicker though, so use bottle for now. -# =!= NOTICE =!= - -brew install https://gist.githubusercontent.com/DDRBoxman/b3956fab6073335a4bf151db0dcbd4ad/raw/ed1342a8a86793ea8c10d8b4d712a654da121ace/qt.rb - -# Pin this version of QT5 to avoid `brew upgrade` -# upgrading it to incompatible version -brew pin qt - -# 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 -o './Packages.pkg' --retry-connrefused -s --retry-delay 1 'https://s3-us-west-2.amazonaws.com/obs-nightly/Packages.pkg' - sudo installer -pkg ./Packages.pkg -target / -fi diff --git a/CI/install-qt-win.cmd b/CI/install-qt-win.cmd deleted file mode 100644 index e0537fe8..00000000 --- a/CI/install-qt-win.cmd +++ /dev/null @@ -1,8 +0,0 @@ -if not exist %QtBaseDir% ( - curl -kLO https://cdn-fastly.obsproject.com/downloads/Qt_5.10.1.7z -f --retry 5 -z Qt_5.10.1.7z - 7z x Qt_5.10.1.7z -o%QtBaseDir% -) else ( - echo "Qt is already installed. Download skipped." -) - -dir %QtBaseDir% diff --git a/CI/linux/build-plugin-ubuntu.sh b/CI/linux/build-plugin-ubuntu.sh new file mode 100755 index 00000000..584242f4 --- /dev/null +++ b/CI/linux/build-plugin-ubuntu.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -ex + +echo "[obs-websocket] Running CMake.." +mkdir build && cd build +cmake -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=true .. + +echo "[obs-websocket] Building plugin.." +make -j4 diff --git a/CI/install-dependencies-ubuntu.sh b/CI/linux/install-dependencies-ubuntu.sh similarity index 53% rename from CI/install-dependencies-ubuntu.sh rename to CI/linux/install-dependencies-ubuntu.sh index d0e16f53..43cd58f1 100755 --- a/CI/install-dependencies-ubuntu.sh +++ b/CI/linux/install-dependencies-ubuntu.sh @@ -1,9 +1,11 @@ #!/bin/sh set -ex +echo "[obs-websocket] Installing obs-studio PPA and updates.." sudo add-apt-repository -y ppa:obsproject/obs-studio sudo apt-get -qq update +echo "[obs-websocket] Installing obs-studio and dependencies.." sudo apt-get install -y \ libc-dev-bin \ libc6-dev git \ @@ -13,7 +15,12 @@ sudo apt-get install -y \ obs-studio \ qtbase5-dev +echo "[obs-websocket] Installed OBS Version: $(obs --version)" + +ls /usr/include/ +ls /usr/include/obs/ + # Dirty hack -sudo wget -O /usr/include/obs/obs-frontend-api.h https://raw.githubusercontent.com/obsproject/obs-studio/25.0.0/UI/obs-frontend-api/obs-frontend-api.h +sudo wget -O /usr/include/obs/obs-frontend-api.h https://raw.githubusercontent.com/obsproject/obs-studio/26.1.2/UI/obs-frontend-api/obs-frontend-api.h sudo ldconfig diff --git a/CI/package-ubuntu.sh b/CI/linux/package-plugin-ubuntu.sh similarity index 71% rename from CI/package-ubuntu.sh rename to CI/linux/package-plugin-ubuntu.sh index 367e002c..fb3aac46 100755 --- a/CI/package-ubuntu.sh +++ b/CI/linux/package-plugin-ubuntu.sh @@ -5,8 +5,9 @@ set -e export GIT_HASH=$(git rev-parse --short HEAD) export PKG_VERSION="1-$GIT_HASH-$BRANCH_SHORT_NAME-git" -if [[ "$BRANCH_FULL_NAME" =~ "^refs/tags/" ]]; then +if [[ $BRANCH_FULL_NAME =~ ^refs/tags/ ]]; then export PKG_VERSION="$BRANCH_SHORT_NAME" + echo "[obs-websocket] Branch is a tag. Setting version to $PKG_VERSION." fi cd ./build @@ -17,7 +18,7 @@ PAGER="cat" sudo checkinstall -y --type=debian --fstrans=no --nodoc \ --pkglicense="GPLv2.0" --maintainer="stephane.lepin@gmail.com" \ --pkggroup="video" \ --pkgsource="https://github.com/Palakis/obs-websocket" \ - --requires="obs-studio,libqt5core5a,libqt5widgets5,qt5-image-formats-plugins" \ + --requires="obs-studio \(\>= 26.1.0\), libqt5core5a, libqt5widgets5, qt5-image-formats-plugins" \ --pakdir="../package" sudo chmod ao+r ../package/* diff --git a/CI/macos/Brewfile b/CI/macos/Brewfile new file mode 100644 index 00000000..923af906 --- /dev/null +++ b/CI/macos/Brewfile @@ -0,0 +1,5 @@ +brew "jack" +brew "speexdsp" +brew "cmake" +brew "freetype" +brew "fdk-aac" diff --git a/CI/build-macos.sh b/CI/macos/build-plugin-macos.sh similarity index 61% rename from CI/build-macos.sh rename to CI/macos/build-plugin-macos.sh index f528ce2e..13c15806 100755 --- a/CI/build-macos.sh +++ b/CI/macos/build-plugin-macos.sh @@ -3,23 +3,21 @@ OSTYPE=$(uname) if [ "${OSTYPE}" != "Darwin" ]; then - echo "[obs-websocket - Error] macOS build script can be run on Darwin-type OS only." - exit 1 + 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 + echo "[obs-websocket - Error] CMake not installed - please run 'install-dependencies-macos.sh' first." + exit 1 fi -#export QT_PREFIX="$(find /usr/local/Cellar/qt5 -d 1 | tail -n 1)" - echo "[obs-websocket] Building 'obs-websocket' for macOS." mkdir -p build && cd build cmake .. \ - -DQTDIR=/usr/local/opt/qt \ + -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" \ diff --git a/CI/macos/install-build-obs-macos.sh b/CI/macos/install-build-obs-macos.sh new file mode 100755 index 00000000..580c1dfd --- /dev/null +++ b/CI/macos/install-build-obs-macos.sh @@ -0,0 +1,39 @@ +#!/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 new file mode 100755 index 00000000..8c734b8a --- /dev/null +++ b/CI/macos/install-dependencies-macos.sh @@ -0,0 +1,57 @@ +#!/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 59d17ad7..7c574505 100644 --- a/CI/macos/obs-websocket.pkgproj +++ b/CI/macos/obs-websocket.pkgproj @@ -514,11 +514,11 @@ CONCLUSION_ACTION 0 IDENTIFIER - fr.palakis.obswebsocket + fr.palakis.obs-websocket OVERWRITE_PERMISSIONS VERSION - 4.7.0 + 4.9.0 PROJECT_COMMENTS diff --git a/CI/macos/package-plugin-macos.sh b/CI/macos/package-plugin-macos.sh new file mode 100755 index 00000000..6af9cd97 --- /dev/null +++ b/CI/macos/package-plugin-macos.sh @@ -0,0 +1,89 @@ +#!/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 \ + ./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 \ No newline at end of file diff --git a/CI/macos/qt.rb b/CI/macos/qt.rb deleted file mode 100644 index 50ff4f57..00000000 --- a/CI/macos/qt.rb +++ /dev/null @@ -1,163 +0,0 @@ -# Patches for Qt must be at the very least submitted to Qt's Gerrit codereview -# rather than their bug-report Jira. The latter is rarely reviewed by Qt. -class Qt < Formula - desc "Cross-platform application and UI framework" - homepage "https://www.qt.io/" - url "https://download.qt.io/archive/qt/5.10/5.10.1/single/qt-everywhere-src-5.10.1.tar.xz" - mirror "https://mirrorservice.org/sites/download.qt-project.org/official_releases/qt/5.10/5.10.1/single/qt-everywhere-src-5.10.1.tar.xz" - sha256 "05ffba7b811b854ed558abf2be2ddbd3bb6ddd0b60ea4b5da75d277ac15e740a" - head "https://code.qt.io/qt/qt5.git", :branch => "5.10.1", :shallow => false - - bottle do - sha256 "8b4bad005596a5f8790150fe455db998ac2406f4e0f04140d6656205d844d266" => :high_sierra - sha256 "9c488554935fb573554a4e36d36d3c81e47245b7fefc4b61edef894e67ba1740" => :sierra - sha256 "c0407afba5951df6cc4c6f6c1c315972bd41c99cecb4e029919c4c15ab6f7bdc" => :el_capitan - end - - keg_only "Qt 5 has CMake issues when linked" - - option "with-docs", "Build documentation" - option "with-examples", "Build examples" - - deprecated_option "with-mysql" => "with-mysql-client" - - # OS X 10.7 Lion is still supported in Qt 5.5, but is no longer a reference - # configuration and thus untested in practice. Builds on OS X 10.7 have been - # reported to fail: . - depends_on :macos => :mountain_lion - - depends_on "pkg-config" => :build - depends_on :xcode => :build - depends_on "mysql-client" => :optional - depends_on "postgresql" => :optional - - # Restore `.pc` files for framework-based build of Qt 5 on OS X. This - # partially reverts merged - # between the 5.5.1 and 5.6.0 releases. (Remove this as soon as feasible!) - # - # Core formulae known to fail without this patch (as of 2016-10-15): - # * gnuplot (with `--with-qt` option) - # * mkvtoolnix (with `--with-qt` option, silent build failure) - # * poppler (with `--with-qt` option) - patch do - url "https://raw.githubusercontent.com/Homebrew/formula-patches/e8fe6567/qt5/restore-pc-files.patch" - sha256 "48ff18be2f4050de7288bddbae7f47e949512ac4bcd126c2f504be2ac701158b" - end - - # Fix compile error on macOS 10.13 around QFixed: - # https://github.com/Homebrew/homebrew-core/issues/27095 - # https://bugreports.qt.io/browse/QTBUG-67545 - patch do - url "https://raw.githubusercontent.com/z00m1n/formula-patches/0de0e229/qt/QTBUG-67545.patch" - sha256 "4a115097c7582c7dce4207f5500d13feb8c990eb8a05a43f41953985976ebe6c" - end - - # Fix compile error on macOS 10.13 caused by qtlocation dependency - # mapbox-gl-native using Boost 1.62.0 does not build with C++ 17: - # https://github.com/Homebrew/homebrew-core/issues/27095 - # https://bugreports.qt.io/browse/QTBUG-67810 - patch do - url "https://raw.githubusercontent.com/z00m1n/formula-patches/a1a1f0dd/qt/QTBUG-67810.patch" - sha256 "8ee0bf71df1043f08ebae3aa35036be29c4d9ebff8a27e3b0411a6bd635e9382" - end - - def install - args = %W[ - -verbose - -prefix #{prefix} - -release - -opensource -confirm-license - -system-zlib - -qt-libpng - -qt-libjpeg - -qt-freetype - -qt-pcre - -nomake tests - -no-rpath - -pkg-config - -dbus-runtime - -no-assimp - ] - - args << "-nomake" << "examples" if build.without? "examples" - - if build.with? "mysql-client" - args << "-plugin-sql-mysql" - (buildpath/"brew_shim/mysql_config").write <<~EOS - #!/bin/sh - if [ x"$1" = x"--libs" ]; then - mysql_config --libs | sed "s/-lssl -lcrypto//" - else - exec mysql_config "$@" - fi - EOS - chmod 0755, "brew_shim/mysql_config" - args << "-mysql_config" << buildpath/"brew_shim/mysql_config" - end - - args << "-plugin-sql-psql" if build.with? "postgresql" - - system "./configure", *args - system "make" - ENV.deparallelize - system "make", "install" - - if build.with? "docs" - system "make", "docs" - system "make", "install_docs" - end - - # Some config scripts will only find Qt in a "Frameworks" folder - frameworks.install_symlink Dir["#{lib}/*.framework"] - - # The pkg-config files installed suggest that headers can be found in the - # `include` directory. Make this so by creating symlinks from `include` to - # the Frameworks' Headers folders. - Pathname.glob("#{lib}/*.framework/Headers") do |path| - include.install_symlink path => path.parent.basename(".framework") - end - - # Move `*.app` bundles into `libexec` to expose them to `brew linkapps` and - # because we don't like having them in `bin`. - # (Note: This move breaks invocation of Assistant via the Help menu - # of both Designer and Linguist as that relies on Assistant being in `bin`.) - libexec.mkpath - Pathname.glob("#{bin}/*.app") { |app| mv app, libexec } - end - - def caveats; <<~EOS - We agreed to the Qt opensource license for you. - If this is unacceptable you should uninstall. - EOS - end - - test do - (testpath/"hello.pro").write <<~EOS - QT += core - QT -= gui - TARGET = hello - CONFIG += console - CONFIG -= app_bundle - TEMPLATE = app - SOURCES += main.cpp - EOS - - (testpath/"main.cpp").write <<~EOS - #include - #include - - int main(int argc, char *argv[]) - { - QCoreApplication a(argc, argv); - qDebug() << "Hello World!"; - return 0; - } - EOS - - system bin/"qmake", testpath/"hello.pro" - system "make" - assert_predicate testpath/"hello", :exist? - assert_predicate testpath/"main.o", :exist? - system "./hello" - end -end diff --git a/CI/package-macos.sh b/CI/package-macos.sh deleted file mode 100755 index 5210ba5b..00000000 --- a/CI/package-macos.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/sh - -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" -export QT_CELLAR_PREFIX="$(/usr/bin/find /usr/local/Cellar/qt -d 1 | sort -t '.' -k 1,1n -k 2,2n -k 3,3n | tail -n 1)" - -export GIT_HASH=$(git rev-parse --short HEAD) -export GIT_BRANCH_OR_TAG=$(git name-rev --name-only HEAD | awk -F/ '{print $NF}') - -export VERSION="$GIT_HASH-$GIT_BRANCH_OR_TAG" -export LATEST_VERSION="$GIT_BRANCH_OR_TAG" - -export FILENAME="obs-websocket-$VERSION.pkg" - -echo "[obs-websocket] Modifying obs-websocket.so" -install_name_tool \ - -add_rpath @executable_path/../Frameworks/QtWidgets.framework/Versions/5/ \ - -add_rpath @executable_path/../Frameworks/QtGui.framework/Versions/5/ \ - -add_rpath @executable_path/../Frameworks/QtCore.framework/Versions/5/ \ - -change /usr/local/opt/qt/lib/QtWidgets.framework/Versions/5/QtWidgets @rpath/QtWidgets \ - -change /usr/local/opt/qt/lib/QtGui.framework/Versions/5/QtGui @rpath/QtGui \ - -change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore @rpath/QtCore \ - ./build/obs-websocket.so - -# Check if replacement worked -echo "[obs-websocket] Dependencies for obs-websocket" -otool -L ./build/obs-websocket.so - -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 diff --git a/CI/prepare-windows.cmd b/CI/prepare-windows.cmd deleted file mode 100644 index 09057149..00000000 --- a/CI/prepare-windows.cmd +++ /dev/null @@ -1,7 +0,0 @@ -mkdir build32 -mkdir build64 - -cd build32 -cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR32%" -DLibObs_DIR="%OBSPath%\build32\libobs" -DLIBOBS_INCLUDE_DIR="%OBSPath%\libobs" -DLIBOBS_LIB="%OBSPath%\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="%OBSPath%\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" .. -cd ..\build64 -cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR64%" -DLibObs_DIR="%OBSPath%\build64\libobs" -DLIBOBS_INCLUDE_DIR="%OBSPath%\libobs" -DLIBOBS_LIB="%OBSPath%\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="%OBSPath%\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" .. diff --git a/CI/windows/download-obs-deps.cmd b/CI/windows/download-obs-deps.cmd new file mode 100644 index 00000000..2baf5ec4 --- /dev/null +++ b/CI/windows/download-obs-deps.cmd @@ -0,0 +1,7 @@ +if exist %DEPS_BASE_PATH% ( + echo "OBS dependencies found. Download skipped." +) else ( + echo "OBS dependencies not found. Downloading..." + curl -o %DEPS_BASE_PATH%.zip -kLO https://cdn-fastly.obsproject.com/downloads/dependencies2019.zip -f --retry 5 -C - + 7z x %DEPS_BASE_PATH%.zip -o%DEPS_BASE_PATH% +) diff --git a/CI/windows/install-qt-win.cmd b/CI/windows/install-qt-win.cmd new file mode 100644 index 00000000..39abd6ba --- /dev/null +++ b/CI/windows/install-qt-win.cmd @@ -0,0 +1,8 @@ +if exist %QT_BASE_DIR% ( + echo "Qt directory found. Download skipped." +) else ( + echo "Qt directory not found. Downloading..." + curl -kLO https://tt2468.net/dl/Qt_5.15.2.7z -f --retry 5 -C - + 7z x Qt_5.15.2.7z -o%QT_BASE_DIR% +) +dir %QT_BASE_DIR% diff --git a/CI/package-windows.cmd b/CI/windows/package-plugin-windows.cmd similarity index 92% rename from CI/package-windows.cmd rename to CI/windows/package-plugin-windows.cmd index fe752995..e3c3d445 100644 --- a/CI/package-windows.cmd +++ b/CI/windows/package-plugin-windows.cmd @@ -9,4 +9,4 @@ REM Package ZIP archive 7z a "obs-websocket-%PackageVersion%-Windows.zip" "..\release\*" REM Build installer -iscc ..\installer\installer.iss /O. /F"obs-websocket-%PackageVersion%-Windows" +iscc ..\installer\installer.iss /O. /F"obs-websocket-%PackageVersion%-Windows-Installer" diff --git a/CI/windows/prepare-obs-windows.cmd b/CI/windows/prepare-obs-windows.cmd new file mode 100644 index 00000000..1795517c --- /dev/null +++ b/CI/windows/prepare-obs-windows.cmd @@ -0,0 +1,37 @@ + +@echo off +SETLOCAL EnableDelayedExpansion + +REM If obs-studio directory does not exist, clone the git repo +if not exist %OBS_PATH% ( + echo obs-studio directory does not exist + git clone https://github.com/obsproject/obs-studio %OBS_PATH% + cd /D %OBS_PATH%\ + git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBS_PATH%\obs-studio-latest-tag.txt" + set /p OBS_LATEST_TAG=<"%OBS_PATH%\obs-studio-latest-tag.txt" +) + +REM Prepare OBS Studio builds + +echo Running CMake... +cd /D %OBS_PATH% +echo git checkout %OBS_LATEST_TAG% +git checkout %OBS_LATEST_TAG% +echo: + +if not exist build32 mkdir build32 +if not exist build64 mkdir build64 + +echo Running cmake for obs-studio %OBS_LATEST_TAG% 32-bit... +cd build32 +cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR32%" -DDepsPath="%DEPS_PATH_32%" -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true .. +echo: +echo: + +echo Running cmake for obs-studio %OBS_LATEST_TAG% 64-bit... +cd ..\build64 +cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR64%" -DDepsPath="%DEPS_PATH_64%" -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true .. +echo: +echo: + +dir "%OBS_PATH%\libobs" diff --git a/CI/windows/prepare-plugin-windows.cmd b/CI/windows/prepare-plugin-windows.cmd new file mode 100644 index 00000000..08bfdafd --- /dev/null +++ b/CI/windows/prepare-plugin-windows.cmd @@ -0,0 +1,7 @@ +mkdir build32 +mkdir build64 + +cd build32 +cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR32%" -DLibObs_DIR="%OBS_PATH%\build32\libobs" -DLIBOBS_INCLUDE_DIR="%OBS_PATH%\libobs" -DLIBOBS_LIB="%OBS_PATH%\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="%OBS_PATH%\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" .. +cd ..\build64 +cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR64%" -DLibObs_DIR="%OBS_PATH%\build64\libobs" -DLIBOBS_INCLUDE_DIR="%OBS_PATH%\libobs" -DLIBOBS_LIB="%OBS_PATH%\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="%OBS_PATH%\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" .. diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ae9580b..5a2f6d67 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,16 +1,20 @@ -cmake_minimum_required(VERSION 3.2) -project(obs-websocket) +cmake_minimum_required(VERSION 3.5) +project(obs-websocket VERSION 4.9.0) set(CMAKE_PREFIX_PATH "${QTDIR}") set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_definitions(-DASIO_STANDALONE) +if (${CMAKE_SYSTEM_PROCESSOR} MATCHES "arm") + set(CMAKE_CXX_FLAGS "-mfpu=neon") +endif() + if (WIN32 OR APPLE) include(external/FindLibObs.cmake) endif() @@ -35,6 +39,7 @@ set(obs-websocket_SOURCES src/WSRequestHandler_StudioMode.cpp src/WSRequestHandler_Transitions.cpp src/WSRequestHandler_Outputs.cpp + src/WSRequestHandler_MediaControl.cpp src/WSEvents.cpp src/Config.cpp src/Utils.cpp @@ -67,8 +72,8 @@ include_directories( "${LIBOBS_INCLUDE_DIR}/../UI/obs-frontend-api" ${Qt5Core_INCLUDES} ${Qt5Widgets_INCLUDES} - "${CMAKE_SOURCE_DIR}/deps/asio/asio/include" - "${CMAKE_SOURCE_DIR}/deps/websocketpp") + "${CMAKE_CURRENT_SOURCE_DIR}/deps/asio/asio/include" + "${CMAKE_CURRENT_SOURCE_DIR}/deps/websocketpp") target_link_libraries(obs-websocket libobs @@ -85,7 +90,8 @@ if(WIN32) endif() if(MSVC) - add_compile_options("/MP") + # Enable Multicore Builds and disable FH4 (to not depend on VCRUNTIME140_1.DLL) + add_definitions(/MP /d2FH4-) endif() add_definitions(-D_WEBSOCKETPP_CPP11_STL_) @@ -110,67 +116,70 @@ if(WIN32) set(RELEASE_DIR "${PROJECT_SOURCE_DIR}/release") add_custom_command(TARGET obs-websocket POST_BUILD - # If config is Release, package release files - COMMAND if $==1 ( + # If config is Release or RelWithDebInfo, package release files + COMMAND if $,$>==1 ( "${CMAKE_COMMAND}" -E make_directory - "${RELEASE_DIR}/data/obs-plugins/obs-websocket" - "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}") + "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}" + ) - COMMAND if $==1 ("${CMAKE_COMMAND}" -E copy_directory + COMMAND if $,$>==1 ( + "${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/data" - "${RELEASE_DIR}/data/obs-plugins/obs-websocket") + "${RELEASE_DIR}/data/obs-plugins/obs-websocket" + ) - COMMAND if $==1 ("${CMAKE_COMMAND}" -E copy - "$" - "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}") - - # In Release mode, copy Qt image format plugins - COMMAND if $==1 ( + COMMAND if $,$>==1 ( "${CMAKE_COMMAND}" -E copy + "$" + "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}" + ) + + # In Release or RelWithDebInfo mode, copy Qt image format plugins + COMMAND if $,$>==1 ( + "${CMAKE_COMMAND}" -E make_directory + "${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats" + ) + COMMAND if $,$>==1 ( + "${CMAKE_COMMAND}" -E copy + "${QTDIR}/plugins/imageformats/qicns.dll" + "${QTDIR}/plugins/imageformats/qico.dll" "${QTDIR}/plugins/imageformats/qjpeg.dll" - "${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats/qjpeg.dll") + "${QTDIR}/plugins/imageformats/qtiff.dll" + "${QTDIR}/plugins/imageformats/qwbmp.dll" + "${QTDIR}/plugins/imageformats/qwebp.dll" + "${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats" + ) + + # If config is RelWithDebInfo, package PDB file for target COMMAND if $==1 ( "${CMAKE_COMMAND}" -E copy - "${QTDIR}/plugins/imageformats/qjpeg.dll" - "${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats/qjpeg.dll") - - # If config is RelWithDebInfo, package release files - COMMAND if $==1 ( - "${CMAKE_COMMAND}" -E make_directory - "${RELEASE_DIR}/data/obs-plugins/obs-websocket" - "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}") - - COMMAND if $==1 ("${CMAKE_COMMAND}" -E copy_directory - "${PROJECT_SOURCE_DIR}/data" - "${RELEASE_DIR}/data/obs-plugins/obs-websocket") - - COMMAND if $==1 ("${CMAKE_COMMAND}" -E copy - "$" - "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}") - - COMMAND if $==1 ("${CMAKE_COMMAND}" -E copy "$" - "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}") + "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}" + ) - # Copy to obs-studio dev environment for immediate testing + # In the Debug configuration, copy to obs-studio dev environment for immediate testing COMMAND if $==1 ( "${CMAKE_COMMAND}" -E copy "$" - "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$/obs-plugins/${ARCH_NAME}") + "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$/obs-plugins/${ARCH_NAME}" + ) COMMAND if $==1 ( "${CMAKE_COMMAND}" -E copy - "$" - "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$/obs-plugins/${ARCH_NAME}") + "$" + "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$/obs-plugins/${ARCH_NAME}" + ) COMMAND if $==1 ( "${CMAKE_COMMAND}" -E make_directory - "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$/data/obs-plugins/obs-websocket") + "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$/data/obs-plugins/obs-websocket" + ) COMMAND if $==1 ( "${CMAKE_COMMAND}" -E copy_directory - "${PROJECT_SOURCE_DIR}/data" - "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$/data/obs-plugins/obs-websocket") + "${PROJECT_SOURCE_DIR}/data" + "${LIBOBS_INCLUDE_DIR}/../${OBS_BUILDDIR_ARCH}/rundir/$/data/obs-plugins/obs-websocket" + ) ) # --- End of sub-section --- @@ -179,20 +188,27 @@ endif() # --- Linux-specific build settings and tasks --- if(UNIX AND NOT APPLE) + include(GNUInstallDirs) + set_target_properties(obs-websocket PROPERTIES PREFIX "") target_link_libraries(obs-websocket obs-frontend-api) file(GLOB locale_files data/locale/*.ini) - execute_process(COMMAND uname -m COMMAND tr -d '\n' OUTPUT_VARIABLE UNAME_MACHINE) - install(TARGETS obs-websocket - LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/obs-plugins") - # Dirty fix for Ubuntu - install(TARGETS obs-websocket - LIBRARY DESTINATION "${CMAKE_INSTALL_PREFIX}/lib/${UNAME_MACHINE}-linux-gnu/obs-plugins") + set(CMAKE_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS + OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_WRITE GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) + + if(${USE_UBUNTU_FIX}) + install(TARGETS obs-websocket LIBRARY + DESTINATION "/usr/lib/obs-plugins" + PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ) + endif() + install(TARGETS obs-websocket LIBRARY + DESTINATION "${CMAKE_INSTALL_FULL_LIBDIR}/obs-plugins" + PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ) install(FILES ${locale_files} - DESTINATION "${CMAKE_INSTALL_PREFIX}/share/obs/obs-plugins/obs-websocket/locale") + DESTINATION "${CMAKE_INSTALL_FULL_DATAROOTDIR}/obs/obs-plugins/obs-websocket/locale") endif() # --- End of section --- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..f94782b9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,82 @@ +# Contributing to obs-websocket + +## Translating obs-websocket to your language + +Localization happens on [Crowdin](https://crowdin.com/project/obs-websocket) + +## Branches + +**Development happens on `4.x-current`** + +## Writing code for obs-websocket + +### Code Formatting Guidelines + +* Function and variable names: snake_case for C names, camelCase for C++ method names + +* Request and Event names should use MixedCaps names + +* Request and Event json properties should use camelCase. For more detailed info on property naming, see [Google's JSON Style Guide](https://google.github.io/styleguide/jsoncstyleguide.xml) + +* Code is indented with Tabs. Assume they are 8 columns wide + +* 80 columns max code width. (Docs can be larger) + +* New and updated requests/events must always come with accompanying documentation comments (see existing protocol elements for examples). +These are required to automatically generate the [protocol specification document](docs/generated/protocol.md). + +### Code Best-Practices + +* Favor return-early code and avoid wrapping huge portions of code in conditionals. As an example, this: +```cpp +if (success) { + return req->SendOKResponse(); +} else { + return req->SendErrorResponse("something went wrong"); +} +``` +is better like this: +```cpp +if (!success) { + return req->SendErrorResponse("something went wrong"); +} +return req->SendOKResponse(); +``` + +* Some example common response/request property names are: + * `sceneName` - The name of a scene + * `sourceName` - The name of a source + * `fromScene` - From a scene - scene name + +### Commit Guidelines + +* Commits follow the 50/72 standard: + * 50 characters max for the commit title (excluding scope name) + * One empty line after the title + * Description wrapped to 72 columns max width per line. + +* Commit titles: + * Use present tense + * Prefix the title with a "scope" name + * e.g: "CI: fix wrong behaviour when packaging for OS X" + * Typical scopes: CI, General, Requests, Events, Server + +**Example commit:** + +``` +Requests: Add GetTransitionPosition + +Adds a new request called `GetTransitionPosition` which gets the current +transition's state from 0.0f to 1.0f. Works with both auto and manual +transitions. +``` + +### Pull Requests + +* Pull Requests must never be based off your fork's main branch (in this case, `4.x-current`). + * Start your work in a newly named branch based on the upstream main one (e.g.: `feature/cool-new-feature`, `bugfix/fix-palakis-mistakes`, ...) + +* Only open a pull request if you are ready to show off your work. + +* If your work is not done yet, but for any reason you need to PR it (like collecting discussions, testing with CI, getting testers), + create it as a Draft Pull Request (open the little arrow menu next to the "Create pull request" button, then select "Create draft pull request"). \ No newline at end of file diff --git a/README.md b/README.md index 83862294..06a28135 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,69 @@ -obs-websocket -============== +# obs-websocket + +

+ +

WebSockets API for OBS Studio. -Follow the main author on Twitter for news & updates : [@LePalakis](https://twitter.com/LePalakis) - -[![Build Status - Windows](https://ci.appveyor.com/api/projects/status/github/Palakis/obs-websocket)](https://ci.appveyor.com/project/Palakis/obs-websocket/history) [![Build Status - Linux](https://travis-ci.org/Palakis/obs-websocket.svg?branch=master)](https://travis-ci.org/Palakis/obs-websocket) +[![Build Status](https://dev.azure.com/Palakis/obs-websocket/_apis/build/status/Palakis.obs-websocket?branchName=4.x-current)](https://dev.azure.com/Palakis/obs-websocket/_build/latest?definitionId=2&branchName=4.x-current) +[![CodeFactor](https://www.codefactor.io/repository/github/palakis/obs-websocket/badge)](https://www.codefactor.io/repository/github/palakis/obs-websocket) +[![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fold_left.svg?style=social&label=Follow%20%40LePalakis)](https://twitter.com/LePalakis) +[![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/all/badge.svg?label=financial+contributors)](https://opencollective.com/obs-websocket) ## Downloads Binaries for Windows, MacOS, and Linux are available in the [Releases](https://github.com/Palakis/obs-websocket/releases) section. +### Homebrew + +If you're using MacOS you can use Homebrew for installation as well: + +```sh +brew install obs-websocket +``` + ## Using obs-websocket -A web client and frontend made by [t2t2](https://github.com/t2t2/obs-tablet-remote) (compatible with tablets and other touch interfaces) is available here : http://t2t2.github.io/obs-tablet-remote/ +Here is a list of available web clients: (compatible with tablets and other touch interfaces) + +- [Niek/obs-web](https://github.com/Niek/obs-web) +- [t2t2/obs-tablet-remote](https://github.com/t2t2/obs-tablet-remote) It is **highly recommended** to protect obs-websocket with a password against unauthorized control. To do this, open the "Websocket server settings" dialog under OBS' "Tools" menu. In the settings dialogs, you can enable or disable authentication and set a password for it. ### Possible use cases - Remote control OBS from a phone or tablet on the same local network -- Change your stream overlay/graphics based on the current scene (like the AGDQ overlay does) +- Change your stream overlay/graphics based on the current scene - Automate scene switching with a third-party program (e.g. : auto-pilot, foot pedal, ...) ### For developers The server is a typical Websockets server running by default on port 4444 (the port number can be changed in the Settings dialog). -The protocol understood by the server is documented in [PROTOCOL.md](docs/generated/protocol.md). +The protocol understood by the server is documented in [PROTOCOL.md](docs/generated/protocol.md). Here's a list of available language APIs for obs-websocket : - Javascript (browser & nodejs): [obs-websocket-js](https://github.com/haganbmj/obs-websocket-js) by Brendan Hagan - C#/VB.NET: [obs-websocket-dotnet](https://github.com/Palakis/obs-websocket-dotnet) - Python 2 and 3: [obs-websocket-py](https://github.com/Elektordi/obs-websocket-py) by Guillaume Genty a.k.a Elektordi - Python 3.5+ with asyncio: [obs-ws-rc](https://github.com/KirillMysnik/obs-ws-rc) by Kirill Mysnik +- Python 3.6+ with asyncio: [simpleobsws](https://github.com/IRLToolkit/simpleobsws) by tt2468 - Java 8+: [obs-websocket-java](https://github.com/Twasi/websocket-obs-java) by TwasiNET +- Java 11+: [obs-java-client](https://github.com/harm27/obs-java-client) by harm27 - Golang: [go-obs-websocket](https://github.com/christopher-dG/go-obs-websocket) by Chris de Graaf +- Rust: [obws](https://github.com/dnaka91/obws) by dnaka91 +- HTTP API: [obs-websocket-http](https://github.com/IRLToolkit/obs-websocket-http) by tt2468 +- CLI: [obs-cli](https://github.com/leafac/obs-cli) by leafac -I'd like to know what you're building with or for obs-websocket. If you do something in this fashion, feel free to drop me an email at `stephane /dot/ lepin /at/ gmail /dot/ com` ! +I'd like to know what you're building with or for obs-websocket. If you do something in this fashion, feel free to drop a message in `#project-showoff` in the [discord server!](https://discord.gg/WBaSQ3A) + +### Securing obs-websocket (via TLS/SSL) + +If you are intending to use obs-websocket outside of a LAN environment, it is highly recommended to secure the connection using a tunneling service. + +See the SSL [tunnelling guide](SSL-TUNNELLING.md) for easy instructions on how to encrypt your websocket connection. ## Compiling obs-websocket @@ -44,94 +71,62 @@ See the [build instructions](BUILDING.md). ## Contributing -### Branches - -The two main development branches are: - -- `4.x-current`: actively-maintained codebase for 4.x releases. Backwards-compatible (unless stated otherwise) with existing clients until 5.0. -- `5.x`: upcoming 5.0 version - -**New features and fixes must be based off and contributed to `4.x-current`**, as obs-websocket 5.0 is not in active development yet. - -### Pull Requests - -Pull Requests must never be based off your fork's main branch (in our case, `4.x-current` or `5.x`). Start your work in a new branch -based on the main one (e.g.: `cool-new-feature`, `fix-palakis-mistakes`, ...) and open a Pull Request once you feel ready to show your work. - -If your Pull Request is not ready to merge yet, tag it with the `work in progress` label. You can also use the `help needed` label if you have questions, need a hand or want to ask for input. - -### Code style & formatting - -Source code is indented with tabs, with spaces allowed for alignment. - -Regarding protocol changes: new and updated request types / events must always come with accompanying documentation comments (see existing protocol elements for examples). -These are required to automatically generate the [protocol specification document](docs/generated/protocol.md). - -Among other recommendations: favor return-early code and avoid wrapping huge portions of code in conditionals. As an example, this: - -```cpp -if (success) { - return req->SendOKResponse(); -} else { - return req->SendErrorResponse("something went wrong"); -} -``` - -is better like this: - -```cpp -if (!success) { - return req->SendErrorResponse("something went wrong"); -} -return req->SendOKResponse(); -``` - +See [the contributing document](/CONTRIBUTING.md) ## Translations -**Your help is welcome on translations**. Please join the localization project on Crowdin: https://crowdin.com/project/obs-websocket +**Your help is welcome on translations.** + +Please join the localization project on [Crowdin](https://crowdin.com/project/obs-websocket) ## Special thanks -In (almost) order of appearance: - -- [Brendan H.](https://github.com/haganbmj): Code contributions and gooder English in the Protocol specification -- [Mikhail Swift](https://github.com/mikhailswift): Code contributions -- [Tobias Frahmer](https://github.com/Frahmer): Initial German localization -- [Genture](https://github.com/Genteure): Initial Simplified Chinese and Traditional Chinese localizations -- [Larissa Gabilan](https://github.com/laris151): Initial Portuguese localization -- [Andy Asquelt](https://github.com/asquelt): Initial Polish localization -- [Marcel Haazen](https://github.com/nekocentral): Initial Dutch localization -- [Peter Antonvich](https://github.com/pantonvich): Code contributions -- [yinzara](https://github.com/yinzara): Code contributions -- [Chris Angelico](https://github.com/Rosuav): Code contributions -- [Guillaume "Elektordi" Genty](https://github.com/Elektordi): Code contributions -- [Marwin M](https://github.com/dragonbane0): Code contributions -- [Logan S.](https://github.com/lsdaniel): Code contributions -- [RainbowEK](https://github.com/RainbowEK): Code contributions -- [RytoEX](https://github.com/RytoEX): CI script and code contributions -- [Theodore Stoddard](https://github.com/TStod): Code contributions -- [Philip Loche](https://github.com/PicoCentauri): Code contributions -- [Patrick Heyer](https://github.com/PatTheMav): Code contributions and CI fixes -- [Alex Van Camp](https://github.com/Lange): Code contributions -- [Freddie Meyer](https://github.com/DungFu): Code contributions -- [Casey Muller](https://github.com/caseymrm): CI fixes -- [Chris Angelico](https://github.com/Rosuav): Documentation fixes +Thank you so much to all of the contibutors [(here)](https://github.com/Palakis/obs-websocket/graphs/contributors) for your amazing help. And also: special thanks to supporters of the project! ## Supporters -They have contributed financially to the project and made possible the addition of several features into obs-websocket. Many thanks to them! +These supporters have contributed financially to the project and made possible the addition of several features into obs-websocket. Many thanks to them! --- -[Support Class](http://supportclass.net) designs and develops professional livestreams, with services ranging from broadcast graphics design and integration to event organization, along many other skills. +[Support Class](http://supportclass.net) designs and develops professional livestreams, with services ranging from broadcast graphics design and integration to event organization, along many other skills. [![Support Class](.github/images/supportclass_logo_blacktext.png)](http://supportclass.net) --- -[MediaUnit](http://www.mediaunit.no) is a Norwegian media company developing products and services for the media industry, primarly focused on web and events. +[MediaUnit](http://www.mediaunit.no) is a Norwegian media company developing products and services for the media industry, primarly focused on web and events. [![MediaUnit](.github/images/mediaunit_logo_black.png)](http://www.mediaunit.no/) + +## Contributors + +### Code Contributors + +This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. + + +### Financial Contributors + +Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/obs-websocket/contribute)] + +#### Individuals + + + +#### Organizations + +Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/obs-websocket/contribute)] + + + + + + + + + + + diff --git a/SSL-TUNNELLING.md b/SSL-TUNNELLING.md new file mode 100644 index 00000000..b841e711 --- /dev/null +++ b/SSL-TUNNELLING.md @@ -0,0 +1,45 @@ +# Connecting over a TLS/secure connection (or remotely) + +If you want to expose the WebSocket server of obs-websocket over a secure TLS connection (or to connect remotely), the easiest approach is to use a localhost tunneling service like [ngrok](https://ngrok.com/) or [pagekite](https://pagekite.net/). + +**Before doing this, secure the WebSocket server first by enabling authentication with a strong password!** + +**Please bear in mind that doing this will expose your OBS instance to the open Internet and the security risks it implies. *You've been warned!*** + + +## ngrok + +[Install the ngrok CLI tool](https://ngrok.com/download) on a linux OS, then start ngrok bound to port 4444 like this: + +```bash +ngrok http 4444 +``` + +The ngrok command will output something like this: + +```text +ngrok by @inconshreveable + +Tunnel Status online +Version 2.0/2.0 +Web Interface http://127.0.0.1:4040 +Forwarding http://TUNNEL_ID.ngrok.io -> localhost:4444 +Forwarding https://TUNNEL_ID.ngrok.io -> localhost:4444 +``` + +Where `TUNNEL_ID` is, as the name implies, the unique name of your ngrok tunnel. You'll get a new one every time you start ngrok. + +Then, use `wss://TUNNEL_ID.ngrok.io` to connect to obs-websocket over TLS. + +See the [ngrok documentation](https://ngrok.com/docs) for more tunneling options and settings. + + +## PageKite + +[Install the PageKite CLI tool](http://pagekite.net/downloads), then start PageKite bound to port 4444 like this (replace NAME with one of your choosing): + +```bash +python pagekite.py 4444 NAME.pagekite.me +``` + +Then, use `wss://NAME.pagekite.me` to connect to obs-websocket over TLS. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 11c09166..d08cb5e4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,3 +1,14 @@ +variables: + isReleaseMode: ${{ startsWith(variables['Build.SourceBranch'], 'refs/tags/') }} + +trigger: + branches: + include: + - '4.x-current' + tags: + include: + - '*' + jobs: - job: 'GenerateDocs' condition: | @@ -22,31 +33,41 @@ jobs: vmImage: 'windows-2019' variables: build_config: RelWithDebInfo - DepsBasePath: 'D:\obsdependencies' - DepsPath32: '$(DepsBasePath)\win32' - DepsPath64: '$(DepsBasePath)\win64' - QtBaseDir: 'D:\QtDep' - QTDIR32: '$(QtBaseDir)\5.10.1\msvc2017' - QTDIR64: '$(QtBaseDir)\5.10.1\msvc2017_64' - OBSPath: 'D:\obs-studio' + DEPS_CACHE_VERSION: '1' # Change whenever updating OBS dependencies URL, in order to force a cache reset + DEPS_BASE_PATH: 'D:\obsdependencies' + DEPS_PATH_32: '$(DEPS_BASE_PATH)\win32' + DEPS_PATH_64: '$(DEPS_BASE_PATH)\win64' + QT_CACHE_VERSION: '1' # Change whenever updating Qt dependency URL, in order to force a cache reset + QT_BASE_DIR: 'D:\QtDep' + QTDIR32: '$(QT_BASE_DIR)\5.15.2\msvc2019' + QTDIR64: '$(QT_BASE_DIR)\5.15.2\msvc2019_64' + OBS_PATH: 'D:\obs-studio' steps: - checkout: self submodules: true - - script: ./CI/install-qt-win.cmd + - task: Cache@2 + displayName: Restore cached Qt archive file + inputs: + key: 'qtdep-"$(QT_CACHE_VERSION)" | "$(Agent.OS)"' + restoreKeys: | + qtdep-"$(QT_CACHE_VERSION)" | "$(Agent.OS)" + path: $(QT_BASE_DIR) + + - script: ./CI/windows/install-qt-win.cmd displayName: 'Install Qt' env: - QtBaseDir: $(QtBaseDir) + QT_BASE_DIR: $(QT_BASE_DIR) - task: Cache@2 displayName: Restore cached OBS Studio dependencies inputs: - key: 'obsdeps | "$(Agent.OS)"' + key: 'obsdeps-"$(DEPS_CACHE_VERSION)" | "$(Agent.OS)"' restoreKeys: | - obsdeps | "$(Agent.OS)" - path: $(DepsBasePath) + obsdeps-"$(DEPS_CACHE_VERSION)" | "$(Agent.OS)" + path: $(DEPS_BASE_PATH) - - script: ./CI/download-obs-deps.cmd + - script: ./CI/windows/download-obs-deps.cmd displayName: 'Download OBS Studio dependencies' - task: Cache@2 @@ -55,37 +76,37 @@ jobs: key: 'obs | "$(Agent.OS)"' restoreKeys: | obs | "$(Agent.OS)" - path: $(OBSPath) + path: $(OBS_PATH) - - script: ./CI/checkout-cmake-obs-windows.cmd + - script: ./CI/windows/prepare-obs-windows.cmd displayName: 'Checkout & CMake OBS Studio' env: build_config: $(build_config) - DepsPath32: $(DepsPath32) - DepsPath64: $(DepsPath64) + DEPS_PATH_32: $(DEPS_PATH_32) + DEPS_PATH_64: $(DEPS_PATH_64) QTDIR32: $(QTDIR32) QTDIR64: $(QTDIR64) - OBSPath: $(OBSPath) + OBS_PATH: $(OBS_PATH) - task: MSBuild@1 displayName: 'Build OBS Studio 32-bit' inputs: msbuildArguments: '/m /p:Configuration=$(build_config)' - solution: '$(OBSPath)\build32\obs-studio.sln' + solution: '$(OBS_PATH)\build32\obs-studio.sln' - task: MSBuild@1 displayName: 'Build OBS Studio 64-bit' inputs: msbuildArguments: '/m /p:Configuration=$(build_config)' - solution: '$(OBSPath)\build64\obs-studio.sln' + solution: '$(OBS_PATH)\build64\obs-studio.sln' - - script: ./CI/prepare-windows.cmd + - script: ./CI/windows/prepare-plugin-windows.cmd displayName: 'CMake obs-websocket' env: build_config: $(build_config) QTDIR32: $(QTDIR32) QTDIR64: $(QTDIR64) - OBSPath: $(OBSPath) + OBS_PATH: $(OBS_PATH) - task: MSBuild@1 displayName: 'Build obs-websocket 32-bit' @@ -99,7 +120,7 @@ jobs: msbuildArguments: '/m /p:Configuration=$(build_config)' solution: '.\build64\obs-websocket.sln' - - script: ./CI/package-windows.cmd + - script: ./CI/windows/package-plugin-windows.cmd displayName: 'Package obs-websocket' - task: PublishBuildArtifacts@1 @@ -110,22 +131,21 @@ jobs: - job: 'Build_Linux' pool: - vmImage: 'ubuntu-18.04' + vmImage: 'ubuntu-20.04' variables: - BUILD_REASON: $(Build.Reason) BRANCH_SHORT_NAME: $(Build.SourceBranchName) BRANCH_FULL_NAME: $(Build.SourceBranch) steps: - checkout: self submodules: true - - script: ./CI/install-dependencies-ubuntu.sh + - script: ./CI/linux/install-dependencies-ubuntu.sh displayName: 'Install dependencies' - - script: ./CI/build-ubuntu.sh + - script: ./CI/linux/build-plugin-ubuntu.sh displayName: 'Build obs-websocket' - - script: ./CI/package-ubuntu.sh + - script: ./CI/linux/package-plugin-ubuntu.sh displayName: 'Package obs-websocket' - task: PublishBuildArtifacts@1 @@ -135,22 +155,42 @@ jobs: - job: 'Build_macOS' pool: - vmImage: 'macos-10.14' + vmImage: 'macOS-10.15' + variables: + OBS_DEPS_VERSION: '2020-12-22' + QT_VERSION: '5.15.2' steps: - checkout: self submodules: true - - script: ./CI/install-dependencies-macos.sh + - script: ./CI/macos/install-dependencies-macos.sh displayName: 'Install dependencies' + env: + OBS_DEPS_VERSION: $(OBS_DEPS_VERSION) + QT_VERSION: $(QT_VERSION) - - script: ./CI/install-build-obs-macos.sh + - script: ./CI/macos/install-build-obs-macos.sh displayName: 'Build OBS' - - script: ./CI/build-macos.sh + - script: ./CI/macos/build-plugin-macos.sh displayName: 'Build obs-websocket' - - script: ./CI/package-macos.sh + - task: InstallAppleCertificate@2 + displayName: 'Install release signing certificates' + condition: eq(variables['isReleaseMode'], true) + inputs: + certSecureFile: 'Certificates.p12' + certPwd: $(secrets.macOS.certificatesImportPassword) + + - script: ./CI/macos/package-plugin-macos.sh displayName: 'Package obs-websocket' + env: + RELEASE_MODE: $(isReleaseMode) + CODE_SIGNING_IDENTITY: $(secrets.macOS.codeSigningIdentity) + INSTALLER_SIGNING_IDENTITY: $(secrets.macOS.installerSigningIdentity) + AC_USERNAME: $(secrets.macOS.notarization.username) + AC_PASSWORD: $(secrets.macOS.notarization.password) + AC_PROVIDER_SHORTNAME: $(secrets.macOS.notarization.providerShortName) - task: PublishBuildArtifacts@1 inputs: diff --git a/data/locale/ar-SA.ini b/data/locale/ar-SA.ini index e69de29b..8b137891 100644 --- a/data/locale/ar-SA.ini +++ b/data/locale/ar-SA.ini @@ -0,0 +1 @@ + diff --git a/data/locale/de-DE.ini b/data/locale/de-DE.ini index 3cd6e23b..d2f5efc9 100644 --- a/data/locale/de-DE.ini +++ b/data/locale/de-DE.ini @@ -3,6 +3,7 @@ OBSWebsocket.Settings.ServerEnable="WebSockets-Server aktivieren" OBSWebsocket.Settings.ServerPort="Server-Port" OBSWebsocket.Settings.AuthRequired="Authentifizierung aktivieren" OBSWebsocket.Settings.Password="Passwort" +OBSWebsocket.Settings.LockToIPv4="Nur IPv4 verwenden (deaktiviert IPv6)" OBSWebsocket.Settings.DebugEnable="Debug-Protokollierung aktivieren" OBSWebsocket.Settings.AlertsEnable="Infobereichbenachrichtigungen aktivieren" OBSWebsocket.NotifyConnect.Title="Neue Websocket-Verbindung" @@ -10,7 +11,8 @@ OBSWebsocket.NotifyConnect.Message="Client %1 verbunden" OBSWebsocket.NotifyDisconnect.Title="Websocket-Client getrennt" OBSWebsocket.NotifyDisconnect.Message="Client %1 getrennt" OBSWebsocket.Server.StartFailed.Title="Websocket-Serverfehler" -OBSWebsocket.Server.StartFailed.Message="Der WebSockets-Server konnte nicht gestartet werden, mögliche Gründe:\n - Der TCP-Port %1 wird möglicherweise gerade von einem anderen Programm verwendet. Versuchen Sie einen anderen Port in den Websocket-Servereinstellungen zu setzen oder alle Programme zu beenden, die den Port möglicherweise verwenden.\n - Ein unbekannter Netzwerkfehler ist aufgetreten. Versuchen Sie es mit anderen Einstellungen, einem OBS-Neustart oder einem Systemneustart erneut." +OBSWebsocket.Server.StartFailed.Message="Der WebSocket-Server konnte nicht gestartet werden, mögliche Gründe:\n - Der TCP-Port %1 wird möglicherweise gerade von einem anderen Programm verwendet. Versuchen Sie einen anderen Port in den Websocket-Servereinstellungen zu setzen oder alle Programme zu beenden, die den Port möglicherweise verwenden.\n - Fehler: %2" OBSWebsocket.ProfileChanged.Started="WebSockets-Server in diesem Profil aktiviert. Server gestartet." OBSWebsocket.ProfileChanged.Stopped="WebSockets-Server in diesem Profil deaktiviert. Server gestoppt." OBSWebsocket.ProfileChanged.Restarted="WebSockets-Server in diesem Profil geändert. Server startet neu." + diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index 8725e18d..0382082f 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -3,14 +3,19 @@ OBSWebsocket.Settings.ServerEnable="Enable WebSockets server" OBSWebsocket.Settings.ServerPort="Server Port" OBSWebsocket.Settings.AuthRequired="Enable authentication" OBSWebsocket.Settings.Password="Password" +OBSWebsocket.Settings.LockToIPv4="Lock server to only using IPv4" OBSWebsocket.Settings.DebugEnable="Enable debug logging" OBSWebsocket.Settings.AlertsEnable="Enable System Tray Alerts" +OBSWebsocket.Settings.AuthDisabledWarning="Running obs-websocket with authentication disabled is not recommended, as it allows attackers to easily collect sensetive data. Are you sure you want to proceed?" OBSWebsocket.NotifyConnect.Title="New WebSocket connection" OBSWebsocket.NotifyConnect.Message="Client %1 connected" OBSWebsocket.NotifyDisconnect.Title="WebSocket client disconnected" OBSWebsocket.NotifyDisconnect.Message="Client %1 disconnected" OBSWebsocket.Server.StartFailed.Title="WebSockets Server failure" -OBSWebsocket.Server.StartFailed.Message="The WebSockets server failed to start, maybe because:\n - TCP port %1 may currently be in use elsewhere on this system, possibly by another application. Try setting a different TCP port in the WebSocket server settings, or stop any application that could be using this port.\n - An unknown network error happened on your system. Try again by changing settings, restarting OBS or restarting your system." +OBSWebsocket.Server.StartFailed.Message="The WebSockets server failed to start, maybe because:\n - TCP port %1 may currently be in use elsewhere on this system, possibly by another application. Try setting a different TCP port in the WebSocket server settings, or stop any application that could be using this port.\n - Error message: %2" OBSWebsocket.ProfileChanged.Started="WebSockets server enabled in this profile. Server started." OBSWebsocket.ProfileChanged.Stopped="WebSockets server disabled in this profile. Server stopped." -OBSWebsocket.ProfileChanged.Restarted="WebSockets server port changed in this profile. Server restarted." \ No newline at end of file +OBSWebsocket.ProfileChanged.Restarted="WebSockets server port changed in this profile. Server restarted." +OBSWebsocket.InitialPasswordSetup.Title="obs-websocket - Server Password Configuration" +OBSWebsocket.InitialPasswordSetup.Text="It looks like you are running obs-websocket for the first time. Do you want to configure a password now for the WebSockets server? Setting a password is highly recommended." +OBSWebsocket.InitialPasswordSetup.DismissedText="You can configure a server password later in the WebSockets Server Settings. (Under the Tools menu of OBS Studio)" diff --git a/data/locale/es-ES.ini b/data/locale/es-ES.ini index 511d2f91..7c82ef2b 100644 --- a/data/locale/es-ES.ini +++ b/data/locale/es-ES.ini @@ -1,3 +1,4 @@ +OBSWebsocket.Settings.DialogTitle="Configuración del servidor WebSocket" OBSWebsocket.Settings.ServerEnable="Habilitar el servidor WebSockets" OBSWebsocket.Settings.ServerPort="Puerto del Servidor" OBSWebsocket.Settings.AuthRequired="Habilitar autenticación" @@ -9,4 +10,7 @@ OBSWebsocket.NotifyConnect.Message="Cliente %1 conectado" OBSWebsocket.NotifyDisconnect.Title="Cliente WebSocket desconectado" OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado" OBSWebsocket.Server.StartFailed.Title="Falla en el servidor WebSockets" -OBSWebsocket.Server.StartFailed.Message="El servidor obs-websocket no se pudo iniciar, tal vez porque: \n - el puerto TCP %1 podría estar actualmente siendo usado este sistema, posiblemente por otra aplicación. Intente configurar un puerto TCP diferente en la configuración del servidor WebSocket, o detenga cualquier aplicación que pudiese estar utilizando este puerto \n - Un error de red desconocido ha afectado su sistema. Inténtelo de nuevo cambiando la configuración, reiniciando OBS o reiniciando su sistema." +OBSWebsocket.ProfileChanged.Started="El servidor WebSocket esta habilitado en este perfil. El servidor ha iniciado." +OBSWebsocket.ProfileChanged.Stopped="Servidor WebSockets deshabilitado en este perfil. Servidor detenido." +OBSWebsocket.ProfileChanged.Restarted="Puerto del servidor WebSockets cambiado en este perfil. Servidor reiniciado." + diff --git a/data/locale/fr-FR.ini b/data/locale/fr-FR.ini index b2ba9238..ed8fb085 100644 --- a/data/locale/fr-FR.ini +++ b/data/locale/fr-FR.ini @@ -10,7 +10,7 @@ OBSWebsocket.NotifyConnect.Message="Le client %1 s'est connecté" OBSWebsocket.NotifyDisconnect.Title="Déconnexion WebSocket" OBSWebsocket.NotifyDisconnect.Message="Le client %1 s'est déconnecté" OBSWebsocket.Server.StartFailed.Title="Impossible de démarrer le serveur WebSockets" -OBSWebsocket.Server.StartFailed.Message="Le serveur WebSockets n'a pas pu démarrer, peut-être parce que :\n - Le port TCP %1 est en cours d'utilisation sur ce système, certainement par un autre programme. Essayez un port différent dans les réglages du serveur WebSocket, ou arrêtez tout programme susceptible d'utiliser ce port.\n - Une erreur réseau inconnue est survenue. Essayez à nouveau en modifiant vos réglages, en redémarrant OBS ou en redémarrant votre ordinateur." OBSWebsocket.ProfileChanged.Started="Serveur WebSockets actif dans ce profil." OBSWebsocket.ProfileChanged.Stopped="Serveur WebSockets désactivé dans ce profil." OBSWebsocket.ProfileChanged.Restarted="Le port actuel diffère du port configuré dans ce profil. Serveur WebSockets redémarré." + diff --git a/data/locale/hi-IN.ini b/data/locale/hi-IN.ini index e69de29b..8b137891 100644 --- a/data/locale/hi-IN.ini +++ b/data/locale/hi-IN.ini @@ -0,0 +1 @@ + diff --git a/data/locale/it-IT.ini b/data/locale/it-IT.ini index df133462..4142ed47 100644 --- a/data/locale/it-IT.ini +++ b/data/locale/it-IT.ini @@ -1,7 +1,9 @@ +OBSWebsocket.Settings.DialogTitle="Impostazioni del server di WebSocket" OBSWebsocket.Settings.ServerEnable="Abilitare il server WebSockets" OBSWebsocket.Settings.ServerPort="Porta del server" OBSWebsocket.Settings.AuthRequired="Abilitare l'autenticazione" OBSWebsocket.Settings.Password="Password" +OBSWebsocket.Settings.LockToIPv4="Blocca il server per usare solo IPv4" OBSWebsocket.Settings.DebugEnable="Attivare la registrazione di debug" OBSWebsocket.Settings.AlertsEnable="Attivare gli avvisi di vassoio di sistema" OBSWebsocket.NotifyConnect.Title="Nuova connessione WebSocket" @@ -9,4 +11,8 @@ OBSWebsocket.NotifyConnect.Message="%1 cliente collegato" OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente disconnesso" OBSWebsocket.NotifyDisconnect.Message="%1 cliente disconnesso" OBSWebsocket.Server.StartFailed.Title="Errore del WebSocket Server" -OBSWebsocket.Server.StartFailed.Message="Impossibile avviare, forse perché il server di WebSockets: \n - %1 porta TCP potrebbe essere attualmente in uso altrove su questo sistema, possibilmente da un'altra applicazione. Provare a impostare una porta TCP diversa nelle impostazioni del server di WebSockets, o arrestare tutte le applicazioni che potrebbero utilizzare questa porta. \n - è verificato un errore di rete sconosciuto sul sistema. Riprova modificando le impostazioni, riavviare OBS o riavvio del sistema." +OBSWebsocket.Server.StartFailed.Message="L'avvio del server WebSockets è fallito, forse perché:\n - La porta TCP %1 può attualmente essere in uso altrove su questo sistema, forse da un'altra applicazione. Provare a impostare una porta TCP diversa nelle impostazioni del server WebSocket o interrompere qualsiasi applicazione che potrebbe utilizzare questa porta.\n - Messaggio di errore: %2" +OBSWebsocket.ProfileChanged.Started="Server WebSockets abilitato in questo profilo. Il server è avviato." +OBSWebsocket.ProfileChanged.Stopped="Server WebSocket disabilitato in questo profilo. Server interrotto." +OBSWebsocket.ProfileChanged.Restarted="La porta del server WebSocket è stata modificata in questo profilo. Il server è stato riavviato." + diff --git a/data/locale/ja-JP.ini b/data/locale/ja-JP.ini index e80892a1..4218cdd7 100644 --- a/data/locale/ja-JP.ini +++ b/data/locale/ja-JP.ini @@ -1,3 +1,4 @@ +OBSWebsocket.Settings.DialogTitle="Websocket サーバー設定" OBSWebsocket.Settings.ServerEnable="WebSockets サーバーを有効にする" OBSWebsocket.Settings.ServerPort="サーバーポート" OBSWebsocket.Settings.AuthRequired="認証を有効にする" @@ -9,3 +10,7 @@ OBSWebsocket.NotifyConnect.Message="接続されているクライアント %1" OBSWebsocket.NotifyDisconnect.Title="WebSocket クライアントが切断されました" OBSWebsocket.NotifyDisconnect.Message="切断されたクライアント %1" OBSWebsocket.Server.StartFailed.Title="WebSockets サーバー障害" +OBSWebsocket.ProfileChanged.Started="このプロファイルでWebSocketサーバが有効になりました。サーバを起動しました。" +OBSWebsocket.ProfileChanged.Stopped="このプロファイルでWebSocketサーバが無効になりました。サーバを停止しました。" +OBSWebsocket.ProfileChanged.Restarted="このプロファイルでWebSocketサーバの通信ポートが変更されました。サーバを再起動しました。" + diff --git a/data/locale/ko-KR.ini b/data/locale/ko-KR.ini index e69de29b..0154da74 100644 --- a/data/locale/ko-KR.ini +++ b/data/locale/ko-KR.ini @@ -0,0 +1,18 @@ +OBSWebsocket.Settings.DialogTitle="웹소켓 서버 설정" +OBSWebsocket.Settings.ServerEnable="웹소켓 서버 활성화" +OBSWebsocket.Settings.ServerPort="서버 포트" +OBSWebsocket.Settings.AuthRequired="인증 활성화" +OBSWebsocket.Settings.Password="비밀번호" +OBSWebsocket.Settings.LockToIPv4="IPv4만 이용하여 서버를 연결" +OBSWebsocket.Settings.DebugEnable="디버그 로깅 활성화" +OBSWebsocket.Settings.AlertsEnable="시스템 트레이 알림 활성화" +OBSWebsocket.NotifyConnect.Title="새로운 웹소켓 연결" +OBSWebsocket.NotifyConnect.Message="클라이언트 %1 가 연결되었습니다" +OBSWebsocket.NotifyDisconnect.Title="웹소켓 클라이언트가 연결 해제되었습니다" +OBSWebsocket.NotifyDisconnect.Message="클라이언트 %1 가 연결이 해제되었습니다" +OBSWebsocket.Server.StartFailed.Title="웹소켓 서버 시작이 실패하였습니다" +OBSWebsocket.Server.StartFailed.Message="웹소켓 서버가 시작되지 못했습니다. \n 시스템 상의 다른 프로그램이 TCP 포트 %1을 사용 중인 것으로 보입니다. 다른 TCP 포트를 사용하거나, 해당 포트를 사용 중인 프로그램을 종료하고 다시 시도해주세요. \n 에러 메시지: %2" +OBSWebsocket.ProfileChanged.Started="프로파일 설정에 따라 웹소켓 서버가 활성화되었습니다. 서버가 시작되었습니다." +OBSWebsocket.ProfileChanged.Stopped="프로파일 설정에 따라 웹소켓 서버가 비활성화되었습니다. 서버가 중지되었습니다." +OBSWebsocket.ProfileChanged.Restarted="프로파일 설정에 따라 웹소켓 포트가 변경되었습니다. 서버가 재시작되었습니다." + diff --git a/data/locale/nl-NL.ini b/data/locale/nl-NL.ini index f3151408..29e72d61 100644 --- a/data/locale/nl-NL.ini +++ b/data/locale/nl-NL.ini @@ -1,4 +1,9 @@ +OBSWebsocket.Settings.DialogTitle="WebSockets Server Instellingen" +OBSWebsocket.Settings.ServerEnable="WebSockets server inschakelen" OBSWebsocket.Settings.ServerPort="Serverpoort" +OBSWebsocket.Settings.AuthRequired="Verificatie inschakelen" +OBSWebsocket.Settings.Password="Wachtwoord" +OBSWebsocket.Settings.LockToIPv4="Server vergrendelen om alleen IPv4 te gebruiken" OBSWebsocket.Settings.DebugEnable="Activeer debug logs" OBSWebsocket.Settings.AlertsEnable="Systemvak waarschuwingen inschakelen" OBSWebsocket.NotifyConnect.Title="Nieuwe WebSocket verbinding" @@ -7,3 +12,7 @@ OBSWebsocket.NotifyDisconnect.Title="WebSocket client connectie verbroken" OBSWebsocket.NotifyDisconnect.Message="Client %1 losgekoppeld" OBSWebsocket.Server.StartFailed.Title="Fout in WebSocket server" OBSWebsocket.Server.StartFailed.Message="De obs-websocket server kan niet worden gestart, misschien omdat: \n - TCP-poort %1 momenteel elders wordt gebruikt op dit systeem, eventueel door een andere toepassing. Probeer een andere TCP-poort in te stellen in de WebSocket Server-instellingen of stop elke toepassing die deze poort zou kunnen gebruiken.\n Een onbekende Netwerkfout op uw systeem. Probeer het opnieuw door de instellingen te wijzigen, OBS te herstarten of uw systeem te herstarten." +OBSWebsocket.ProfileChanged.Started="WebSockets server ingeschakeld in dit profiel. Server gestart." +OBSWebsocket.ProfileChanged.Stopped="WebSockets server uitgeschakeld in dit profiel. Server is gestopt." +OBSWebsocket.ProfileChanged.Restarted="WebSockets server poort is veranderd in dit profiel. Server is herstart." + diff --git a/data/locale/pl-PL.ini b/data/locale/pl-PL.ini index 984b630d..8b434cda 100644 --- a/data/locale/pl-PL.ini +++ b/data/locale/pl-PL.ini @@ -1,5 +1,9 @@ +OBSWebsocket.Settings.DialogTitle="Ustawienia serwera WebSockets" OBSWebsocket.Settings.ServerEnable="Włącz serwer WebSockets" +OBSWebsocket.Settings.ServerPort="Port serwera" OBSWebsocket.Settings.AuthRequired="Wymagaj uwierzytelniania" +OBSWebsocket.Settings.Password="Hasło" +OBSWebsocket.Settings.LockToIPv4="Zablokuj serwer tylko za pomocą IPv4" OBSWebsocket.Settings.DebugEnable="Włącz rejestrowanie debugowania" OBSWebsocket.Settings.AlertsEnable="Włącz powiadomienia w zasobniku systemowym" OBSWebsocket.NotifyConnect.Title="Nowe połączenie WebSocket" @@ -7,4 +11,5 @@ OBSWebsocket.NotifyConnect.Message="Klient %1 połączony" OBSWebsocket.NotifyDisconnect.Title="Klient WebSocket odłączony" OBSWebsocket.NotifyDisconnect.Message="Klient %1 rozłączony" OBSWebsocket.Server.StartFailed.Title="Awaria serwera WebSockets" -OBSWebsocket.Server.StartFailed.Message="Nie udało się uruchomić serwera WebSockets, może a powodu: \n - TCP port %1 może być obecnie używany gdzie indziej w tym systemie, możliwie przez inną aplikację. Spróbuj ustawić inny port TCP w ustawieniach serwera WebSockets, lub zatrzymać dowolną aplikację, która może używać tego portu \n -nieznany błąd sieci wydarzył się w systemie. Spróbuj ponownie, zmieniając ustawienia, ponownie uruchamiając OBS lub ponownie uruchamiając system." +OBSWebsocket.ProfileChanged.Started="Serwer WebSockets włączony w tym profilu. Serwer uruchomiony." + diff --git a/data/locale/pt-PT.ini b/data/locale/pt-PT.ini index 211d8547..99de1931 100644 --- a/data/locale/pt-PT.ini +++ b/data/locale/pt-PT.ini @@ -1,7 +1,9 @@ +OBSWebsocket.Settings.DialogTitle="Configurações do servidor de WebSockets" OBSWebsocket.Settings.ServerEnable="Habilitar servidor de WebSockets" OBSWebsocket.Settings.ServerPort="Porta do Servidor" OBSWebsocket.Settings.AuthRequired="Activar autenticação" OBSWebsocket.Settings.Password="Palavra passe" +OBSWebsocket.Settings.LockToIPv4="Forçar apenas o uso de IPv4 (desabilitar IPv6)" OBSWebsocket.Settings.DebugEnable="Habilitar registro de debug" OBSWebsocket.Settings.AlertsEnable="Ativar Alertas da bandeja do sistema" OBSWebsocket.NotifyConnect.Title="Nova conexão WebSocket" @@ -9,4 +11,8 @@ OBSWebsocket.NotifyConnect.Message="Cliente %1 conectado" OBSWebsocket.NotifyDisconnect.Title="WebSocket cliente desconectado" OBSWebsocket.NotifyDisconnect.Message="Cliente %1 desconectado" OBSWebsocket.Server.StartFailed.Title="Falha do servidor de WebSocket" -OBSWebsocket.Server.StartFailed.Message="O servidor de WebSockets falhou ao iniciar, talvez porque: \n - TCP port %1 pode estar atualmente em uso em outro lugar sobre este sistema, possivelmente por outro aplicativo. Tente definir uma porta TCP diferente nas configurações do servidor de WebSockets, ou parar qualquer aplicativo que poderia estar usando este porto \n - um erro de rede desconhecido aconteceu no seu sistema. Tente novamente alterar configurações, reiniciando OBS ou reiniciando o sistema." +OBSWebsocket.Server.StartFailed.Message="O servidor de WebSockets falhou ao iniciar, possivelmente porque:\n - A porta TCP %1 pode já estar em uso em algum outro lugar neste sistema, possivelmente por outro aplicativo. Tente definir uma porta TCP diferente nas configurações do servidor de WebSockets, ou pare qualquer aplicativo que possa estar usando essa porta.\n - Mensagem de erro: %2" +OBSWebsocket.ProfileChanged.Started="Servidor de WebSockets habilitado nesse perfil. Servidor iniciado." +OBSWebsocket.ProfileChanged.Stopped="Servidor de WebSockets desabilitado nesse perfil. Servidor parado." +OBSWebsocket.ProfileChanged.Restarted="Porta do servidor de WebSockets foi alterada neste perfil. Servidor reiniciado." + diff --git a/data/locale/ru-RU.ini b/data/locale/ru-RU.ini index 782f33c0..efbf1f46 100644 --- a/data/locale/ru-RU.ini +++ b/data/locale/ru-RU.ini @@ -3,6 +3,7 @@ OBSWebsocket.Settings.ServerEnable="Включить сервер WebSockets" OBSWebsocket.Settings.ServerPort="Порт сервера" OBSWebsocket.Settings.AuthRequired="Включить авторизацию" OBSWebsocket.Settings.Password="Пароль" +OBSWebsocket.Settings.LockToIPv4="Блокировка сервера только с использованием IPv4" OBSWebsocket.Settings.DebugEnable="Включить ведение журнала отладки" OBSWebsocket.Settings.AlertsEnable="Включить оповещения в системном трее" OBSWebsocket.NotifyConnect.Title="Новое соединение WebSocket" @@ -10,4 +11,8 @@ OBSWebsocket.NotifyConnect.Message="Клиент %1 подключен" OBSWebsocket.NotifyDisconnect.Title="Клиент WebSocket отключён" OBSWebsocket.NotifyDisconnect.Message="Клиент %1 отключен" OBSWebsocket.Server.StartFailed.Title="Сбой сервера WebSockets" -OBSWebsocket.Server.StartFailed.Message="Ошибка запуска сервера WebSockets. Вероятные причины:\n - Возможно, TCP-порт %1 занят другим приложением в системе. Попробуйте задать другой TCP-порт в настройках сервера WebSockets или закройте приложение, которое может использовать данный порт.\n - Произошла неизвестная сетевая ошибка в системе. Попробуйте снова после изменения настроек, перезапуска OBS или системы." +OBSWebsocket.Server.StartFailed.Message="Сервер WebSockets не запустился, возможно, потому, что:\n-TCP-порт %1 в настоящее время может использоваться в другом месте этой системы, возможно, другим приложением. Попробуйте установить другой TCP-порт в настройках сервера WebSocket или остановить любое приложение, которое может использовать этот порт.\n-сообщение об ошибке: %2" +OBSWebsocket.ProfileChanged.Started="Сервер WebSockets включен в этом профиле. Сервер запущен." +OBSWebsocket.ProfileChanged.Stopped="Сервер WebSockets отключен в этом профиле. Сервер остановлен." +OBSWebsocket.ProfileChanged.Restarted="Порт сервера WebSockets изменен в этом профиле. Сервер перезапущен." + diff --git a/data/locale/zh-CN.ini b/data/locale/zh-CN.ini index a40d3432..cab511cc 100644 --- a/data/locale/zh-CN.ini +++ b/data/locale/zh-CN.ini @@ -1,3 +1,4 @@ +OBSWebsocket.Settings.DialogTitle="WebSockets 服务器设置" OBSWebsocket.Settings.ServerEnable="启用 WebSockets 服务器" OBSWebsocket.Settings.ServerPort="服务器端口" OBSWebsocket.Settings.AuthRequired="启用身份验证" @@ -9,4 +10,7 @@ OBSWebsocket.NotifyConnect.Message="客户端 %1 已连接" OBSWebsocket.NotifyDisconnect.Title="WebSocket 客户端已断开" OBSWebsocket.NotifyDisconnect.Message="客户端 %1 已断开连接" OBSWebsocket.Server.StartFailed.Title="WebSockets 服务器错误" -OBSWebsocket.Server.StartFailed.Message="WebSockets 服务器启动失败,可能是因为:\n - TCP 端口 %1 可能被本机的另一个应用程序占用。尝试在 WebSockets 服务器设置中设置不同的 TCP 端口,或关闭任何可能使用此端口的应用程序。\n - 在您的系统上发生了未知的网络错误。请尝试更改设置、重新启动 OBS 或重新启动系统。" +OBSWebsocket.ProfileChanged.Started="此配置文件中启用了 WebSockets 服务器。服务器已启动。" +OBSWebsocket.ProfileChanged.Stopped="此配置文件中禁用了 WebSockets 服务器。服务器已停止。" +OBSWebsocket.ProfileChanged.Restarted="此配置文件中的 WebSockets 服务器端口已更改。服务器已重新启动。" + diff --git a/data/locale/zh-TW.ini b/data/locale/zh-TW.ini index e72077df..41cc0591 100644 --- a/data/locale/zh-TW.ini +++ b/data/locale/zh-TW.ini @@ -6,4 +6,4 @@ OBSWebsocket.NotifyConnect.Message="客戶端 %1 已連線" OBSWebsocket.NotifyDisconnect.Title="WebSocket 客戶端已離線" OBSWebsocket.NotifyDisconnect.Message="客戶端 %1 已離線" OBSWebsocket.Server.StartFailed.Title="WebSocket 伺服器錯誤" -OBSWebsocket.Server.StartFailed.Message="WebSockets 伺服器啟動失敗,可能的原因有:\n - TCP 連接埠 %1 被系統的其他程式所使用,試著為 WebSockets 伺服器指定不同的 TCP 連接埠,或是關閉任何可能使用此連接埠的程式。\n - 發生的未知的網路錯誤,試著更改設定、重新啟動 OBS 或是重新啟動您的系統。" + diff --git a/docs/generated/comments.json b/docs/generated/comments.json index 2c54dc9a..95fb966e 100644 --- a/docs/generated/comments.json +++ b/docs/generated/comments.json @@ -121,12 +121,13 @@ "subheads": [], "typedef": "{Object} `SceneItemTransform`", "property": [ - "{int} `position.x` The x position of the scene item from the left.", - "{int} `position.y` The y position of the scene item from the top.", + "{double} `position.x` The x position of the scene item from the left.", + "{double} `position.y` The y position of the scene item from the top.", "{int} `position.alignment` The point on the scene item that the item is manipulated from.", "{double} `rotation` The clockwise rotation of the scene item in degrees around the point of alignment.", "{double} `scale.x` The x-scale factor of the scene item.", "{double} `scale.y` The y-scale factor of the scene item.", + "{String} `scale.filter` The scale filter of the source. Can be \"OBS_SCALE_DISABLE\", \"OBS_SCALE_POINT\", \"OBS_SCALE_BICUBIC\", \"OBS_SCALE_BILINEAR\", \"OBS_SCALE_LANCZOS\" or \"OBS_SCALE_AREA\".", "{int} `crop.top` The number of pixels cropped off the top of the scene item before scaling.", "{int} `crop.right` The number of pixels cropped off the right of the scene item before scaling.", "{int} `crop.bottom` The number of pixels cropped off the bottom of the scene item before scaling.", @@ -146,12 +147,12 @@ ], "properties": [ { - "type": "int", + "type": "double", "name": "position.x", "description": "The x position of the scene item from the left." }, { - "type": "int", + "type": "double", "name": "position.y", "description": "The y position of the scene item from the top." }, @@ -175,6 +176,11 @@ "name": "scale.y", "description": "The y-scale factor of the scene item." }, + { + "type": "String", + "name": "scale.filter", + "description": "The scale filter of the source. Can be \"OBS_SCALE_DISABLE\", \"OBS_SCALE_POINT\", \"OBS_SCALE_BICUBIC\", \"OBS_SCALE_BILINEAR\", \"OBS_SCALE_LANCZOS\" or \"OBS_SCALE_AREA\"." + }, { "type": "int", "name": "crop.top", @@ -360,7 +366,7 @@ "{boolean} `flags.encoded` Output is encoded", "{boolean} `flags.multiTrack` Output uses several audio tracks", "{boolean} `flags.service` Output uses a service", - "{Object} `settings` Output name", + "{Object} `settings` Output settings", "{boolean} `active` Output status (active or not)", "{boolean} `reconnecting` Output reconnection status (reconnecting or not)", "{double} `congestion` Output congestion", @@ -427,7 +433,7 @@ { "type": "Object", "name": "settings", - "description": "Output name" + "description": "Output settings" }, { "type": "boolean", @@ -474,6 +480,31 @@ }, "examples": [] }, + { + "subheads": [], + "typedef": "{Object} `ScenesCollection`", + "property": "{String} `sc-name` Name of the scene collection", + "properties": [ + { + "type": "String", + "name": "sc-name", + "description": "Name of the scene collection" + } + ], + "typedefs": [ + { + "type": "Object", + "name": "ScenesCollection", + "description": "" + } + ], + "name": "", + "heading": { + "level": 2, + "text": "" + }, + "examples": [] + }, { "subheads": [], "typedef": "{Object} `Scene`", @@ -561,11 +592,19 @@ }, { "subheads": [], - "description": "The scene list has been modified.\nScenes have been added, removed, or renamed.", + "description": "\n\nNote: This event is not fired when the scenes are reordered.", + "return": "{Array} `scenes` Scenes list.", "api": "events", "name": "ScenesChanged", "category": "scenes", "since": "0.3", + "returns": [ + { + "type": "Array", + "name": "scenes", + "description": "Scenes list." + } + ], "names": [ { "name": "", @@ -588,17 +627,25 @@ "level": 2, "text": "ScenesChanged" }, - "lead": "", + "lead": "The scene list has been modified. Scenes have been added, removed, or renamed.", "type": "class", "examples": [] }, { "subheads": [], "description": "Triggered when switching to another scene collection or when renaming the current scene collection.", + "return": "{String} `sceneCollection` Name of the new current scene collection.", "api": "events", "name": "SceneCollectionChanged", "category": "scenes", "since": "4.0.0", + "returns": [ + { + "type": "String", + "name": "sceneCollection", + "description": "Name of the new current scene collection." + } + ], "names": [ { "name": "", @@ -628,10 +675,26 @@ { "subheads": [], "description": "Triggered when a scene collection is created, added, renamed, or removed.", + "return": [ + "{Array} `sceneCollections` Scene collections list.", + "{String} `sceneCollections.*.name` Scene collection name." + ], "api": "events", "name": "SceneCollectionListChanged", "category": "scenes", "since": "4.0.0", + "returns": [ + { + "type": "Array", + "name": "sceneCollections", + "description": "Scene collections list." + }, + { + "type": "String", + "name": "sceneCollections.*.name", + "description": "Scene collection name." + } + ], "names": [ { "name": "", @@ -704,10 +767,26 @@ { "subheads": [], "description": "The list of available transitions has been modified.\nTransitions have been added, removed, or renamed.", + "return": [ + "{Array} `transitions` Transitions list.", + "{String} `transitions.*.name` Transition name." + ], "api": "events", "name": "TransitionListChanged", "category": "transitions", "since": "4.0.0", + "returns": [ + { + "type": "Array", + "name": "transitions", + "description": "Transitions list." + }, + { + "type": "String", + "name": "transitions.*.name", + "description": "Transition name." + } + ], "names": [ { "name": "", @@ -782,7 +861,7 @@ "{String} `name` Transition name.", "{String} `type` Transition type.", "{int} `duration` Transition duration (in milliseconds). Will be -1 for any transition with a fixed duration, such as a Stinger, due to limitations of the OBS API.", - "{String} `from-scene` Source scene of the transition", + "{String (optional)} `from-scene` Source scene of the transition", "{String} `to-scene` Destination scene of the transition" ], "api": "events", @@ -806,7 +885,7 @@ "description": "Transition duration (in milliseconds). Will be -1 for any transition with a fixed duration, such as a Stinger, due to limitations of the OBS API." }, { - "type": "String", + "type": "String (optional)", "name": "from-scene", "description": "Source scene of the transition" }, @@ -844,7 +923,7 @@ }, { "subheads": [], - "description": "A transition (other than \"cut\") has ended.\nPlease note that the `from-scene` field is not available in TransitionEnd.", + "description": "A transition (other than \"cut\") has ended.\nNote: The `from-scene` field is not available in TransitionEnd.", "return": [ "{String} `name` Transition name.", "{String} `type` Transition type.", @@ -910,7 +989,7 @@ "{String} `name` Transition name.", "{String} `type` Transition type.", "{int} `duration` Transition duration (in milliseconds).", - "{String} `from-scene` Source scene of the transition", + "{String (optional)} `from-scene` Source scene of the transition", "{String} `to-scene` Destination scene of the transition" ], "api": "events", @@ -934,7 +1013,7 @@ "description": "Transition duration (in milliseconds)." }, { - "type": "String", + "type": "String (optional)", "name": "from-scene", "description": "Source scene of the transition" }, @@ -975,10 +1054,18 @@ { "subheads": [], "description": "Triggered when switching to another profile or when renaming the current profile.", + "return": "{String} `profile` Name of the new current profile.", "api": "events", "name": "ProfileChanged", "category": "profiles", "since": "4.0.0", + "returns": [ + { + "type": "String", + "name": "profile", + "description": "Name of the new current profile." + } + ], "names": [ { "name": "", @@ -1008,10 +1095,26 @@ { "subheads": [], "description": "Triggered when a profile is created, added, renamed, or removed.", + "return": [ + "{Array} `profiles` Profiles list.", + "{String} `profiles.*.name` Profile name." + ], "api": "events", "name": "ProfileListChanged", "category": "profiles", "since": "4.0.0", + "returns": [ + { + "type": "Array", + "name": "profiles", + "description": "Profiles list." + }, + { + "type": "String", + "name": "profiles.*.name", + "description": "Profile name." + } + ], "names": [ { "name": "", @@ -1190,7 +1293,7 @@ }, { "subheads": [], - "description": "Emit every 2 seconds.", + "description": "Emitted every 2 seconds when stream is active.", "return": [ "{boolean} `streaming` Current streaming state.", "{boolean} `recording` Current recording state.", @@ -1343,7 +1446,7 @@ "recording": [ { "subheads": [], - "description": "A request to start recording has been issued.", + "description": "\n\nNote: `recordingFilename` is not provided in this event because this information\nis not available at the time this event is emitted.", "api": "events", "name": "RecordingStarting", "category": "recording", @@ -1370,17 +1473,25 @@ "level": 2, "text": "RecordingStarting" }, - "lead": "", + "lead": "A request to start recording has been issued.", "type": "class", "examples": [] }, { "subheads": [], "description": "Recording started successfully.", + "return": "{String} `recordingFilename` Absolute path to the file of the current recording.", "api": "events", "name": "RecordingStarted", "category": "recording", "since": "0.3", + "returns": [ + { + "type": "String", + "name": "recordingFilename", + "description": "Absolute path to the file of the current recording." + } + ], "names": [ { "name": "", @@ -1410,10 +1521,18 @@ { "subheads": [], "description": "A request to stop recording has been issued.", + "return": "{String} `recordingFilename` Absolute path to the file of the current recording.", "api": "events", "name": "RecordingStopping", "category": "recording", "since": "0.3", + "returns": [ + { + "type": "String", + "name": "recordingFilename", + "description": "Absolute path to the file of the current recording." + } + ], "names": [ { "name": "", @@ -1443,10 +1562,18 @@ { "subheads": [], "description": "Recording stopped successfully.", + "return": "{String} `recordingFilename` Absolute path to the file of the current recording.", "api": "events", "name": "RecordingStopped", "category": "recording", "since": "0.3", + "returns": [ + { + "type": "String", + "name": "recordingFilename", + "description": "Absolute path to the file of the current recording." + } + ], "names": [ { "name": "", @@ -1730,6 +1857,7 @@ "api": "events", "name": "Heartbeat", "category": "general", + "since": "v0.3", "returns": [ { "type": "boolean", @@ -1804,6 +1932,12 @@ "description": "general" } ], + "sinces": [ + { + "name": "", + "description": "v0.3" + } + ], "heading": { "level": 2, "text": "Heartbeat" @@ -1814,7 +1948,7 @@ }, { "subheads": [], - "description": "A custom broadcast message was received", + "description": "A custom broadcast message, sent by the server, requested by one of the websocket clients.", "return": [ "{String} `realm` Identifier provided by the sender", "{Object} `data` User-defined data" @@ -1984,7 +2118,8 @@ "description": "The volume of a source has changed.", "return": [ "{String} `sourceName` Source name", - "{float} `volume` Source volume" + "{float} `volume` Source volume", + "{float} `volumeDb` Source volume in Decibel" ], "api": "events", "name": "SourceVolumeChanged", @@ -2000,6 +2135,11 @@ "type": "float", "name": "volume", "description": "Source volume" + }, + { + "type": "float", + "name": "volumeDb", + "description": "Source volume in Decibel" } ], "names": [ @@ -2077,6 +2217,88 @@ "type": "class", "examples": [] }, + { + "subheads": [], + "description": "A source has removed audio.", + "return": "{String} `sourceName` Source name", + "api": "events", + "name": "SourceAudioDeactivated", + "category": "sources", + "since": "4.9.0", + "returns": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name" + } + ], + "names": [ + { + "name": "", + "description": "SourceAudioDeactivated" + } + ], + "categories": [ + { + "name": "", + "description": "sources" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "SourceAudioDeactivated" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "A source has added audio.", + "return": "{String} `sourceName` Source name", + "api": "events", + "name": "SourceAudioActivated", + "category": "sources", + "since": "4.9.0", + "returns": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name" + } + ], + "names": [ + { + "name": "", + "description": "SourceAudioActivated" + } + ], + "categories": [ + { + "name": "", + "description": "sources" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "SourceAudioActivated" + }, + "lead": "", + "type": "class", + "examples": [] + }, { "subheads": [], "description": "The audio sync offset of a source has changed.", @@ -2198,7 +2420,8 @@ "description": "A source has been renamed.", "return": [ "{String} `previousName` Previous source name", - "{String} `newName` New source name" + "{String} `newName` New source name", + "{String} `sourceType` Type of source (input, scene, filter, transition)" ], "api": "events", "name": "SourceRenamed", @@ -2214,6 +2437,11 @@ "type": "String", "name": "newName", "description": "New source name" + }, + { + "type": "String", + "name": "sourceType", + "description": "Type of source (input, scene, filter, transition)" } ], "names": [ @@ -2420,7 +2648,8 @@ "{String} `sourceName` Source name", "{Array} `filters` Ordered Filters list", "{String} `filters.*.name` Filter name", - "{String} `filters.*.type` Filter type" + "{String} `filters.*.type` Filter type", + "{boolean} `filters.*.enabled` Filter visibility status" ], "api": "events", "name": "SourceFiltersReordered", @@ -2446,6 +2675,11 @@ "type": "String", "name": "filters.*.type", "description": "Filter type" + }, + { + "type": "boolean", + "name": "filters.*.enabled", + "description": "Filter visibility status" } ], "names": [ @@ -2473,10 +2707,406 @@ "lead": "", "type": "class", "examples": [] + } + ], + "media": [ + { + "subheads": [], + "description": "\n\nNote: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally.", + "return": [ + "{String} `sourceName` Source name", + "{String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" + ], + "api": "events", + "name": "MediaPlaying", + "category": "media", + "since": "4.9.0", + "returns": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name" + }, + { + "type": "String", + "name": "sourceKind", + "description": "The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" + } + ], + "names": [ + { + "name": "", + "description": "MediaPlaying" + } + ], + "categories": [ + { + "name": "", + "description": "media" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "MediaPlaying" + }, + "lead": "A media source has started playing.", + "type": "class", + "examples": [] }, { "subheads": [], - "description": "Scene items have been reordered.", + "description": "\n\nNote: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally.", + "return": [ + "{String} `sourceName` Source name", + "{String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" + ], + "api": "events", + "name": "MediaPaused", + "category": "media", + "since": "4.9.0", + "returns": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name" + }, + { + "type": "String", + "name": "sourceKind", + "description": "The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" + } + ], + "names": [ + { + "name": "", + "description": "MediaPaused" + } + ], + "categories": [ + { + "name": "", + "description": "media" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "MediaPaused" + }, + "lead": "A media source has been paused.", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "\n\nNote: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally.", + "return": [ + "{String} `sourceName` Source name", + "{String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" + ], + "api": "events", + "name": "MediaRestarted", + "category": "media", + "since": "4.9.0", + "returns": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name" + }, + { + "type": "String", + "name": "sourceKind", + "description": "The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" + } + ], + "names": [ + { + "name": "", + "description": "MediaRestarted" + } + ], + "categories": [ + { + "name": "", + "description": "media" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "MediaRestarted" + }, + "lead": "A media source has been restarted.", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "\n\nNote: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally.", + "return": [ + "{String} `sourceName` Source name", + "{String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" + ], + "api": "events", + "name": "MediaStopped", + "category": "media", + "since": "4.9.0", + "returns": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name" + }, + { + "type": "String", + "name": "sourceKind", + "description": "The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" + } + ], + "names": [ + { + "name": "", + "description": "MediaStopped" + } + ], + "categories": [ + { + "name": "", + "description": "media" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "MediaStopped" + }, + "lead": "A media source has been stopped.", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "\n\nNote: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally.", + "return": [ + "{String} `sourceName` Source name", + "{String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" + ], + "api": "events", + "name": "MediaNext", + "category": "media", + "since": "4.9.0", + "returns": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name" + }, + { + "type": "String", + "name": "sourceKind", + "description": "The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" + } + ], + "names": [ + { + "name": "", + "description": "MediaNext" + } + ], + "categories": [ + { + "name": "", + "description": "media" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "MediaNext" + }, + "lead": "A media source has gone to the next item in the playlist.", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "\n\nNote: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally.", + "return": [ + "{String} `sourceName` Source name", + "{String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" + ], + "api": "events", + "name": "MediaPrevious", + "category": "media", + "since": "4.9.0", + "returns": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name" + }, + { + "type": "String", + "name": "sourceKind", + "description": "The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" + } + ], + "names": [ + { + "name": "", + "description": "MediaPrevious" + } + ], + "categories": [ + { + "name": "", + "description": "media" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "MediaPrevious" + }, + "lead": "A media source has gone to the previous item in the playlist.", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "\n\nNote: These events are emitted by the OBS sources themselves. For example when the media file starts playing. The behavior depends on the type of media source being used.", + "return": [ + "{String} `sourceName` Source name", + "{String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" + ], + "api": "events", + "name": "MediaStarted", + "category": "media", + "since": "4.9.0", + "returns": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name" + }, + { + "type": "String", + "name": "sourceKind", + "description": "The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" + } + ], + "names": [ + { + "name": "", + "description": "MediaStarted" + } + ], + "categories": [ + { + "name": "", + "description": "media" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "MediaStarted" + }, + "lead": "A media source has been started.", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "\n\nNote: These events are emitted by the OBS sources themselves. For example when the media file ends. The behavior depends on the type of media source being used.", + "return": [ + "{String} `sourceName` Source name", + "{String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" + ], + "api": "events", + "name": "MediaEnded", + "category": "media", + "since": "4.9.0", + "returns": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name" + }, + { + "type": "String", + "name": "sourceKind", + "description": "The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`)" + } + ], + "names": [ + { + "name": "", + "description": "MediaEnded" + } + ], + "categories": [ + { + "name": "", + "description": "media" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "MediaEnded" + }, + "lead": "A media source has ended.", + "type": "class", + "examples": [] + } + ], + "scene items": [ + { + "subheads": [], + "description": "Scene items within a scene have been reordered.", "return": [ "{String} `scene-name` Name of the scene where items have been reordered.", "{Array} `scene-items` Ordered list of scene items", @@ -2485,7 +3115,7 @@ ], "api": "events", "name": "SourceOrderChanged", - "category": "sources", + "category": "scene items", "since": "4.0.0", "returns": [ { @@ -2518,7 +3148,7 @@ "categories": [ { "name": "", - "description": "sources" + "description": "scene items" } ], "sinces": [ @@ -2537,7 +3167,7 @@ }, { "subheads": [], - "description": "An item has been added to the current scene.", + "description": "A scene item has been added to a scene.", "return": [ "{String} `scene-name` Name of the scene.", "{String} `item-name` Name of the item added to the scene.", @@ -2545,7 +3175,7 @@ ], "api": "events", "name": "SceneItemAdded", - "category": "sources", + "category": "scene items", "since": "4.0.0", "returns": [ { @@ -2573,7 +3203,7 @@ "categories": [ { "name": "", - "description": "sources" + "description": "scene items" } ], "sinces": [ @@ -2592,7 +3222,7 @@ }, { "subheads": [], - "description": "An item has been removed from the current scene.", + "description": "A scene item has been removed from a scene.", "return": [ "{String} `scene-name` Name of the scene.", "{String} `item-name` Name of the item removed from the scene.", @@ -2600,7 +3230,7 @@ ], "api": "events", "name": "SceneItemRemoved", - "category": "sources", + "category": "scene items", "since": "4.0.0", "returns": [ { @@ -2628,7 +3258,7 @@ "categories": [ { "name": "", - "description": "sources" + "description": "scene items" } ], "sinces": [ @@ -2647,7 +3277,7 @@ }, { "subheads": [], - "description": "An item's visibility has been toggled.", + "description": "A scene item's visibility has been toggled.", "return": [ "{String} `scene-name` Name of the scene.", "{String} `item-name` Name of the item in the scene.", @@ -2656,7 +3286,7 @@ ], "api": "events", "name": "SceneItemVisibilityChanged", - "category": "sources", + "category": "scene items", "since": "4.0.0", "returns": [ { @@ -2689,7 +3319,7 @@ "categories": [ { "name": "", - "description": "sources" + "description": "scene items" } ], "sinces": [ @@ -2708,7 +3338,7 @@ }, { "subheads": [], - "description": "An item's locked status has been toggled.", + "description": "A scene item's locked status has been toggled.", "return": [ "{String} `scene-name` Name of the scene.", "{String} `item-name` Name of the item in the scene.", @@ -2717,8 +3347,8 @@ ], "api": "events", "name": "SceneItemLockChanged", - "category": "sources", - "since": "unreleased", + "category": "scene items", + "since": "4.8.0", "returns": [ { "type": "String", @@ -2750,13 +3380,13 @@ "categories": [ { "name": "", - "description": "sources" + "description": "scene items" } ], "sinces": [ { "name": "", - "description": "unreleased" + "description": "4.8.0" } ], "heading": { @@ -2769,7 +3399,7 @@ }, { "subheads": [], - "description": "An item's transform has been changed.", + "description": "A scene item's transform has been changed.", "return": [ "{String} `scene-name` Name of the scene.", "{String} `item-name` Name of the item in the scene.", @@ -2778,7 +3408,7 @@ ], "api": "events", "name": "SceneItemTransformChanged", - "category": "sources", + "category": "scene items", "since": "4.6.0", "returns": [ { @@ -2811,7 +3441,7 @@ "categories": [ { "name": "", - "description": "sources" + "description": "scene items" } ], "sinces": [ @@ -2838,7 +3468,7 @@ ], "api": "events", "name": "SceneItemSelected", - "category": "sources", + "category": "scene items", "since": "4.6.0", "returns": [ { @@ -2866,7 +3496,7 @@ "categories": [ { "name": "", - "description": "sources" + "description": "scene items" } ], "sinces": [ @@ -2893,7 +3523,7 @@ ], "api": "events", "name": "SceneItemDeselected", - "category": "sources", + "category": "scene items", "since": "4.6.0", "returns": [ { @@ -2921,7 +3551,7 @@ "categories": [ { "name": "", - "description": "sources" + "description": "scene items" } ], "sinces": [ @@ -3205,6 +3835,7 @@ "name": "SetHeartbeat", "category": "general", "since": "4.3.0", + "deprecated": "Since 4.9.0. Please poll the appropriate data using requests. Will be removed in v5.0.0.", "params": [ { "type": "boolean", @@ -3230,6 +3861,12 @@ "description": "4.3.0" } ], + "deprecateds": [ + { + "name": "", + "description": "Since 4.9.0. Please poll the appropriate data using requests. Will be removed in v5.0.0." + } + ], "heading": { "level": 2, "text": "SetHeartbeat" @@ -3323,7 +3960,7 @@ { "subheads": [], "description": "Get OBS stats (almost the same info as provided in OBS' stats window)", - "return": "{OBSStats} `stats` OBS stats", + "return": "{OBSStats} `stats` [OBS stats](#obsstats)", "api": "requests", "name": "GetStats", "category": "general", @@ -3332,7 +3969,7 @@ { "type": "OBSStats", "name": "stats", - "description": "OBS stats" + "description": "[OBS stats](#obsstats)" } ], "names": [ @@ -3505,20 +4142,20 @@ "subheads": [], "description": "Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 or newer.", "param": [ - "{String (Optional)} `type` Type of projector: Preview (default), Source, Scene, StudioProgram, or Multiview (case insensitive).", + "{String (Optional)} `type` Type of projector: `Preview` (default), `Source`, `Scene`, `StudioProgram`, or `Multiview` (case insensitive).", "{int (Optional)} `monitor` Monitor to open the projector on. If -1 or omitted, opens a window.", - "{String (Optional)} `geometry` Size and position of the projector window (only if monitor is -1). Encoded in Base64 using Qt's geometry encoding (https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors.", + "{String (Optional)} `geometry` Size and position of the projector window (only if monitor is -1). Encoded in Base64 using [Qt's geometry encoding](https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors.", "{String (Optional)} `name` Name of the source or scene to be displayed (ignored for other projector types)." ], "api": "requests", "name": "OpenProjector", "category": "general", - "since": "unreleased", + "since": "4.8.0", "params": [ { "type": "String (Optional)", "name": "type", - "description": "Type of projector: Preview (default), Source, Scene, StudioProgram, or Multiview (case insensitive)." + "description": "Type of projector: `Preview` (default), `Source`, `Scene`, `StudioProgram`, or `Multiview` (case insensitive)." }, { "type": "int (Optional)", @@ -3528,7 +4165,7 @@ { "type": "String (Optional)", "name": "geometry", - "description": "Size and position of the projector window (only if monitor is -1). Encoded in Base64 using Qt's geometry encoding (https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors." + "description": "Size and position of the projector window (only if monitor is -1). Encoded in Base64 using [Qt's geometry encoding](https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors." }, { "type": "String (Optional)", @@ -3551,7 +4188,7 @@ "sinces": [ { "name": "", - "description": "unreleased" + "description": "4.8.0" } ], "heading": { @@ -3561,94 +4198,43 @@ "lead": "", "type": "class", "examples": [] - } - ], - "outputs": [ - { - "subheads": [], - "description": "List existing outputs", - "return": "{Array} `outputs` Outputs list", - "api": "requests", - "name": "ListOutputs", - "category": "outputs", - "since": "4.7.0", - "returns": [ - { - "type": "Array", - "name": "outputs", - "description": "Outputs list" - } - ], - "names": [ - { - "name": "", - "description": "ListOutputs" - } - ], - "categories": [ - { - "name": "", - "description": "outputs" - } - ], - "sinces": [ - { - "name": "", - "description": "4.7.0" - } - ], - "heading": { - "level": 2, - "text": "ListOutputs" - }, - "lead": "", - "type": "class", - "examples": [] }, { "subheads": [], - "description": "Get information about a single output", - "param": "{String} `outputName` Output name", - "return": "{Output} `outputInfo` Output info", + "description": "Executes hotkey routine, identified by hotkey unique name", + "param": "{String} `hotkeyName` Unique name of the hotkey, as defined when registering the hotkey (e.g. \"ReplayBuffer.Save\")", "api": "requests", - "name": "GetOutputInfo", - "category": "outputs", - "since": "4.7.0", - "returns": [ - { - "type": "Output", - "name": "outputInfo", - "description": "Output info" - } - ], + "name": "TriggerHotkeyByName", + "category": "general", + "since": "4.9.0", "params": [ { "type": "String", - "name": "outputName", - "description": "Output name" + "name": "hotkeyName", + "description": "Unique name of the hotkey, as defined when registering the hotkey (e.g. \"ReplayBuffer.Save\")" } ], "names": [ { "name": "", - "description": "GetOutputInfo" + "description": "TriggerHotkeyByName" } ], "categories": [ { "name": "", - "description": "outputs" + "description": "general" } ], "sinces": [ { "name": "", - "description": "4.7.0" + "description": "4.9.0" } ], "heading": { "level": 2, - "text": "GetOutputInfo" + "text": "TriggerHotkeyByName" }, "lead": "", "type": "class", @@ -3656,1183 +4242,72 @@ }, { "subheads": [], - "description": "Start an output", - "param": "{String} `outputName` Output name", - "api": "requests", - "name": "StartOutput", - "category": "outputs", - "since": "4.7.0", - "params": [ - { - "type": "String", - "name": "outputName", - "description": "Output name" - } - ], - "names": [ - { - "name": "", - "description": "StartOutput" - } - ], - "categories": [ - { - "name": "", - "description": "outputs" - } - ], - "sinces": [ - { - "name": "", - "description": "4.7.0" - } - ], - "heading": { - "level": 2, - "text": "StartOutput" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Stop an output", + "description": "Executes hotkey routine, identified by bound combination of keys. A single key combination might trigger multiple hotkey routines depending on user settings", "param": [ - "{String} `outputName` Output name", - "{boolean (optional)} `force` Force stop (default: false)" + "{String} `keyId` Main key identifier (e.g. `OBS_KEY_A` for key \"A\"). Available identifiers [here](https://github.com/obsproject/obs-studio/blob/master/libobs/obs-hotkeys.h)", + "{Object (Optional)} `keyModifiers` Optional key modifiers object. False entries can be ommitted", + "{boolean} `keyModifiers.shift` Trigger Shift Key", + "{boolean} `keyModifiers.alt` Trigger Alt Key", + "{boolean} `keyModifiers.control` Trigger Control (Ctrl) Key", + "{boolean} `keyModifiers.command` Trigger Command Key (Mac)" ], "api": "requests", - "name": "StopOutput", - "category": "outputs", - "since": "4.7.0", + "name": "TriggerHotkeyBySequence", + "category": "general", + "since": "4.9.0", "params": [ { "type": "String", - "name": "outputName", - "description": "Output name" + "name": "keyId", + "description": "Main key identifier (e.g. `OBS_KEY_A` for key \"A\"). Available identifiers [here](https://github.com/obsproject/obs-studio/blob/master/libobs/obs-hotkeys.h)" }, { - "type": "boolean (optional)", - "name": "force", - "description": "Force stop (default: false)" - } - ], - "names": [ - { - "name": "", - "description": "StopOutput" - } - ], - "categories": [ - { - "name": "", - "description": "outputs" - } - ], - "sinces": [ - { - "name": "", - "description": "4.7.0" - } - ], - "heading": { - "level": 2, - "text": "StopOutput" - }, - "lead": "", - "type": "class", - "examples": [] - } - ], - "profiles": [ - { - "subheads": [], - "description": "Set the currently active profile.", - "param": "{String} `profile-name` Name of the desired profile.", - "api": "requests", - "name": "SetCurrentProfile", - "category": "profiles", - "since": "4.0.0", - "params": [ - { - "type": "String", - "name": "profile-name", - "description": "Name of the desired profile." - } - ], - "names": [ - { - "name": "", - "description": "SetCurrentProfile" - } - ], - "categories": [ - { - "name": "", - "description": "profiles" - } - ], - "sinces": [ - { - "name": "", - "description": "4.0.0" - } - ], - "heading": { - "level": 2, - "text": "SetCurrentProfile" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Get the name of the current profile.", - "return": "{String} `profile-name` Name of the currently active profile.", - "api": "requests", - "name": "GetCurrentProfile", - "category": "profiles", - "since": "4.0.0", - "returns": [ - { - "type": "String", - "name": "profile-name", - "description": "Name of the currently active profile." - } - ], - "names": [ - { - "name": "", - "description": "GetCurrentProfile" - } - ], - "categories": [ - { - "name": "", - "description": "profiles" - } - ], - "sinces": [ - { - "name": "", - "description": "4.0.0" - } - ], - "heading": { - "level": 2, - "text": "GetCurrentProfile" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Get a list of available profiles.", - "return": "{Array} `profiles` List of available profiles.", - "api": "requests", - "name": "ListProfiles", - "category": "profiles", - "since": "4.0.0", - "returns": [ - { - "type": "Array", - "name": "profiles", - "description": "List of available profiles." - } - ], - "names": [ - { - "name": "", - "description": "ListProfiles" - } - ], - "categories": [ - { - "name": "", - "description": "profiles" - } - ], - "sinces": [ - { - "name": "", - "description": "4.0.0" - } - ], - "heading": { - "level": 2, - "text": "ListProfiles" - }, - "lead": "", - "type": "class", - "examples": [] - } - ], - "recording": [ - { - "subheads": [], - "description": "Toggle recording on or off.", - "api": "requests", - "name": "StartStopRecording", - "category": "recording", - "since": "0.3", - "names": [ - { - "name": "", - "description": "StartStopRecording" - } - ], - "categories": [ - { - "name": "", - "description": "recording" - } - ], - "sinces": [ - { - "name": "", - "description": "0.3" - } - ], - "heading": { - "level": 2, - "text": "StartStopRecording" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Start recording.\nWill return an `error` if recording is already active.", - "api": "requests", - "name": "StartRecording", - "category": "recording", - "since": "4.1.0", - "names": [ - { - "name": "", - "description": "StartRecording" - } - ], - "categories": [ - { - "name": "", - "description": "recording" - } - ], - "sinces": [ - { - "name": "", - "description": "4.1.0" - } - ], - "heading": { - "level": 2, - "text": "StartRecording" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Stop recording.\nWill return an `error` if recording is not active.", - "api": "requests", - "name": "StopRecording", - "category": "recording", - "since": "4.1.0", - "names": [ - { - "name": "", - "description": "StopRecording" - } - ], - "categories": [ - { - "name": "", - "description": "recording" - } - ], - "sinces": [ - { - "name": "", - "description": "4.1.0" - } - ], - "heading": { - "level": 2, - "text": "StopRecording" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Pause the current recording.\nReturns an error if recording is not active or already paused.", - "api": "requests", - "name": "PauseRecording", - "category": "recording", - "since": "4.7.0", - "names": [ - { - "name": "", - "description": "PauseRecording" - } - ], - "categories": [ - { - "name": "", - "description": "recording" - } - ], - "sinces": [ - { - "name": "", - "description": "4.7.0" - } - ], - "heading": { - "level": 2, - "text": "PauseRecording" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Resume/unpause the current recording (if paused).\nReturns an error if recording is not active or not paused.", - "api": "requests", - "name": "ResumeRecording", - "category": "recording", - "since": "4.7.0", - "names": [ - { - "name": "", - "description": "ResumeRecording" - } - ], - "categories": [ - { - "name": "", - "description": "recording" - } - ], - "sinces": [ - { - "name": "", - "description": "4.7.0" - } - ], - "heading": { - "level": 2, - "text": "ResumeRecording" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "\n\nPlease note: if `SetRecordingFolder` is called while a recording is\nin progress, the change won't be applied immediately and will be\neffective on the next recording.", - "param": "{String} `rec-folder` Path of the recording folder.", - "api": "requests", - "name": "SetRecordingFolder", - "category": "recording", - "since": "4.1.0", - "params": [ - { - "type": "String", - "name": "rec-folder", - "description": "Path of the recording folder." - } - ], - "names": [ - { - "name": "", - "description": "SetRecordingFolder" - } - ], - "categories": [ - { - "name": "", - "description": "recording" - } - ], - "sinces": [ - { - "name": "", - "description": "4.1.0" - } - ], - "heading": { - "level": 2, - "text": "SetRecordingFolder" - }, - "lead": "In the current profile, sets the recording folder of the Simple and Advanced output modes to the specified value.", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Get the path of the current recording folder.", - "return": "{String} `rec-folder` Path of the recording folder.", - "api": "requests", - "name": "GetRecordingFolder", - "category": "recording", - "since": "4.1.0", - "returns": [ - { - "type": "String", - "name": "rec-folder", - "description": "Path of the recording folder." - } - ], - "names": [ - { - "name": "", - "description": "GetRecordingFolder" - } - ], - "categories": [ - { - "name": "", - "description": "recording" - } - ], - "sinces": [ - { - "name": "", - "description": "4.1.0" - } - ], - "heading": { - "level": 2, - "text": "GetRecordingFolder" - }, - "lead": "", - "type": "class", - "examples": [] - } - ], - "replay buffer": [ - { - "subheads": [], - "description": "Toggle the Replay Buffer on/off.", - "api": "requests", - "name": "StartStopReplayBuffer", - "category": "replay buffer", - "since": "4.2.0", - "names": [ - { - "name": "", - "description": "StartStopReplayBuffer" - } - ], - "categories": [ - { - "name": "", - "description": "replay buffer" - } - ], - "sinces": [ - { - "name": "", - "description": "4.2.0" - } - ], - "heading": { - "level": 2, - "text": "StartStopReplayBuffer" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Start recording into the Replay Buffer.\nWill return an `error` if the Replay Buffer is already active or if the\n\"Save Replay Buffer\" hotkey is not set in OBS' settings.\nSetting this hotkey is mandatory, even when triggering saves only\nthrough obs-websocket.", - "api": "requests", - "name": "StartReplayBuffer", - "category": "replay buffer", - "since": "4.2.0", - "names": [ - { - "name": "", - "description": "StartReplayBuffer" - } - ], - "categories": [ - { - "name": "", - "description": "replay buffer" - } - ], - "sinces": [ - { - "name": "", - "description": "4.2.0" - } - ], - "heading": { - "level": 2, - "text": "StartReplayBuffer" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Stop recording into the Replay Buffer.\nWill return an `error` if the Replay Buffer is not active.", - "api": "requests", - "name": "StopReplayBuffer", - "category": "replay buffer", - "since": "4.2.0", - "names": [ - { - "name": "", - "description": "StopReplayBuffer" - } - ], - "categories": [ - { - "name": "", - "description": "replay buffer" - } - ], - "sinces": [ - { - "name": "", - "description": "4.2.0" - } - ], - "heading": { - "level": 2, - "text": "StopReplayBuffer" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Flush and save the contents of the Replay Buffer to disk. This is\nbasically the same as triggering the \"Save Replay Buffer\" hotkey.\nWill return an `error` if the Replay Buffer is not active.", - "api": "requests", - "name": "SaveReplayBuffer", - "category": "replay buffer", - "since": "4.2.0", - "names": [ - { - "name": "", - "description": "SaveReplayBuffer" - } - ], - "categories": [ - { - "name": "", - "description": "replay buffer" - } - ], - "sinces": [ - { - "name": "", - "description": "4.2.0" - } - ], - "heading": { - "level": 2, - "text": "SaveReplayBuffer" - }, - "lead": "", - "type": "class", - "examples": [] - } - ], - "scene collections": [ - { - "subheads": [], - "description": "Change the active scene collection.", - "param": "{String} `sc-name` Name of the desired scene collection.", - "api": "requests", - "name": "SetCurrentSceneCollection", - "category": "scene collections", - "since": "4.0.0", - "params": [ - { - "type": "String", - "name": "sc-name", - "description": "Name of the desired scene collection." - } - ], - "names": [ - { - "name": "", - "description": "SetCurrentSceneCollection" - } - ], - "categories": [ - { - "name": "", - "description": "scene collections" - } - ], - "sinces": [ - { - "name": "", - "description": "4.0.0" - } - ], - "heading": { - "level": 2, - "text": "SetCurrentSceneCollection" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Get the name of the current scene collection.", - "return": "{String} `sc-name` Name of the currently active scene collection.", - "api": "requests", - "name": "GetCurrentSceneCollection", - "category": "scene collections", - "since": "4.0.0", - "returns": [ - { - "type": "String", - "name": "sc-name", - "description": "Name of the currently active scene collection." - } - ], - "names": [ - { - "name": "", - "description": "GetCurrentSceneCollection" - } - ], - "categories": [ - { - "name": "", - "description": "scene collections" - } - ], - "sinces": [ - { - "name": "", - "description": "4.0.0" - } - ], - "heading": { - "level": 2, - "text": "GetCurrentSceneCollection" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "List available scene collections", - "return": "{Array} `scene-collections` Scene collections list", - "api": "requests", - "name": "ListSceneCollections", - "category": "scene collections", - "since": "4.0.0", - "returns": [ - { - "type": "Array", - "name": "scene-collections", - "description": "Scene collections list" - } - ], - "names": [ - { - "name": "", - "description": "ListSceneCollections" - } - ], - "categories": [ - { - "name": "", - "description": "scene collections" - } - ], - "sinces": [ - { - "name": "", - "description": "4.0.0" - } - ], - "heading": { - "level": 2, - "text": "ListSceneCollections" - }, - "lead": "", - "type": "class", - "examples": [] - } - ], - "scene items": [ - { - "subheads": [], - "description": "Gets the scene specific properties of the specified source item.\nCoordinates are relative to the item's parent (the scene or group it belongs to).", - "param": [ - "{String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.", - "{String} `item` The name of the source." - ], - "return": [ - "{String} `name` The name of the source.", - "{int} `position.x` The x position of the source from the left.", - "{int} `position.y` The y position of the source from the top.", - "{int} `position.alignment` The point on the source that the item is manipulated from.", - "{double} `rotation` The clockwise rotation of the item in degrees around the point of alignment.", - "{double} `scale.x` The x-scale factor of the source.", - "{double} `scale.y` The y-scale factor of the source.", - "{int} `crop.top` The number of pixels cropped off the top of the source before scaling.", - "{int} `crop.right` The number of pixels cropped off the right of the source before scaling.", - "{int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling.", - "{int} `crop.left` The number of pixels cropped off the left of the source before scaling.", - "{bool} `visible` If the source is visible.", - "{bool} `muted` If the source is muted.", - "{bool} `locked` If the source's transform is locked.", - "{String} `bounds.type` Type of bounding box. Can be \"OBS_BOUNDS_STRETCH\", \"OBS_BOUNDS_SCALE_INNER\", \"OBS_BOUNDS_SCALE_OUTER\", \"OBS_BOUNDS_SCALE_TO_WIDTH\", \"OBS_BOUNDS_SCALE_TO_HEIGHT\", \"OBS_BOUNDS_MAX_ONLY\" or \"OBS_BOUNDS_NONE\".", - "{int} `bounds.alignment` Alignment of the bounding box.", - "{double} `bounds.x` Width of the bounding box.", - "{double} `bounds.y` Height of the bounding box.", - "{int} `sourceWidth` Base width (without scaling) of the source", - "{int} `sourceHeight` Base source (without scaling) of the source", - "{double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)", - "{double} `height` Scene item height (base source height multiplied by the vertical scaling factor)", - "{int} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.", - "{String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)", - "{Array (optional)} `groupChildren` List of children (if this item is a group)" - ], - "api": "requests", - "name": "GetSceneItemProperties", - "category": "scene items", - "since": "4.3.0", - "returns": [ - { - "type": "String", - "name": "name", - "description": "The name of the source." - }, - { - "type": "int", - "name": "position.x", - "description": "The x position of the source from the left." - }, - { - "type": "int", - "name": "position.y", - "description": "The y position of the source from the top." - }, - { - "type": "int", - "name": "position.alignment", - "description": "The point on the source that the item is manipulated from." - }, - { - "type": "double", - "name": "rotation", - "description": "The clockwise rotation of the item in degrees around the point of alignment." - }, - { - "type": "double", - "name": "scale.x", - "description": "The x-scale factor of the source." - }, - { - "type": "double", - "name": "scale.y", - "description": "The y-scale factor of the source." - }, - { - "type": "int", - "name": "crop.top", - "description": "The number of pixels cropped off the top of the source before scaling." - }, - { - "type": "int", - "name": "crop.right", - "description": "The number of pixels cropped off the right of the source before scaling." - }, - { - "type": "int", - "name": "crop.bottom", - "description": "The number of pixels cropped off the bottom of the source before scaling." - }, - { - "type": "int", - "name": "crop.left", - "description": "The number of pixels cropped off the left of the source before scaling." - }, - { - "type": "bool", - "name": "visible", - "description": "If the source is visible." - }, - { - "type": "bool", - "name": "muted", - "description": "If the source is muted." - }, - { - "type": "bool", - "name": "locked", - "description": "If the source's transform is locked." - }, - { - "type": "String", - "name": "bounds.type", - "description": "Type of bounding box. Can be \"OBS_BOUNDS_STRETCH\", \"OBS_BOUNDS_SCALE_INNER\", \"OBS_BOUNDS_SCALE_OUTER\", \"OBS_BOUNDS_SCALE_TO_WIDTH\", \"OBS_BOUNDS_SCALE_TO_HEIGHT\", \"OBS_BOUNDS_MAX_ONLY\" or \"OBS_BOUNDS_NONE\"." - }, - { - "type": "int", - "name": "bounds.alignment", - "description": "Alignment of the bounding box." - }, - { - "type": "double", - "name": "bounds.x", - "description": "Width of the bounding box." - }, - { - "type": "double", - "name": "bounds.y", - "description": "Height of the bounding box." - }, - { - "type": "int", - "name": "sourceWidth", - "description": "Base width (without scaling) of the source" - }, - { - "type": "int", - "name": "sourceHeight", - "description": "Base source (without scaling) of the source" - }, - { - "type": "double", - "name": "width", - "description": "Scene item width (base source width multiplied by the horizontal scaling factor)" - }, - { - "type": "double", - "name": "height", - "description": "Scene item height (base source height multiplied by the vertical scaling factor)" - }, - { - "type": "int", - "name": "alignment", - "description": "The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis." - }, - { - "type": "String (optional)", - "name": "parentGroupName", - "description": "Name of the item's parent (if this item belongs to a group)" - }, - { - "type": "Array (optional)", - "name": "groupChildren", - "description": "List of children (if this item is a group)" - } - ], - "params": [ - { - "type": "String (optional)", - "name": "scene-name", - "description": "the name of the scene that the source item belongs to. Defaults to the current scene." - }, - { - "type": "String", - "name": "item", - "description": "The name of the source." - } - ], - "names": [ - { - "name": "", - "description": "GetSceneItemProperties" - } - ], - "categories": [ - { - "name": "", - "description": "scene items" - } - ], - "sinces": [ - { - "name": "", - "description": "4.3.0" - } - ], - "heading": { - "level": 2, - "text": "GetSceneItemProperties" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Sets the scene specific properties of a source. Unspecified properties will remain unchanged.\nCoordinates are relative to the item's parent (the scene or group it belongs to).", - "param": [ - "{String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.", - "{String} `item` The name of the source.", - "{int (optional)} `position.x` The new x position of the source.", - "{int (optional)} `position.y` The new y position of the source.", - "{int (optional)} `position.alignment` The new alignment of the source.", - "{double (optional)} `rotation` The new clockwise rotation of the item in degrees.", - "{double (optional)} `scale.x` The new x scale of the item.", - "{double (optional)} `scale.y` The new y scale of the item.", - "{int (optional)} `crop.top` The new amount of pixels cropped off the top of the source before scaling.", - "{int (optional)} `crop.bottom` The new amount of pixels cropped off the bottom of the source before scaling.", - "{int (optional)} `crop.left` The new amount of pixels cropped off the left of the source before scaling.", - "{int (optional)} `crop.right` The new amount of pixels cropped off the right of the source before scaling.", - "{bool (optional)} `visible` The new visibility of the source. 'true' shows source, 'false' hides source.", - "{bool (optional)} `locked` The new locked status of the source. 'true' keeps it in its current position, 'false' allows movement.", - "{String (optional)} `bounds.type` The new bounds type of the source. Can be \"OBS_BOUNDS_STRETCH\", \"OBS_BOUNDS_SCALE_INNER\", \"OBS_BOUNDS_SCALE_OUTER\", \"OBS_BOUNDS_SCALE_TO_WIDTH\", \"OBS_BOUNDS_SCALE_TO_HEIGHT\", \"OBS_BOUNDS_MAX_ONLY\" or \"OBS_BOUNDS_NONE\".", - "{int (optional)} `bounds.alignment` The new alignment of the bounding box. (0-2, 4-6, 8-10)", - "{double (optional)} `bounds.x` The new width of the bounding box.", - "{double (optional)} `bounds.y` The new height of the bounding box." - ], - "api": "requests", - "name": "SetSceneItemProperties", - "category": "scene items", - "since": "4.3.0", - "params": [ - { - "type": "String (optional)", - "name": "scene-name", - "description": "the name of the scene that the source item belongs to. Defaults to the current scene." - }, - { - "type": "String", - "name": "item", - "description": "The name of the source." - }, - { - "type": "int (optional)", - "name": "position.x", - "description": "The new x position of the source." - }, - { - "type": "int (optional)", - "name": "position.y", - "description": "The new y position of the source." - }, - { - "type": "int (optional)", - "name": "position.alignment", - "description": "The new alignment of the source." - }, - { - "type": "double (optional)", - "name": "rotation", - "description": "The new clockwise rotation of the item in degrees." - }, - { - "type": "double (optional)", - "name": "scale.x", - "description": "The new x scale of the item." - }, - { - "type": "double (optional)", - "name": "scale.y", - "description": "The new y scale of the item." - }, - { - "type": "int (optional)", - "name": "crop.top", - "description": "The new amount of pixels cropped off the top of the source before scaling." - }, - { - "type": "int (optional)", - "name": "crop.bottom", - "description": "The new amount of pixels cropped off the bottom of the source before scaling." - }, - { - "type": "int (optional)", - "name": "crop.left", - "description": "The new amount of pixels cropped off the left of the source before scaling." - }, - { - "type": "int (optional)", - "name": "crop.right", - "description": "The new amount of pixels cropped off the right of the source before scaling." - }, - { - "type": "bool (optional)", - "name": "visible", - "description": "The new visibility of the source. 'true' shows source, 'false' hides source." - }, - { - "type": "bool (optional)", - "name": "locked", - "description": "The new locked status of the source. 'true' keeps it in its current position, 'false' allows movement." - }, - { - "type": "String (optional)", - "name": "bounds.type", - "description": "The new bounds type of the source. Can be \"OBS_BOUNDS_STRETCH\", \"OBS_BOUNDS_SCALE_INNER\", \"OBS_BOUNDS_SCALE_OUTER\", \"OBS_BOUNDS_SCALE_TO_WIDTH\", \"OBS_BOUNDS_SCALE_TO_HEIGHT\", \"OBS_BOUNDS_MAX_ONLY\" or \"OBS_BOUNDS_NONE\"." - }, - { - "type": "int (optional)", - "name": "bounds.alignment", - "description": "The new alignment of the bounding box. (0-2, 4-6, 8-10)" - }, - { - "type": "double (optional)", - "name": "bounds.x", - "description": "The new width of the bounding box." - }, - { - "type": "double (optional)", - "name": "bounds.y", - "description": "The new height of the bounding box." - } - ], - "names": [ - { - "name": "", - "description": "SetSceneItemProperties" - } - ], - "categories": [ - { - "name": "", - "description": "scene items" - } - ], - "sinces": [ - { - "name": "", - "description": "4.3.0" - } - ], - "heading": { - "level": 2, - "text": "SetSceneItemProperties" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Reset a scene item.", - "param": [ - "{String (optional)} `scene-name` Name of the scene the source belongs to. Defaults to the current scene.", - "{String} `item` Name of the source item." - ], - "api": "requests", - "name": "ResetSceneItem", - "category": "scene items", - "since": "4.2.0", - "params": [ - { - "type": "String (optional)", - "name": "scene-name", - "description": "Name of the scene the source belongs to. Defaults to the current scene." - }, - { - "type": "String", - "name": "item", - "description": "Name of the source item." - } - ], - "names": [ - { - "name": "", - "description": "ResetSceneItem" - } - ], - "categories": [ - { - "name": "", - "description": "scene items" - } - ], - "sinces": [ - { - "name": "", - "description": "4.2.0" - } - ], - "heading": { - "level": 2, - "text": "ResetSceneItem" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Show or hide a specified source item in a specified scene.", - "param": [ - "{String} `source` Scene item name in the specified scene.", - "{boolean} `render` true = shown ; false = hidden", - "{String (optional)} `scene-name` Name of the scene where the source resides. Defaults to the currently active scene." - ], - "api": "requests", - "name": "SetSceneItemRender", - "category": "scene items", - "since": "0.3", - "deprecated": "Since 4.3.0. Prefer the use of SetSceneItemProperties.", - "params": [ - { - "type": "String", - "name": "source", - "description": "Scene item name in the specified scene." + "type": "Object (Optional)", + "name": "keyModifiers", + "description": "Optional key modifiers object. False entries can be ommitted" }, { "type": "boolean", - "name": "render", - "description": "true = shown ; false = hidden" + "name": "keyModifiers.shift", + "description": "Trigger Shift Key" }, { - "type": "String (optional)", - "name": "scene-name", - "description": "Name of the scene where the source resides. Defaults to the currently active scene." + "type": "boolean", + "name": "keyModifiers.alt", + "description": "Trigger Alt Key" + }, + { + "type": "boolean", + "name": "keyModifiers.control", + "description": "Trigger Control (Ctrl) Key" + }, + { + "type": "boolean", + "name": "keyModifiers.command", + "description": "Trigger Command Key (Mac)" } ], "names": [ { "name": "", - "description": "SetSceneItemRender" + "description": "TriggerHotkeyBySequence" } ], "categories": [ { "name": "", - "description": "scene items" + "description": "general" } ], "sinces": [ { "name": "", - "description": "0.3" - } - ], - "deprecateds": [ - { - "name": "", - "description": "Since 4.3.0. Prefer the use of SetSceneItemProperties." + "description": "4.9.0" } ], "heading": { "level": 2, - "text": "SetSceneItemRender" + "text": "TriggerHotkeyBySequence" }, "lead": "", "type": "class", @@ -4840,579 +4315,589 @@ }, { "subheads": [], - "description": "Sets the coordinates of a specified source item.", + "description": "Executes a list of requests sequentially (one-by-one on the same thread).", "param": [ - "{String (optional)} `scene-name` The name of the scene that the source item belongs to. Defaults to the current scene.", - "{String} `item` The name of the source item.", - "{double} `x` X coordinate.", - "{double} `y` Y coordinate." - ], - "api": "requests", - "name": "SetSceneItemPosition", - "category": "scene items", - "since": "4.0.0", - "deprecated": "Since 4.3.0. Prefer the use of SetSceneItemProperties.", - "params": [ - { - "type": "String (optional)", - "name": "scene-name", - "description": "The name of the scene that the source item belongs to. Defaults to the current scene." - }, - { - "type": "String", - "name": "item", - "description": "The name of the source item." - }, - { - "type": "double", - "name": "x", - "description": "X coordinate." - }, - { - "type": "double", - "name": "y", - "description": "Y coordinate." - } - ], - "names": [ - { - "name": "", - "description": "SetSceneItemPosition" - } - ], - "categories": [ - { - "name": "", - "description": "scene items" - } - ], - "sinces": [ - { - "name": "", - "description": "4.0.0" - } - ], - "deprecateds": [ - { - "name": "", - "description": "Since 4.3.0. Prefer the use of SetSceneItemProperties." - } - ], - "heading": { - "level": 2, - "text": "SetSceneItemPosition" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Set the transform of the specified source item.", - "param": [ - "{String (optional)} `scene-name` The name of the scene that the source item belongs to. Defaults to the current scene.", - "{String} `item` The name of the source item.", - "{double} `x-scale` Width scale factor.", - "{double} `y-scale` Height scale factor.", - "{double} `rotation` Source item rotation (in degrees)." - ], - "api": "requests", - "name": "SetSceneItemTransform", - "category": "scene items", - "since": "4.0.0", - "deprecated": "Since 4.3.0. Prefer the use of SetSceneItemProperties.", - "params": [ - { - "type": "String (optional)", - "name": "scene-name", - "description": "The name of the scene that the source item belongs to. Defaults to the current scene." - }, - { - "type": "String", - "name": "item", - "description": "The name of the source item." - }, - { - "type": "double", - "name": "x-scale", - "description": "Width scale factor." - }, - { - "type": "double", - "name": "y-scale", - "description": "Height scale factor." - }, - { - "type": "double", - "name": "rotation", - "description": "Source item rotation (in degrees)." - } - ], - "names": [ - { - "name": "", - "description": "SetSceneItemTransform" - } - ], - "categories": [ - { - "name": "", - "description": "scene items" - } - ], - "sinces": [ - { - "name": "", - "description": "4.0.0" - } - ], - "deprecateds": [ - { - "name": "", - "description": "Since 4.3.0. Prefer the use of SetSceneItemProperties." - } - ], - "heading": { - "level": 2, - "text": "SetSceneItemTransform" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Sets the crop coordinates of the specified source item.", - "param": [ - "{String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene.", - "{String} `item` The name of the source.", - "{int} `top` Pixel position of the top of the source item.", - "{int} `bottom` Pixel position of the bottom of the source item.", - "{int} `left` Pixel position of the left of the source item.", - "{int} `right` Pixel position of the right of the source item." - ], - "api": "requests", - "name": "SetSceneItemCrop", - "category": "scene items", - "since": "4.1.0", - "deprecated": "Since 4.3.0. Prefer the use of SetSceneItemProperties.", - "params": [ - { - "type": "String (optional)", - "name": "scene-name", - "description": "the name of the scene that the source item belongs to. Defaults to the current scene." - }, - { - "type": "String", - "name": "item", - "description": "The name of the source." - }, - { - "type": "int", - "name": "top", - "description": "Pixel position of the top of the source item." - }, - { - "type": "int", - "name": "bottom", - "description": "Pixel position of the bottom of the source item." - }, - { - "type": "int", - "name": "left", - "description": "Pixel position of the left of the source item." - }, - { - "type": "int", - "name": "right", - "description": "Pixel position of the right of the source item." - } - ], - "names": [ - { - "name": "", - "description": "SetSceneItemCrop" - } - ], - "categories": [ - { - "name": "", - "description": "scene items" - } - ], - "sinces": [ - { - "name": "", - "description": "4.1.0" - } - ], - "deprecateds": [ - { - "name": "", - "description": "Since 4.3.0. Prefer the use of SetSceneItemProperties." - } - ], - "heading": { - "level": 2, - "text": "SetSceneItemCrop" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Deletes a scene item.", - "param": [ - "{String (optional)} `scene` Name of the scene the source belongs to. Defaults to the current scene.", - "{Object} `item` item to delete (required)", - "{String} `item.name` name of the scene item (prefer `id`, including both is acceptable).", - "{int} `item.id` id of the scene item." - ], - "api": "requests", - "name": "DeleteSceneItem", - "category": "scene items", - "since": "4.5.0", - "params": [ - { - "type": "String (optional)", - "name": "scene", - "description": "Name of the scene the source belongs to. Defaults to the current scene." - }, - { - "type": "Object", - "name": "item", - "description": "item to delete (required)" - }, - { - "type": "String", - "name": "item.name", - "description": "name of the scene item (prefer `id`, including both is acceptable)." - }, - { - "type": "int", - "name": "item.id", - "description": "id of the scene item." - } - ], - "names": [ - { - "name": "", - "description": "DeleteSceneItem" - } - ], - "categories": [ - { - "name": "", - "description": "scene items" - } - ], - "sinces": [ - { - "name": "", - "description": "4.5.0" - } - ], - "heading": { - "level": 2, - "text": "DeleteSceneItem" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Duplicates a scene item.", - "param": [ - "{String (optional)} `fromScene` Name of the scene to copy the item from. Defaults to the current scene.", - "{String (optional)} `toScene` Name of the scene to create the item in. Defaults to the current scene.", - "{Object} `item` item to duplicate (required)", - "{String} `item.name` name of the scene item (prefer `id`, including both is acceptable).", - "{int} `item.id` id of the scene item." + "{Array} `requests` Array of requests to perform. Executed in order.", + "{String} `requests.*.request-type` Request type. Eg. `GetVersion`.", + "{String (Optional)} `requests.*.message-id` ID of the individual request. Can be any string and not required to be unique. Defaults to empty string if not specified.", + "{boolean (Optional)} `abortOnFail` Stop processing batch requests if one returns a failure." ], "return": [ - "{String} `scene` Name of the scene where the new item was created", - "{Object} `item` New item info", - "{int} `item.id` New item ID", - "{String} `item.name` New item name" + "{Array} `results` Batch requests results, ordered sequentially.", + "{String} `results.*.message-id` ID of the individual request which was originally provided by the client.", + "{String} `results.*.status` Status response as string. Either `ok` or `error`.", + "{String (Optional)} `results.*.error` Error message accompanying an `error` status." ], "api": "requests", - "name": "DuplicateSceneItem", - "category": "scene items", - "since": "4.5.0", + "name": "ExecuteBatch", + "category": "general", + "since": "4.9.0", "returns": [ { - "type": "String", - "name": "scene", - "description": "Name of the scene where the new item was created" - }, - { - "type": "Object", - "name": "item", - "description": "New item info" - }, - { - "type": "int", - "name": "item.id", - "description": "New item ID" + "type": "Array", + "name": "results", + "description": "Batch requests results, ordered sequentially." }, { "type": "String", - "name": "item.name", - "description": "New item name" + "name": "results.*.message-id", + "description": "ID of the individual request which was originally provided by the client." + }, + { + "type": "String", + "name": "results.*.status", + "description": "Status response as string. Either `ok` or `error`." + }, + { + "type": "String (Optional)", + "name": "results.*.error", + "description": "Error message accompanying an `error` status." } ], "params": [ { - "type": "String (optional)", - "name": "fromScene", - "description": "Name of the scene to copy the item from. Defaults to the current scene." - }, - { - "type": "String (optional)", - "name": "toScene", - "description": "Name of the scene to create the item in. Defaults to the current scene." - }, - { - "type": "Object", - "name": "item", - "description": "item to duplicate (required)" + "type": "Array", + "name": "requests", + "description": "Array of requests to perform. Executed in order." }, { "type": "String", - "name": "item.name", - "description": "name of the scene item (prefer `id`, including both is acceptable)." + "name": "requests.*.request-type", + "description": "Request type. Eg. `GetVersion`." }, { - "type": "int", - "name": "item.id", - "description": "id of the scene item." + "type": "String (Optional)", + "name": "requests.*.message-id", + "description": "ID of the individual request. Can be any string and not required to be unique. Defaults to empty string if not specified." + }, + { + "type": "boolean (Optional)", + "name": "abortOnFail", + "description": "Stop processing batch requests if one returns a failure." } ], "names": [ { "name": "", - "description": "DuplicateSceneItem" + "description": "ExecuteBatch" } ], "categories": [ { "name": "", - "description": "scene items" + "description": "general" } ], "sinces": [ { "name": "", - "description": "4.5.0" + "description": "4.9.0" } ], "heading": { "level": 2, - "text": "DuplicateSceneItem" + "text": "ExecuteBatch" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Waits for the specified duration. Designed to be used in `ExecuteBatch` operations.", + "param": "{int} `sleepMillis` Delay in milliseconds to wait before continuing.", + "api": "requests", + "name": "Sleep", + "category": "general", + "since": "unreleased", + "params": [ + { + "type": "int", + "name": "sleepMillis", + "description": "Delay in milliseconds to wait before continuing." + } + ], + "names": [ + { + "name": "", + "description": "Sleep" + } + ], + "categories": [ + { + "name": "", + "description": "general" + } + ], + "sinces": [ + { + "name": "", + "description": "unreleased" + } + ], + "heading": { + "level": 2, + "text": "Sleep" }, "lead": "", "type": "class", "examples": [] } ], - "scenes": [ + "media control": [ { "subheads": [], - "description": "Switch to the specified scene.", - "param": "{String} `scene-name` Name of the scene to switch to.", - "api": "requests", - "name": "SetCurrentScene", - "category": "scenes", - "since": "0.3", - "params": [ - { - "type": "String", - "name": "scene-name", - "description": "Name of the scene to switch to." - } - ], - "names": [ - { - "name": "", - "description": "SetCurrentScene" - } - ], - "categories": [ - { - "name": "", - "description": "scenes" - } - ], - "sinces": [ - { - "name": "", - "description": "0.3" - } - ], - "heading": { - "level": 2, - "text": "SetCurrentScene" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Get the current scene's name and source items.", - "return": [ - "{String} `name` Name of the currently active scene.", - "{Array} `sources` Ordered list of the current scene's source items." - ], - "api": "requests", - "name": "GetCurrentScene", - "category": "scenes", - "since": "0.3", - "returns": [ - { - "type": "String", - "name": "name", - "description": "Name of the currently active scene." - }, - { - "type": "Array", - "name": "sources", - "description": "Ordered list of the current scene's source items." - } - ], - "names": [ - { - "name": "", - "description": "GetCurrentScene" - } - ], - "categories": [ - { - "name": "", - "description": "scenes" - } - ], - "sinces": [ - { - "name": "", - "description": "0.3" - } - ], - "heading": { - "level": 2, - "text": "GetCurrentScene" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Get a list of scenes in the currently active profile.", - "return": [ - "{String} `current-scene` Name of the currently active scene.", - "{Array} `scenes` Ordered list of the current profile's scenes (See `[GetCurrentScene](#getcurrentscene)` for more information)." - ], - "api": "requests", - "name": "GetSceneList", - "category": "scenes", - "since": "0.3", - "returns": [ - { - "type": "String", - "name": "current-scene", - "description": "Name of the currently active scene." - }, - { - "type": "Array", - "name": "scenes", - "description": "Ordered list of the current profile's scenes (See `[GetCurrentScene](#getcurrentscene)` for more information)." - } - ], - "names": [ - { - "name": "", - "description": "GetSceneList" - } - ], - "categories": [ - { - "name": "", - "description": "scenes" - } - ], - "sinces": [ - { - "name": "", - "description": "0.3" - } - ], - "heading": { - "level": 2, - "text": "GetSceneList" - }, - "lead": "", - "type": "class", - "examples": [] - }, - { - "subheads": [], - "description": "Changes the order of scene items in the requested scene.", + "description": "Pause or play a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)\nNote :Leaving out `playPause` toggles the current pause state", "param": [ - "{String (optional)} `scene` Name of the scene to reorder (defaults to current).", - "{Array} `items` Ordered list of objects with name and/or id specified. Id preferred due to uniqueness per scene", - "{int (optional)} `items[].id` Id of a specific scene item. Unique on a scene by scene basis.", - "{String (optional)} `items[].name` Name of a scene item. Sufficiently unique if no scene items share sources within the scene." + "{String} `sourceName` Source name.", + "{boolean} `playPause` (optional) Whether to pause or play the source. `false` for play, `true` for pause." ], "api": "requests", - "name": "ReorderSceneItems", - "category": "scenes", - "since": "4.5.0", + "name": "PlayPauseMedia", + "category": "media control", + "since": "4.9.0", "params": [ { - "type": "String (optional)", - "name": "scene", - "description": "Name of the scene to reorder (defaults to current)." + "type": "String", + "name": "sourceName", + "description": "Source name." }, { - "type": "Array", - "name": "items", - "description": "Ordered list of objects with name and/or id specified. Id preferred due to uniqueness per scene" - }, - { - "type": "int (optional)", - "name": "items[].id", - "description": "Id of a specific scene item. Unique on a scene by scene basis." - }, - { - "type": "String (optional)", - "name": "items[].name", - "description": "Name of a scene item. Sufficiently unique if no scene items share sources within the scene." + "type": "boolean", + "name": "playPause", + "description": "(optional) Whether to pause or play the source. `false` for play, `true` for pause." } ], "names": [ { "name": "", - "description": "ReorderSceneItems" + "description": "PlayPauseMedia" } ], "categories": [ { "name": "", - "description": "scenes" + "description": "media control" } ], "sinces": [ { "name": "", - "description": "4.5.0" + "description": "4.9.0" } ], "heading": { "level": 2, - "text": "ReorderSceneItems" + "text": "PlayPauseMedia" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Restart a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)", + "param": "{String} `sourceName` Source name.", + "api": "requests", + "name": "RestartMedia", + "category": "media control", + "since": "4.9.0", + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + } + ], + "names": [ + { + "name": "", + "description": "RestartMedia" + } + ], + "categories": [ + { + "name": "", + "description": "media control" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "RestartMedia" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Stop a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)", + "param": "{String} `sourceName` Source name.", + "api": "requests", + "name": "StopMedia", + "category": "media control", + "since": "4.9.0", + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + } + ], + "names": [ + { + "name": "", + "description": "StopMedia" + } + ], + "categories": [ + { + "name": "", + "description": "media control" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "StopMedia" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Skip to the next media item in the playlist. Supports only vlc media source (as of OBS v25.0.8)", + "param": "{String} `sourceName` Source name.", + "api": "requests", + "name": "NextMedia", + "category": "media control", + "since": "4.9.0", + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + } + ], + "names": [ + { + "name": "", + "description": "NextMedia" + } + ], + "categories": [ + { + "name": "", + "description": "media control" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "NextMedia" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Go to the previous media item in the playlist. Supports only vlc media source (as of OBS v25.0.8)", + "param": "{String} `sourceName` Source name.", + "api": "requests", + "name": "PreviousMedia", + "category": "media control", + "since": "4.9.0", + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + } + ], + "names": [ + { + "name": "", + "description": "PreviousMedia" + } + ], + "categories": [ + { + "name": "", + "description": "media control" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "PreviousMedia" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Get the length of media in milliseconds. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)\nNote: For some reason, for the first 5 or so seconds that the media is playing, the total duration can be off by upwards of 50ms.", + "param": "{String} `sourceName` Source name.", + "return": "{int} `mediaDuration` The total length of media in milliseconds..", + "api": "requests", + "name": "GetMediaDuration", + "category": "media control", + "since": "4.9.0", + "returns": [ + { + "type": "int", + "name": "mediaDuration", + "description": "The total length of media in milliseconds.." + } + ], + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + } + ], + "names": [ + { + "name": "", + "description": "GetMediaDuration" + } + ], + "categories": [ + { + "name": "", + "description": "media control" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "GetMediaDuration" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Get the current timestamp of media in milliseconds. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)", + "param": "{String} `sourceName` Source name.", + "return": "{int} `timestamp` The time in milliseconds since the start of the media.", + "api": "requests", + "name": "GetMediaTime", + "category": "media control", + "since": "4.9.0", + "returns": [ + { + "type": "int", + "name": "timestamp", + "description": "The time in milliseconds since the start of the media." + } + ], + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + } + ], + "names": [ + { + "name": "", + "description": "GetMediaTime" + } + ], + "categories": [ + { + "name": "", + "description": "media control" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "GetMediaTime" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Set the timestamp of a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)", + "param": [ + "{String} `sourceName` Source name.", + "{int} `timestamp` Milliseconds to set the timestamp to." + ], + "api": "requests", + "name": "SetMediaTime", + "category": "media control", + "since": "4.9.0", + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + }, + { + "type": "int", + "name": "timestamp", + "description": "Milliseconds to set the timestamp to." + } + ], + "names": [ + { + "name": "", + "description": "SetMediaTime" + } + ], + "categories": [ + { + "name": "", + "description": "media control" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "SetMediaTime" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Scrub media using a supplied offset. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)\nNote: Due to processing/network delays, this request is not perfect. The processing rate of this request has also not been tested.", + "param": [ + "{String} `sourceName` Source name.", + "{int} `timeOffset` Millisecond offset (positive or negative) to offset the current media position." + ], + "api": "requests", + "name": "ScrubMedia", + "category": "media control", + "since": "4.9.0", + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + }, + { + "type": "int", + "name": "timeOffset", + "description": "Millisecond offset (positive or negative) to offset the current media position." + } + ], + "names": [ + { + "name": "", + "description": "ScrubMedia" + } + ], + "categories": [ + { + "name": "", + "description": "media control" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "ScrubMedia" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Get the current playing state of a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8)", + "param": "{String} `sourceName` Source name.", + "return": "{String} `mediaState` The media state of the provided source. States: `none`, `playing`, `opening`, `buffering`, `paused`, `stopped`, `ended`, `error`, `unknown`", + "api": "requests", + "name": "GetMediaState", + "category": "media control", + "since": "4.9.0", + "returns": [ + { + "type": "String", + "name": "mediaState", + "description": "The media state of the provided source. States: `none`, `playing`, `opening`, `buffering`, `paused`, `stopped`, `ended`, `error`, `unknown`" + } + ], + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + } + ], + "names": [ + { + "name": "", + "description": "GetMediaState" + } + ], + "categories": [ + { + "name": "", + "description": "media control" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "GetMediaState" }, "lead": "", "type": "class", @@ -5420,13 +4905,149 @@ } ], "sources": [ + { + "subheads": [], + "description": "List the media state of all media sources (vlc and media source)", + "return": [ + "{Array} `mediaSources` Array of sources", + "{String} `mediaSources.*.sourceName` Unique source name", + "{String} `mediaSources.*.sourceKind` Unique source internal type (a.k.a `ffmpeg_source` or `vlc_source`)", + "{String} `mediaSources.*.mediaState` The current state of media for that source. States: `none`, `playing`, `opening`, `buffering`, `paused`, `stopped`, `ended`, `error`, `unknown`" + ], + "api": "requests", + "name": "GetMediaSourcesList", + "category": "sources", + "since": "4.9.0", + "returns": [ + { + "type": "Array", + "name": "mediaSources", + "description": "Array of sources" + }, + { + "type": "String", + "name": "mediaSources.*.sourceName", + "description": "Unique source name" + }, + { + "type": "String", + "name": "mediaSources.*.sourceKind", + "description": "Unique source internal type (a.k.a `ffmpeg_source` or `vlc_source`)" + }, + { + "type": "String", + "name": "mediaSources.*.mediaState", + "description": "The current state of media for that source. States: `none`, `playing`, `opening`, `buffering`, `paused`, `stopped`, `ended`, `error`, `unknown`" + } + ], + "names": [ + { + "name": "", + "description": "GetMediaSourcesList" + } + ], + "categories": [ + { + "name": "", + "description": "sources" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "GetMediaSourcesList" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Create a source and add it as a sceneitem to a scene.", + "param": [ + "{String} `sourceName` Source name.", + "{String} `sourceKind` Source kind, Eg. `vlc_source`.", + "{String} `sceneName` Scene to add the new source to.", + "{Object (optional)} `sourceSettings` Source settings data.", + "{boolean (optional)} `setVisible` Set the created SceneItem as visible or not. Defaults to true" + ], + "return": "{int} `itemId` ID of the SceneItem in the scene.", + "api": "requests", + "name": "CreateSource", + "category": "sources", + "since": "4.9.0", + "returns": [ + { + "type": "int", + "name": "itemId", + "description": "ID of the SceneItem in the scene." + } + ], + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + }, + { + "type": "String", + "name": "sourceKind", + "description": "Source kind, Eg. `vlc_source`." + }, + { + "type": "String", + "name": "sceneName", + "description": "Scene to add the new source to." + }, + { + "type": "Object (optional)", + "name": "sourceSettings", + "description": "Source settings data." + }, + { + "type": "boolean (optional)", + "name": "setVisible", + "description": "Set the created SceneItem as visible or not. Defaults to true" + } + ], + "names": [ + { + "name": "", + "description": "CreateSource" + } + ], + "categories": [ + { + "name": "", + "description": "sources" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "CreateSource" + }, + "lead": "", + "type": "class", + "examples": [] + }, { "subheads": [], "description": "List all sources available in the running OBS instance", "return": [ "{Array} `sources` Array of sources", "{String} `sources.*.name` Unique source name", - "{String} `sources.*.typeId` Non-unique source internal type (a.k.a type id)", + "{String} `sources.*.typeId` Non-unique source internal type (a.k.a kind)", "{String} `sources.*.type` Source type. Value is one of the following: \"input\", \"filter\", \"transition\", \"scene\" or \"unknown\"" ], "api": "requests", @@ -5447,7 +5068,7 @@ { "type": "String", "name": "sources.*.typeId", - "description": "Non-unique source internal type (a.k.a type id)" + "description": "Non-unique source internal type (a.k.a kind)" }, { "type": "String", @@ -5598,11 +5219,14 @@ }, { "subheads": [], - "description": "Get the volume of the specified source.", - "param": "{String} `source` Source name.", + "description": "Get the volume of the specified source. Default response uses mul format, NOT SLIDER PERCENTAGE.", + "param": [ + "{String} `source` Source name.", + "{boolean (optional)} `useDecibel` Output volume in decibels of attenuation instead of amplitude/mul." + ], "return": [ "{String} `name` Source name.", - "{double} `volume` Volume of the source. Between `0.0` and `1.0`.", + "{double} `volume` Volume of the source. Between `0.0` and `20.0` if using mul, under `26.0` if using dB.", "{boolean} `muted` Indicates whether the source is muted." ], "api": "requests", @@ -5618,7 +5242,7 @@ { "type": "double", "name": "volume", - "description": "Volume of the source. Between `0.0` and `1.0`." + "description": "Volume of the source. Between `0.0` and `20.0` if using mul, under `26.0` if using dB." }, { "type": "boolean", @@ -5631,6 +5255,11 @@ "type": "String", "name": "source", "description": "Source name." + }, + { + "type": "boolean (optional)", + "name": "useDecibel", + "description": "Output volume in decibels of attenuation instead of amplitude/mul." } ], "names": [ @@ -5661,10 +5290,11 @@ }, { "subheads": [], - "description": "Set the volume of the specified source.", + "description": "Set the volume of the specified source. Default request format uses mul, NOT SLIDER PERCENTAGE.", "param": [ "{String} `source` Source name.", - "{double} `volume` Desired volume. Must be between `0.0` and `1.0`." + "{double} `volume` Desired volume. Must be between `0.0` and `20.0` for mul, and under 26.0 for dB. OBS will interpret dB values under -100.0 as Inf. Note: The OBS volume sliders only reach a maximum of 1.0mul/0.0dB, however OBS actually supports larger values.", + "{boolean (optional)} `useDecibel` Interperet `volume` data as decibels instead of amplitude/mul." ], "api": "requests", "name": "SetVolume", @@ -5679,7 +5309,12 @@ { "type": "double", "name": "volume", - "description": "Desired volume. Must be between `0.0` and `1.0`." + "description": "Desired volume. Must be between `0.0` and `20.0` for mul, and under 26.0 for dB. OBS will interpret dB values under -100.0 as Inf. Note: The OBS volume sliders only reach a maximum of 1.0mul/0.0dB, however OBS actually supports larger values." + }, + { + "type": "boolean (optional)", + "name": "useDecibel", + "description": "Interperet `volume` data as decibels instead of amplitude/mul." } ], "names": [ @@ -5708,6 +5343,142 @@ "type": "class", "examples": [] }, + { + "subheads": [], + "description": "Changes whether an audio track is active for a source.", + "param": [ + "{String} `sourceName` Source name.", + "{int} `track` Audio tracks 1-6.", + "{boolean} `active` Whether audio track is active or not." + ], + "api": "requests", + "name": "SetTracks", + "category": "sources", + "since": "unreleased", + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + }, + { + "type": "int", + "name": "track", + "description": "Audio tracks 1-6." + }, + { + "type": "boolean", + "name": "active", + "description": "Whether audio track is active or not." + } + ], + "names": [ + { + "name": "", + "description": "SetTracks" + } + ], + "categories": [ + { + "name": "", + "description": "sources" + } + ], + "sinces": [ + { + "name": "", + "description": "unreleased" + } + ], + "heading": { + "level": 2, + "text": "SetTracks" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Gets whether an audio track is active for a source.", + "param": "{String} `sourceName` Source name.", + "return": [ + "{boolean} `track1`", + "{boolean} `track2`", + "{boolean} `track3`", + "{boolean} `track4`", + "{boolean} `track5`", + "{boolean} `track6`" + ], + "api": "requests", + "name": "GetTracks", + "category": "sources", + "since": "unreleased", + "returns": [ + { + "type": "boolean", + "name": "track1", + "description": "" + }, + { + "type": "boolean", + "name": "track2", + "description": "" + }, + { + "type": "boolean", + "name": "track3", + "description": "" + }, + { + "type": "boolean", + "name": "track4", + "description": "" + }, + { + "type": "boolean", + "name": "track5", + "description": "" + }, + { + "type": "boolean", + "name": "track6", + "description": "" + } + ], + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + } + ], + "names": [ + { + "name": "", + "description": "GetTracks" + } + ], + "categories": [ + { + "name": "", + "description": "sources" + } + ], + "sinces": [ + { + "name": "", + "description": "unreleased" + } + ], + "heading": { + "level": 2, + "text": "GetTracks" + }, + "lead": "", + "type": "class", + "examples": [] + }, { "subheads": [], "description": "Get the mute status of a specified source.", @@ -5855,6 +5626,153 @@ "type": "class", "examples": [] }, + { + "subheads": [], + "description": "Get the source's active status of a specified source (if it is showing in the final mix).", + "param": "{String} `sourceName` Source name.", + "return": "{boolean} `sourceActive` Source active status of the source.", + "api": "requests", + "name": "GetSourceActive", + "category": "sources", + "since": "unreleased", + "returns": [ + { + "type": "boolean", + "name": "sourceActive", + "description": "Source active status of the source." + } + ], + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + } + ], + "names": [ + { + "name": "", + "description": "GetSourceActive" + } + ], + "categories": [ + { + "name": "", + "description": "sources" + } + ], + "sinces": [ + { + "name": "", + "description": "unreleased" + } + ], + "heading": { + "level": 2, + "text": "GetSourceActive" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Get the audio's active status of a specified source.", + "param": "{String} `sourceName` Source name.", + "return": "{boolean} `audioActive` Audio active status of the source.", + "api": "requests", + "name": "GetAudioActive", + "category": "sources", + "since": "4.9.0", + "returns": [ + { + "type": "boolean", + "name": "audioActive", + "description": "Audio active status of the source." + } + ], + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + } + ], + "names": [ + { + "name": "", + "description": "GetAudioActive" + } + ], + "categories": [ + { + "name": "", + "description": "sources" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "GetAudioActive" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "\n\nNote: If the new name already exists as a source, obs-websocket will return an error.", + "param": [ + "{String} `sourceName` Source name.", + "{String} `newName` New source name." + ], + "api": "requests", + "name": "SetSourceName", + "category": "sources", + "since": "4.8.0", + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + }, + { + "type": "String", + "name": "newName", + "description": "New source name." + } + ], + "names": [ + { + "name": "", + "description": "SetSourceName" + } + ], + "categories": [ + { + "name": "", + "description": "sources" + } + ], + "sinces": [ + { + "name": "", + "description": "4.8.0" + } + ], + "heading": { + "level": 2, + "text": "SetSourceName" + }, + "lead": "Sets (aka rename) the name of a source. Also works with scenes since scenes are technically sources in OBS.", + "type": "class", + "examples": [] + }, { "subheads": [], "description": "Set the audio sync offset of a specified source.", @@ -6116,8 +6034,8 @@ "return": [ "{String} `source` Source name.", "{String} `align` Text Alignment (\"left\", \"center\", \"right\").", - "{int} `bk-color` Background color.", - "{int} `bk-opacity` Background opacity (0-100).", + "{int} `bk_color` Background color.", + "{int} `bk_opacity` Background opacity (0-100).", "{boolean} `chatlog` Chat log.", "{int} `chatlog_lines` Chat log lines.", "{int} `color` Text color.", @@ -6160,12 +6078,12 @@ }, { "type": "int", - "name": "bk-color", + "name": "bk_color", "description": "Background color." }, { "type": "int", - "name": "bk-opacity", + "name": "bk_opacity", "description": "Background opacity (0-100)." }, { @@ -6328,8 +6246,8 @@ "param": [ "{String} `source` Name of the source.", "{String (optional)} `align` Text Alignment (\"left\", \"center\", \"right\").", - "{int (optional)} `bk-color` Background color.", - "{int (optional)} `bk-opacity` Background opacity (0-100).", + "{int (optional)} `bk_color` Background color.", + "{int (optional)} `bk_opacity` Background opacity (0-100).", "{boolean (optional)} `chatlog` Chat log.", "{int (optional)} `chatlog_lines` Chat log lines.", "{int (optional)} `color` Text color.", @@ -6373,12 +6291,12 @@ }, { "type": "int (optional)", - "name": "bk-color", + "name": "bk_color", "description": "Background color." }, { "type": "int (optional)", - "name": "bk-opacity", + "name": "bk_opacity", "description": "Background opacity (0-100)." }, { @@ -6826,6 +6744,7 @@ "name": "GetBrowserSourceProperties", "category": "sources", "since": "4.1.0", + "deprecated": "Since 4.8.0. Prefer the use of GetSourceSettings. Will be removed in v5.0.0", "returns": [ { "type": "String", @@ -6898,6 +6817,12 @@ "description": "4.1.0" } ], + "deprecateds": [ + { + "name": "", + "description": "Since 4.8.0. Prefer the use of GetSourceSettings. Will be removed in v5.0.0" + } + ], "heading": { "level": 2, "text": "GetBrowserSourceProperties" @@ -6924,6 +6849,7 @@ "api": "requests", "name": "SetBrowserSourceProperties", "category": "sources", + "deprecated": "Since 4.8.0. Prefer the use of SetSourceSettings. Will be removed in v5.0.0", "since": "4.1.0", "params": [ { @@ -6989,6 +6915,12 @@ "description": "sources" } ], + "deprecateds": [ + { + "name": "", + "description": "Since 4.8.0. Prefer the use of SetSourceSettings. Will be removed in v5.0.0" + } + ], "sinces": [ { "name": "", @@ -7552,13 +7484,170 @@ "type": "class", "examples": [] }, + { + "subheads": [], + "description": "Get the audio monitoring type of the specified source.", + "param": "{String} `sourceName` Source name.", + "return": "{String} `monitorType` The monitor type in use. Options: `none`, `monitorOnly`, `monitorAndOutput`.", + "api": "requests", + "name": "GetAudioMonitorType", + "category": "sources", + "since": "4.8.0", + "returns": [ + { + "type": "String", + "name": "monitorType", + "description": "The monitor type in use. Options: `none`, `monitorOnly`, `monitorAndOutput`." + } + ], + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + } + ], + "names": [ + { + "name": "", + "description": "GetAudioMonitorType" + } + ], + "categories": [ + { + "name": "", + "description": "sources" + } + ], + "sinces": [ + { + "name": "", + "description": "4.8.0" + } + ], + "heading": { + "level": 2, + "text": "GetAudioMonitorType" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Set the audio monitoring type of the specified source.", + "param": [ + "{String} `sourceName` Source name.", + "{String} `monitorType` The monitor type to use. Options: `none`, `monitorOnly`, `monitorAndOutput`." + ], + "api": "requests", + "name": "SetAudioMonitorType", + "category": "sources", + "since": "4.8.0", + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + }, + { + "type": "String", + "name": "monitorType", + "description": "The monitor type to use. Options: `none`, `monitorOnly`, `monitorAndOutput`." + } + ], + "names": [ + { + "name": "", + "description": "SetAudioMonitorType" + } + ], + "categories": [ + { + "name": "", + "description": "sources" + } + ], + "sinces": [ + { + "name": "", + "description": "4.8.0" + } + ], + "heading": { + "level": 2, + "text": "SetAudioMonitorType" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Get the default settings for a given source type.", + "param": "{String} `sourceKind` Source kind. Also called \"source id\" in libobs terminology.", + "return": [ + "{String} `sourceKind` Source kind. Same value as the `sourceKind` parameter.", + "{Object} `defaultSettings` Settings object for source." + ], + "api": "requests", + "name": "GetSourceDefaultSettings", + "category": "sources", + "since": "4.9.0", + "returns": [ + { + "type": "String", + "name": "sourceKind", + "description": "Source kind. Same value as the `sourceKind` parameter." + }, + { + "type": "Object", + "name": "defaultSettings", + "description": "Settings object for source." + } + ], + "params": [ + { + "type": "String", + "name": "sourceKind", + "description": "Source kind. Also called \"source id\" in libobs terminology." + } + ], + "names": [ + { + "name": "", + "description": "GetSourceDefaultSettings" + } + ], + "categories": [ + { + "name": "", + "description": "sources" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "GetSourceDefaultSettings" + }, + "lead": "", + "type": "class", + "examples": [] + }, { "subheads": [], "description": "\n\nAt least `embedPictureFormat` or `saveToFilePath` must be specified.\n\nClients can specify `width` and `height` parameters to receive scaled pictures. Aspect ratio is\npreserved if only one of these two parameters is specified.", "param": [ - "{String} `sourceName` Source name. Note that, since scenes are also sources, you can also provide a scene name.", + "{String (optional)} `sourceName` Source name. Note: Since scenes are also sources, you can also provide a scene name. If not provided, the currently active scene is used.", "{String (optional)} `embedPictureFormat` Format of the Data URI encoded picture. Can be \"png\", \"jpg\", \"jpeg\" or \"bmp\" (or any other value supported by Qt's Image module)", "{String (optional)} `saveToFilePath` Full file path (file extension included) where the captured image is to be saved. Can be in a format different from `pictureFormat`. Can be a relative path.", + "{String (optional)} `fileFormat` Format to save the image file as (one of the values provided in the `supported-image-export-formats` response field of `GetVersion`). If not specified, tries to guess based on file extension.", + "{int (optional)} `compressionQuality` Compression ratio between -1 and 100 to write the image with. -1 is automatic, 1 is smallest file/most compression, 100 is largest file/least compression. Varies with image type.", "{int (optional)} `width` Screenshot width. Defaults to the source's base width.", "{int (optional)} `height` Screenshot height. Defaults to the source's base height." ], @@ -7590,9 +7679,9 @@ ], "params": [ { - "type": "String", + "type": "String (optional)", "name": "sourceName", - "description": "Source name. Note that, since scenes are also sources, you can also provide a scene name." + "description": "Source name. Note: Since scenes are also sources, you can also provide a scene name. If not provided, the currently active scene is used." }, { "type": "String (optional)", @@ -7604,6 +7693,16 @@ "name": "saveToFilePath", "description": "Full file path (file extension included) where the captured image is to be saved. Can be in a format different from `pictureFormat`. Can be a relative path." }, + { + "type": "String (optional)", + "name": "fileFormat", + "description": "Format to save the image file as (one of the values provided in the `supported-image-export-formats` response field of `GetVersion`). If not specified, tries to guess based on file extension." + }, + { + "type": "int (optional)", + "name": "compressionQuality", + "description": "Compression ratio between -1 and 100 to write the image with. -1 is automatic, 1 is smallest file/most compression, 100 is largest file/least compression. Varies with image type." + }, { "type": "int (optional)", "name": "width", @@ -7637,7 +7736,2399 @@ "level": 2, "text": "TakeSourceScreenshot" }, - "lead": "Takes a picture snapshot of a source and then can either or both: - Send it over as a Data URI (base64-encoded data) in the response (by specifying `embedPictureFormat` in the request) - Save it to disk (by specifying `saveToFilePath` in the request)", + "lead": "Takes a picture snapshot of a source and then can either or both: \t- Send it over as a Data URI (base64-encoded data) in the response (by specifying `embedPictureFormat` in the request) \t- Save it to disk (by specifying `saveToFilePath` in the request)", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Refreshes the specified browser source.", + "param": "{String} `sourceName` Source name.", + "api": "requests", + "name": "RefreshBrowserSource", + "category": "sources", + "since": "4.9.0", + "params": [ + { + "type": "String", + "name": "sourceName", + "description": "Source name." + } + ], + "names": [ + { + "name": "", + "description": "RefreshBrowserSource" + } + ], + "categories": [ + { + "name": "", + "description": "sources" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "RefreshBrowserSource" + }, + "lead": "", + "type": "class", + "examples": [] + } + ], + "outputs": [ + { + "subheads": [], + "description": "List existing outputs", + "return": "{Array} `outputs` Outputs list", + "api": "requests", + "name": "ListOutputs", + "category": "outputs", + "since": "4.7.0", + "returns": [ + { + "type": "Array", + "name": "outputs", + "description": "Outputs list" + } + ], + "names": [ + { + "name": "", + "description": "ListOutputs" + } + ], + "categories": [ + { + "name": "", + "description": "outputs" + } + ], + "sinces": [ + { + "name": "", + "description": "4.7.0" + } + ], + "heading": { + "level": 2, + "text": "ListOutputs" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Get information about a single output", + "param": "{String} `outputName` Output name", + "return": "{Output} `outputInfo` Output info", + "api": "requests", + "name": "GetOutputInfo", + "category": "outputs", + "since": "4.7.0", + "returns": [ + { + "type": "Output", + "name": "outputInfo", + "description": "Output info" + } + ], + "params": [ + { + "type": "String", + "name": "outputName", + "description": "Output name" + } + ], + "names": [ + { + "name": "", + "description": "GetOutputInfo" + } + ], + "categories": [ + { + "name": "", + "description": "outputs" + } + ], + "sinces": [ + { + "name": "", + "description": "4.7.0" + } + ], + "heading": { + "level": 2, + "text": "GetOutputInfo" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "\n\nNote: Controlling outputs is an experimental feature of obs-websocket. Some plugins which add outputs to OBS may not function properly when they are controlled in this way.", + "param": "{String} `outputName` Output name", + "api": "requests", + "name": "StartOutput", + "category": "outputs", + "since": "4.7.0", + "params": [ + { + "type": "String", + "name": "outputName", + "description": "Output name" + } + ], + "names": [ + { + "name": "", + "description": "StartOutput" + } + ], + "categories": [ + { + "name": "", + "description": "outputs" + } + ], + "sinces": [ + { + "name": "", + "description": "4.7.0" + } + ], + "heading": { + "level": 2, + "text": "StartOutput" + }, + "lead": "Start an output", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "\n\nNote: Controlling outputs is an experimental feature of obs-websocket. Some plugins which add outputs to OBS may not function properly when they are controlled in this way.", + "param": [ + "{String} `outputName` Output name", + "{boolean (optional)} `force` Force stop (default: false)" + ], + "api": "requests", + "name": "StopOutput", + "category": "outputs", + "since": "4.7.0", + "params": [ + { + "type": "String", + "name": "outputName", + "description": "Output name" + }, + { + "type": "boolean (optional)", + "name": "force", + "description": "Force stop (default: false)" + } + ], + "names": [ + { + "name": "", + "description": "StopOutput" + } + ], + "categories": [ + { + "name": "", + "description": "outputs" + } + ], + "sinces": [ + { + "name": "", + "description": "4.7.0" + } + ], + "heading": { + "level": 2, + "text": "StopOutput" + }, + "lead": "Stop an output", + "type": "class", + "examples": [] + } + ], + "profiles": [ + { + "subheads": [], + "description": "Set the currently active profile.", + "param": "{String} `profile-name` Name of the desired profile.", + "api": "requests", + "name": "SetCurrentProfile", + "category": "profiles", + "since": "4.0.0", + "params": [ + { + "type": "String", + "name": "profile-name", + "description": "Name of the desired profile." + } + ], + "names": [ + { + "name": "", + "description": "SetCurrentProfile" + } + ], + "categories": [ + { + "name": "", + "description": "profiles" + } + ], + "sinces": [ + { + "name": "", + "description": "4.0.0" + } + ], + "heading": { + "level": 2, + "text": "SetCurrentProfile" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Get the name of the current profile.", + "return": "{String} `profile-name` Name of the currently active profile.", + "api": "requests", + "name": "GetCurrentProfile", + "category": "profiles", + "since": "4.0.0", + "returns": [ + { + "type": "String", + "name": "profile-name", + "description": "Name of the currently active profile." + } + ], + "names": [ + { + "name": "", + "description": "GetCurrentProfile" + } + ], + "categories": [ + { + "name": "", + "description": "profiles" + } + ], + "sinces": [ + { + "name": "", + "description": "4.0.0" + } + ], + "heading": { + "level": 2, + "text": "GetCurrentProfile" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Get a list of available profiles.", + "return": [ + "{Array} `profiles` List of available profiles.", + "{String} `profiles.*.profile-name` Filter name" + ], + "api": "requests", + "name": "ListProfiles", + "category": "profiles", + "since": "4.0.0", + "returns": [ + { + "type": "Array", + "name": "profiles", + "description": "List of available profiles." + }, + { + "type": "String", + "name": "profiles.*.profile-name", + "description": "Filter name" + } + ], + "names": [ + { + "name": "", + "description": "ListProfiles" + } + ], + "categories": [ + { + "name": "", + "description": "profiles" + } + ], + "sinces": [ + { + "name": "", + "description": "4.0.0" + } + ], + "heading": { + "level": 2, + "text": "ListProfiles" + }, + "lead": "", + "type": "class", + "examples": [] + } + ], + "recording": [ + { + "subheads": [], + "description": "Get current recording status.", + "return": [ + "{boolean} `isRecording` Current recording status.", + "{boolean} `isRecordingPaused` Whether the recording is paused or not.", + "{String (optional)} `recordTimecode` Time elapsed since recording started (only present if currently recording).", + "{String (optional)} `recordingFilename` Absolute path to the recording file (only present if currently recording)." + ], + "api": "requests", + "name": "GetRecordingStatus", + "category": "recording", + "since": "4.9.0", + "returns": [ + { + "type": "boolean", + "name": "isRecording", + "description": "Current recording status." + }, + { + "type": "boolean", + "name": "isRecordingPaused", + "description": "Whether the recording is paused or not." + }, + { + "type": "String (optional)", + "name": "recordTimecode", + "description": "Time elapsed since recording started (only present if currently recording)." + }, + { + "type": "String (optional)", + "name": "recordingFilename", + "description": "Absolute path to the recording file (only present if currently recording)." + } + ], + "names": [ + { + "name": "", + "description": "GetRecordingStatus" + } + ], + "categories": [ + { + "name": "", + "description": "recording" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "GetRecordingStatus" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Toggle recording on or off (depending on the current recording state).", + "api": "requests", + "name": "StartStopRecording", + "category": "recording", + "since": "0.3", + "names": [ + { + "name": "", + "description": "StartStopRecording" + } + ], + "categories": [ + { + "name": "", + "description": "recording" + } + ], + "sinces": [ + { + "name": "", + "description": "0.3" + } + ], + "heading": { + "level": 2, + "text": "StartStopRecording" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Start recording.\nWill return an `error` if recording is already active.", + "api": "requests", + "name": "StartRecording", + "category": "recording", + "since": "4.1.0", + "names": [ + { + "name": "", + "description": "StartRecording" + } + ], + "categories": [ + { + "name": "", + "description": "recording" + } + ], + "sinces": [ + { + "name": "", + "description": "4.1.0" + } + ], + "heading": { + "level": 2, + "text": "StartRecording" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Stop recording.\nWill return an `error` if recording is not active.", + "api": "requests", + "name": "StopRecording", + "category": "recording", + "since": "4.1.0", + "names": [ + { + "name": "", + "description": "StopRecording" + } + ], + "categories": [ + { + "name": "", + "description": "recording" + } + ], + "sinces": [ + { + "name": "", + "description": "4.1.0" + } + ], + "heading": { + "level": 2, + "text": "StopRecording" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Pause the current recording.\nReturns an error if recording is not active or already paused.", + "api": "requests", + "name": "PauseRecording", + "category": "recording", + "since": "4.7.0", + "names": [ + { + "name": "", + "description": "PauseRecording" + } + ], + "categories": [ + { + "name": "", + "description": "recording" + } + ], + "sinces": [ + { + "name": "", + "description": "4.7.0" + } + ], + "heading": { + "level": 2, + "text": "PauseRecording" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Resume/unpause the current recording (if paused).\nReturns an error if recording is not active or not paused.", + "api": "requests", + "name": "ResumeRecording", + "category": "recording", + "since": "4.7.0", + "names": [ + { + "name": "", + "description": "ResumeRecording" + } + ], + "categories": [ + { + "name": "", + "description": "recording" + } + ], + "sinces": [ + { + "name": "", + "description": "4.7.0" + } + ], + "heading": { + "level": 2, + "text": "ResumeRecording" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "\n\nNote: If `SetRecordingFolder` is called while a recording is\nin progress, the change won't be applied immediately and will be\neffective on the next recording.", + "param": "{String} `rec-folder` Path of the recording folder.", + "api": "requests", + "name": "SetRecordingFolder", + "category": "recording", + "since": "4.1.0", + "params": [ + { + "type": "String", + "name": "rec-folder", + "description": "Path of the recording folder." + } + ], + "names": [ + { + "name": "", + "description": "SetRecordingFolder" + } + ], + "categories": [ + { + "name": "", + "description": "recording" + } + ], + "sinces": [ + { + "name": "", + "description": "4.1.0" + } + ], + "heading": { + "level": 2, + "text": "SetRecordingFolder" + }, + "lead": "In the current profile, sets the recording folder of the Simple and Advanced output modes to the specified value.", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Get the path of the current recording folder.", + "return": "{String} `rec-folder` Path of the recording folder.", + "api": "requests", + "name": "GetRecordingFolder", + "category": "recording", + "since": "4.1.0", + "returns": [ + { + "type": "String", + "name": "rec-folder", + "description": "Path of the recording folder." + } + ], + "names": [ + { + "name": "", + "description": "GetRecordingFolder" + } + ], + "categories": [ + { + "name": "", + "description": "recording" + } + ], + "sinces": [ + { + "name": "", + "description": "4.1.0" + } + ], + "heading": { + "level": 2, + "text": "GetRecordingFolder" + }, + "lead": "", + "type": "class", + "examples": [] + } + ], + "replay buffer": [ + { + "subheads": [], + "description": "Get the status of the OBS replay buffer.", + "return": "{boolean} `isReplayBufferActive` Current recording status.", + "api": "requests", + "name": "GetReplayBufferStatus", + "category": "replay buffer", + "since": "4.9.0", + "returns": [ + { + "type": "boolean", + "name": "isReplayBufferActive", + "description": "Current recording status." + } + ], + "names": [ + { + "name": "", + "description": "GetReplayBufferStatus" + } + ], + "categories": [ + { + "name": "", + "description": "replay buffer" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "GetReplayBufferStatus" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Toggle the Replay Buffer on/off (depending on the current state of the replay buffer).", + "api": "requests", + "name": "StartStopReplayBuffer", + "category": "replay buffer", + "since": "4.2.0", + "names": [ + { + "name": "", + "description": "StartStopReplayBuffer" + } + ], + "categories": [ + { + "name": "", + "description": "replay buffer" + } + ], + "sinces": [ + { + "name": "", + "description": "4.2.0" + } + ], + "heading": { + "level": 2, + "text": "StartStopReplayBuffer" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Start recording into the Replay Buffer.\nWill return an `error` if the Replay Buffer is already active or if the\n\"Save Replay Buffer\" hotkey is not set in OBS' settings.\nSetting this hotkey is mandatory, even when triggering saves only\nthrough obs-websocket.", + "api": "requests", + "name": "StartReplayBuffer", + "category": "replay buffer", + "since": "4.2.0", + "names": [ + { + "name": "", + "description": "StartReplayBuffer" + } + ], + "categories": [ + { + "name": "", + "description": "replay buffer" + } + ], + "sinces": [ + { + "name": "", + "description": "4.2.0" + } + ], + "heading": { + "level": 2, + "text": "StartReplayBuffer" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Stop recording into the Replay Buffer.\nWill return an `error` if the Replay Buffer is not active.", + "api": "requests", + "name": "StopReplayBuffer", + "category": "replay buffer", + "since": "4.2.0", + "names": [ + { + "name": "", + "description": "StopReplayBuffer" + } + ], + "categories": [ + { + "name": "", + "description": "replay buffer" + } + ], + "sinces": [ + { + "name": "", + "description": "4.2.0" + } + ], + "heading": { + "level": 2, + "text": "StopReplayBuffer" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Flush and save the contents of the Replay Buffer to disk. This is\nbasically the same as triggering the \"Save Replay Buffer\" hotkey.\nWill return an `error` if the Replay Buffer is not active.", + "api": "requests", + "name": "SaveReplayBuffer", + "category": "replay buffer", + "since": "4.2.0", + "names": [ + { + "name": "", + "description": "SaveReplayBuffer" + } + ], + "categories": [ + { + "name": "", + "description": "replay buffer" + } + ], + "sinces": [ + { + "name": "", + "description": "4.2.0" + } + ], + "heading": { + "level": 2, + "text": "SaveReplayBuffer" + }, + "lead": "", + "type": "class", + "examples": [] + } + ], + "scene collections": [ + { + "subheads": [], + "description": "Change the active scene collection.", + "param": "{String} `sc-name` Name of the desired scene collection.", + "api": "requests", + "name": "SetCurrentSceneCollection", + "category": "scene collections", + "since": "4.0.0", + "params": [ + { + "type": "String", + "name": "sc-name", + "description": "Name of the desired scene collection." + } + ], + "names": [ + { + "name": "", + "description": "SetCurrentSceneCollection" + } + ], + "categories": [ + { + "name": "", + "description": "scene collections" + } + ], + "sinces": [ + { + "name": "", + "description": "4.0.0" + } + ], + "heading": { + "level": 2, + "text": "SetCurrentSceneCollection" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Get the name of the current scene collection.", + "return": "{String} `sc-name` Name of the currently active scene collection.", + "api": "requests", + "name": "GetCurrentSceneCollection", + "category": "scene collections", + "since": "4.0.0", + "returns": [ + { + "type": "String", + "name": "sc-name", + "description": "Name of the currently active scene collection." + } + ], + "names": [ + { + "name": "", + "description": "GetCurrentSceneCollection" + } + ], + "categories": [ + { + "name": "", + "description": "scene collections" + } + ], + "sinces": [ + { + "name": "", + "description": "4.0.0" + } + ], + "heading": { + "level": 2, + "text": "GetCurrentSceneCollection" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "List available scene collections", + "return": "{Array} `scene-collections` Scene collections list", + "api": "requests", + "name": "ListSceneCollections", + "category": "scene collections", + "since": "4.0.0", + "returns": [ + { + "type": "Array", + "name": "scene-collections", + "description": "Scene collections list" + } + ], + "names": [ + { + "name": "", + "description": "ListSceneCollections" + } + ], + "categories": [ + { + "name": "", + "description": "scene collections" + } + ], + "sinces": [ + { + "name": "", + "description": "4.0.0" + } + ], + "heading": { + "level": 2, + "text": "ListSceneCollections" + }, + "lead": "", + "type": "class", + "examples": [] + } + ], + "scene items": [ + { + "subheads": [], + "description": "Get a list of all scene items in a scene.", + "param": "{String (optional)} `sceneName` Name of the scene to get the list of scene items from. Defaults to the current scene if not specified.", + "return": [ + "{String} `sceneName` Name of the requested (or current) scene", + "{Array} `sceneItems` Array of scene items", + "{int} `sceneItems.*.itemId` Unique item id of the source item", + "{String} `sceneItems.*.sourceKind` ID if the scene item's source. For example `vlc_source` or `image_source`", + "{String} `sceneItems.*.sourceName` Name of the scene item's source", + "{String} `sceneItems.*.sourceType` Type of the scene item's source. Either `input`, `group`, or `scene`" + ], + "api": "requests", + "name": "GetSceneItemList", + "category": "scene items", + "since": "4.9.0", + "returns": [ + { + "type": "String", + "name": "sceneName", + "description": "Name of the requested (or current) scene" + }, + { + "type": "Array", + "name": "sceneItems", + "description": "Array of scene items" + }, + { + "type": "int", + "name": "sceneItems.*.itemId", + "description": "Unique item id of the source item" + }, + { + "type": "String", + "name": "sceneItems.*.sourceKind", + "description": "ID if the scene item's source. For example `vlc_source` or `image_source`" + }, + { + "type": "String", + "name": "sceneItems.*.sourceName", + "description": "Name of the scene item's source" + }, + { + "type": "String", + "name": "sceneItems.*.sourceType", + "description": "Type of the scene item's source. Either `input`, `group`, or `scene`" + } + ], + "params": [ + { + "type": "String (optional)", + "name": "sceneName", + "description": "Name of the scene to get the list of scene items from. Defaults to the current scene if not specified." + } + ], + "names": [ + { + "name": "", + "description": "GetSceneItemList" + } + ], + "categories": [ + { + "name": "", + "description": "scene items" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "GetSceneItemList" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Gets the scene specific properties of the specified source item.\nCoordinates are relative to the item's parent (the scene or group it belongs to).", + "param": [ + "{String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.", + "{String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).", + "{String (optional)} `item.name` Scene Item name (if the `item` field is an object)", + "{int (optional)} `item.id` Scene Item ID (if the `item` field is an object)" + ], + "return": [ + "{String} `name` Scene Item name.", + "{int} `itemId` Scene Item ID.", + "{double} `position.x` The x position of the source from the left.", + "{double} `position.y` The y position of the source from the top.", + "{int} `position.alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.", + "{double} `rotation` The clockwise rotation of the item in degrees around the point of alignment.", + "{double} `scale.x` The x-scale factor of the source.", + "{double} `scale.y` The y-scale factor of the source.", + "{String} `scale.filter` The scale filter of the source. Can be \"OBS_SCALE_DISABLE\", \"OBS_SCALE_POINT\", \"OBS_SCALE_BICUBIC\", \"OBS_SCALE_BILINEAR\", \"OBS_SCALE_LANCZOS\" or \"OBS_SCALE_AREA\".", + "{int} `crop.top` The number of pixels cropped off the top of the source before scaling.", + "{int} `crop.right` The number of pixels cropped off the right of the source before scaling.", + "{int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling.", + "{int} `crop.left` The number of pixels cropped off the left of the source before scaling.", + "{bool} `visible` If the source is visible.", + "{bool} `muted` If the source is muted.", + "{bool} `locked` If the source's transform is locked.", + "{String} `bounds.type` Type of bounding box. Can be \"OBS_BOUNDS_STRETCH\", \"OBS_BOUNDS_SCALE_INNER\", \"OBS_BOUNDS_SCALE_OUTER\", \"OBS_BOUNDS_SCALE_TO_WIDTH\", \"OBS_BOUNDS_SCALE_TO_HEIGHT\", \"OBS_BOUNDS_MAX_ONLY\" or \"OBS_BOUNDS_NONE\".", + "{int} `bounds.alignment` Alignment of the bounding box.", + "{double} `bounds.x` Width of the bounding box.", + "{double} `bounds.y` Height of the bounding box.", + "{int} `sourceWidth` Base width (without scaling) of the source", + "{int} `sourceHeight` Base source (without scaling) of the source", + "{double} `width` Scene item width (base source width multiplied by the horizontal scaling factor)", + "{double} `height` Scene item height (base source height multiplied by the vertical scaling factor)", + "{String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group)", + "{Array (optional)} `groupChildren` List of children (if this item is a group)" + ], + "api": "requests", + "name": "GetSceneItemProperties", + "category": "scene items", + "since": "4.3.0", + "returns": [ + { + "type": "String", + "name": "name", + "description": "Scene Item name." + }, + { + "type": "int", + "name": "itemId", + "description": "Scene Item ID." + }, + { + "type": "double", + "name": "position.x", + "description": "The x position of the source from the left." + }, + { + "type": "double", + "name": "position.y", + "description": "The y position of the source from the top." + }, + { + "type": "int", + "name": "position.alignment", + "description": "The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis." + }, + { + "type": "double", + "name": "rotation", + "description": "The clockwise rotation of the item in degrees around the point of alignment." + }, + { + "type": "double", + "name": "scale.x", + "description": "The x-scale factor of the source." + }, + { + "type": "double", + "name": "scale.y", + "description": "The y-scale factor of the source." + }, + { + "type": "String", + "name": "scale.filter", + "description": "The scale filter of the source. Can be \"OBS_SCALE_DISABLE\", \"OBS_SCALE_POINT\", \"OBS_SCALE_BICUBIC\", \"OBS_SCALE_BILINEAR\", \"OBS_SCALE_LANCZOS\" or \"OBS_SCALE_AREA\"." + }, + { + "type": "int", + "name": "crop.top", + "description": "The number of pixels cropped off the top of the source before scaling." + }, + { + "type": "int", + "name": "crop.right", + "description": "The number of pixels cropped off the right of the source before scaling." + }, + { + "type": "int", + "name": "crop.bottom", + "description": "The number of pixels cropped off the bottom of the source before scaling." + }, + { + "type": "int", + "name": "crop.left", + "description": "The number of pixels cropped off the left of the source before scaling." + }, + { + "type": "bool", + "name": "visible", + "description": "If the source is visible." + }, + { + "type": "bool", + "name": "muted", + "description": "If the source is muted." + }, + { + "type": "bool", + "name": "locked", + "description": "If the source's transform is locked." + }, + { + "type": "String", + "name": "bounds.type", + "description": "Type of bounding box. Can be \"OBS_BOUNDS_STRETCH\", \"OBS_BOUNDS_SCALE_INNER\", \"OBS_BOUNDS_SCALE_OUTER\", \"OBS_BOUNDS_SCALE_TO_WIDTH\", \"OBS_BOUNDS_SCALE_TO_HEIGHT\", \"OBS_BOUNDS_MAX_ONLY\" or \"OBS_BOUNDS_NONE\"." + }, + { + "type": "int", + "name": "bounds.alignment", + "description": "Alignment of the bounding box." + }, + { + "type": "double", + "name": "bounds.x", + "description": "Width of the bounding box." + }, + { + "type": "double", + "name": "bounds.y", + "description": "Height of the bounding box." + }, + { + "type": "int", + "name": "sourceWidth", + "description": "Base width (without scaling) of the source" + }, + { + "type": "int", + "name": "sourceHeight", + "description": "Base source (without scaling) of the source" + }, + { + "type": "double", + "name": "width", + "description": "Scene item width (base source width multiplied by the horizontal scaling factor)" + }, + { + "type": "double", + "name": "height", + "description": "Scene item height (base source height multiplied by the vertical scaling factor)" + }, + { + "type": "String (optional)", + "name": "parentGroupName", + "description": "Name of the item's parent (if this item belongs to a group)" + }, + { + "type": "Array (optional)", + "name": "groupChildren", + "description": "List of children (if this item is a group)" + } + ], + "params": [ + { + "type": "String (optional)", + "name": "scene-name", + "description": "Name of the scene the scene item belongs to. Defaults to the current scene." + }, + { + "type": "String | Object", + "name": "item", + "description": "Scene Item name (if this field is a string) or specification (if it is an object)." + }, + { + "type": "String (optional)", + "name": "item.name", + "description": "Scene Item name (if the `item` field is an object)" + }, + { + "type": "int (optional)", + "name": "item.id", + "description": "Scene Item ID (if the `item` field is an object)" + } + ], + "names": [ + { + "name": "", + "description": "GetSceneItemProperties" + } + ], + "categories": [ + { + "name": "", + "description": "scene items" + } + ], + "sinces": [ + { + "name": "", + "description": "4.3.0" + } + ], + "heading": { + "level": 2, + "text": "GetSceneItemProperties" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Sets the scene specific properties of a source. Unspecified properties will remain unchanged.\nCoordinates are relative to the item's parent (the scene or group it belongs to).", + "param": [ + "{String (optional)} `scene-name` Name of the scene the source item belongs to. Defaults to the current scene.", + "{String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).", + "{String (optional)} `item.name` Scene Item name (if the `item` field is an object)", + "{int (optional)} `item.id` Scene Item ID (if the `item` field is an object)", + "{double (optional)} `position.x` The new x position of the source.", + "{double (optional)} `position.y` The new y position of the source.", + "{int (optional)} `position.alignment` The new alignment of the source.", + "{double (optional)} `rotation` The new clockwise rotation of the item in degrees.", + "{double (optional)} `scale.x` The new x scale of the item.", + "{double (optional)} `scale.y` The new y scale of the item.", + "{String (optional)} `scale.filter` The new scale filter of the source. Can be \"OBS_SCALE_DISABLE\", \"OBS_SCALE_POINT\", \"OBS_SCALE_BICUBIC\", \"OBS_SCALE_BILINEAR\", \"OBS_SCALE_LANCZOS\" or \"OBS_SCALE_AREA\".", + "{int (optional)} `crop.top` The new amount of pixels cropped off the top of the source before scaling.", + "{int (optional)} `crop.bottom` The new amount of pixels cropped off the bottom of the source before scaling.", + "{int (optional)} `crop.left` The new amount of pixels cropped off the left of the source before scaling.", + "{int (optional)} `crop.right` The new amount of pixels cropped off the right of the source before scaling.", + "{bool (optional)} `visible` The new visibility of the source. 'true' shows source, 'false' hides source.", + "{bool (optional)} `locked` The new locked status of the source. 'true' keeps it in its current position, 'false' allows movement.", + "{String (optional)} `bounds.type` The new bounds type of the source. Can be \"OBS_BOUNDS_STRETCH\", \"OBS_BOUNDS_SCALE_INNER\", \"OBS_BOUNDS_SCALE_OUTER\", \"OBS_BOUNDS_SCALE_TO_WIDTH\", \"OBS_BOUNDS_SCALE_TO_HEIGHT\", \"OBS_BOUNDS_MAX_ONLY\" or \"OBS_BOUNDS_NONE\".", + "{int (optional)} `bounds.alignment` The new alignment of the bounding box. (0-2, 4-6, 8-10)", + "{double (optional)} `bounds.x` The new width of the bounding box.", + "{double (optional)} `bounds.y` The new height of the bounding box." + ], + "api": "requests", + "name": "SetSceneItemProperties", + "category": "scene items", + "since": "4.3.0", + "params": [ + { + "type": "String (optional)", + "name": "scene-name", + "description": "Name of the scene the source item belongs to. Defaults to the current scene." + }, + { + "type": "String | Object", + "name": "item", + "description": "Scene Item name (if this field is a string) or specification (if it is an object)." + }, + { + "type": "String (optional)", + "name": "item.name", + "description": "Scene Item name (if the `item` field is an object)" + }, + { + "type": "int (optional)", + "name": "item.id", + "description": "Scene Item ID (if the `item` field is an object)" + }, + { + "type": "double (optional)", + "name": "position.x", + "description": "The new x position of the source." + }, + { + "type": "double (optional)", + "name": "position.y", + "description": "The new y position of the source." + }, + { + "type": "int (optional)", + "name": "position.alignment", + "description": "The new alignment of the source." + }, + { + "type": "double (optional)", + "name": "rotation", + "description": "The new clockwise rotation of the item in degrees." + }, + { + "type": "double (optional)", + "name": "scale.x", + "description": "The new x scale of the item." + }, + { + "type": "double (optional)", + "name": "scale.y", + "description": "The new y scale of the item." + }, + { + "type": "String (optional)", + "name": "scale.filter", + "description": "The new scale filter of the source. Can be \"OBS_SCALE_DISABLE\", \"OBS_SCALE_POINT\", \"OBS_SCALE_BICUBIC\", \"OBS_SCALE_BILINEAR\", \"OBS_SCALE_LANCZOS\" or \"OBS_SCALE_AREA\"." + }, + { + "type": "int (optional)", + "name": "crop.top", + "description": "The new amount of pixels cropped off the top of the source before scaling." + }, + { + "type": "int (optional)", + "name": "crop.bottom", + "description": "The new amount of pixels cropped off the bottom of the source before scaling." + }, + { + "type": "int (optional)", + "name": "crop.left", + "description": "The new amount of pixels cropped off the left of the source before scaling." + }, + { + "type": "int (optional)", + "name": "crop.right", + "description": "The new amount of pixels cropped off the right of the source before scaling." + }, + { + "type": "bool (optional)", + "name": "visible", + "description": "The new visibility of the source. 'true' shows source, 'false' hides source." + }, + { + "type": "bool (optional)", + "name": "locked", + "description": "The new locked status of the source. 'true' keeps it in its current position, 'false' allows movement." + }, + { + "type": "String (optional)", + "name": "bounds.type", + "description": "The new bounds type of the source. Can be \"OBS_BOUNDS_STRETCH\", \"OBS_BOUNDS_SCALE_INNER\", \"OBS_BOUNDS_SCALE_OUTER\", \"OBS_BOUNDS_SCALE_TO_WIDTH\", \"OBS_BOUNDS_SCALE_TO_HEIGHT\", \"OBS_BOUNDS_MAX_ONLY\" or \"OBS_BOUNDS_NONE\"." + }, + { + "type": "int (optional)", + "name": "bounds.alignment", + "description": "The new alignment of the bounding box. (0-2, 4-6, 8-10)" + }, + { + "type": "double (optional)", + "name": "bounds.x", + "description": "The new width of the bounding box." + }, + { + "type": "double (optional)", + "name": "bounds.y", + "description": "The new height of the bounding box." + } + ], + "names": [ + { + "name": "", + "description": "SetSceneItemProperties" + } + ], + "categories": [ + { + "name": "", + "description": "scene items" + } + ], + "sinces": [ + { + "name": "", + "description": "4.3.0" + } + ], + "heading": { + "level": 2, + "text": "SetSceneItemProperties" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Reset a scene item.", + "param": [ + "{String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.", + "{String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object).", + "{String (optional)} `item.name` Scene Item name (if the `item` field is an object)", + "{int (optional)} `item.id` Scene Item ID (if the `item` field is an object)" + ], + "api": "requests", + "name": "ResetSceneItem", + "category": "scene items", + "since": "4.2.0", + "params": [ + { + "type": "String (optional)", + "name": "scene-name", + "description": "Name of the scene the scene item belongs to. Defaults to the current scene." + }, + { + "type": "String | Object", + "name": "item", + "description": "Scene Item name (if this field is a string) or specification (if it is an object)." + }, + { + "type": "String (optional)", + "name": "item.name", + "description": "Scene Item name (if the `item` field is an object)" + }, + { + "type": "int (optional)", + "name": "item.id", + "description": "Scene Item ID (if the `item` field is an object)" + } + ], + "names": [ + { + "name": "", + "description": "ResetSceneItem" + } + ], + "categories": [ + { + "name": "", + "description": "scene items" + } + ], + "sinces": [ + { + "name": "", + "description": "4.2.0" + } + ], + "heading": { + "level": 2, + "text": "ResetSceneItem" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Show or hide a specified source item in a specified scene.", + "param": [ + "{String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the currently active scene.", + "{String (optional)} `source` Scene Item name.", + "{int (optional)} `item` Scene Item id", + "{boolean} `render` true = shown ; false = hidden" + ], + "api": "requests", + "name": "SetSceneItemRender", + "category": "scene items", + "since": "0.3", + "params": [ + { + "type": "String (optional)", + "name": "scene-name", + "description": "Name of the scene the scene item belongs to. Defaults to the currently active scene." + }, + { + "type": "String (optional)", + "name": "source", + "description": "Scene Item name." + }, + { + "type": "int (optional)", + "name": "item", + "description": "Scene Item id" + }, + { + "type": "boolean", + "name": "render", + "description": "true = shown ; false = hidden" + } + ], + "names": [ + { + "name": "", + "description": "SetSceneItemRender" + } + ], + "categories": [ + { + "name": "", + "description": "scene items" + } + ], + "sinces": [ + { + "name": "", + "description": "0.3" + } + ], + "heading": { + "level": 2, + "text": "SetSceneItemRender" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Sets the coordinates of a specified source item.", + "param": [ + "{String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.", + "{String} `item` Scene Item name.", + "{double} `x` X coordinate.", + "{double} `y` Y coordinate." + ], + "api": "requests", + "name": "SetSceneItemPosition", + "category": "scene items", + "since": "4.0.0", + "deprecated": "Since 4.3.0. Prefer the use of SetSceneItemProperties.", + "params": [ + { + "type": "String (optional)", + "name": "scene-name", + "description": "Name of the scene the scene item belongs to. Defaults to the current scene." + }, + { + "type": "String", + "name": "item", + "description": "Scene Item name." + }, + { + "type": "double", + "name": "x", + "description": "X coordinate." + }, + { + "type": "double", + "name": "y", + "description": "Y coordinate." + } + ], + "names": [ + { + "name": "", + "description": "SetSceneItemPosition" + } + ], + "categories": [ + { + "name": "", + "description": "scene items" + } + ], + "sinces": [ + { + "name": "", + "description": "4.0.0" + } + ], + "deprecateds": [ + { + "name": "", + "description": "Since 4.3.0. Prefer the use of SetSceneItemProperties." + } + ], + "heading": { + "level": 2, + "text": "SetSceneItemPosition" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Set the transform of the specified source item.", + "param": [ + "{String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.", + "{String} `item` Scene Item name.", + "{double} `x-scale` Width scale factor.", + "{double} `y-scale` Height scale factor.", + "{double} `rotation` Source item rotation (in degrees)." + ], + "api": "requests", + "name": "SetSceneItemTransform", + "category": "scene items", + "since": "4.0.0", + "deprecated": "Since 4.3.0. Prefer the use of SetSceneItemProperties.", + "params": [ + { + "type": "String (optional)", + "name": "scene-name", + "description": "Name of the scene the scene item belongs to. Defaults to the current scene." + }, + { + "type": "String", + "name": "item", + "description": "Scene Item name." + }, + { + "type": "double", + "name": "x-scale", + "description": "Width scale factor." + }, + { + "type": "double", + "name": "y-scale", + "description": "Height scale factor." + }, + { + "type": "double", + "name": "rotation", + "description": "Source item rotation (in degrees)." + } + ], + "names": [ + { + "name": "", + "description": "SetSceneItemTransform" + } + ], + "categories": [ + { + "name": "", + "description": "scene items" + } + ], + "sinces": [ + { + "name": "", + "description": "4.0.0" + } + ], + "deprecateds": [ + { + "name": "", + "description": "Since 4.3.0. Prefer the use of SetSceneItemProperties." + } + ], + "heading": { + "level": 2, + "text": "SetSceneItemTransform" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Sets the crop coordinates of the specified source item.", + "param": [ + "{String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene.", + "{String} `item` Scene Item name.", + "{int} `top` Pixel position of the top of the source item.", + "{int} `bottom` Pixel position of the bottom of the source item.", + "{int} `left` Pixel position of the left of the source item.", + "{int} `right` Pixel position of the right of the source item." + ], + "api": "requests", + "name": "SetSceneItemCrop", + "category": "scene items", + "since": "4.1.0", + "deprecated": "Since 4.3.0. Prefer the use of SetSceneItemProperties.", + "params": [ + { + "type": "String (optional)", + "name": "scene-name", + "description": "Name of the scene the scene item belongs to. Defaults to the current scene." + }, + { + "type": "String", + "name": "item", + "description": "Scene Item name." + }, + { + "type": "int", + "name": "top", + "description": "Pixel position of the top of the source item." + }, + { + "type": "int", + "name": "bottom", + "description": "Pixel position of the bottom of the source item." + }, + { + "type": "int", + "name": "left", + "description": "Pixel position of the left of the source item." + }, + { + "type": "int", + "name": "right", + "description": "Pixel position of the right of the source item." + } + ], + "names": [ + { + "name": "", + "description": "SetSceneItemCrop" + } + ], + "categories": [ + { + "name": "", + "description": "scene items" + } + ], + "sinces": [ + { + "name": "", + "description": "4.1.0" + } + ], + "deprecateds": [ + { + "name": "", + "description": "Since 4.3.0. Prefer the use of SetSceneItemProperties." + } + ], + "heading": { + "level": 2, + "text": "SetSceneItemCrop" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Deletes a scene item.", + "param": [ + "{String (optional)} `scene` Name of the scene the scene item belongs to. Defaults to the current scene.", + "{Object} `item` Scene item to delete (required)", + "{String} `item.name` Scene Item name (prefer `id`, including both is acceptable).", + "{int} `item.id` Scene Item ID." + ], + "api": "requests", + "name": "DeleteSceneItem", + "category": "scene items", + "since": "4.5.0", + "params": [ + { + "type": "String (optional)", + "name": "scene", + "description": "Name of the scene the scene item belongs to. Defaults to the current scene." + }, + { + "type": "Object", + "name": "item", + "description": "Scene item to delete (required)" + }, + { + "type": "String", + "name": "item.name", + "description": "Scene Item name (prefer `id`, including both is acceptable)." + }, + { + "type": "int", + "name": "item.id", + "description": "Scene Item ID." + } + ], + "names": [ + { + "name": "", + "description": "DeleteSceneItem" + } + ], + "categories": [ + { + "name": "", + "description": "scene items" + } + ], + "sinces": [ + { + "name": "", + "description": "4.5.0" + } + ], + "heading": { + "level": 2, + "text": "DeleteSceneItem" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Creates a scene item in a scene. In other words, this is how you add a source into a scene.", + "param": [ + "{String} `sceneName` Name of the scene to create the scene item in", + "{String} `sourceName` Name of the source to be added", + "{boolean (optional)} `setVisible` Whether to make the sceneitem visible on creation or not. Default `true`" + ], + "return": "{int} `itemId` Numerical ID of the created scene item", + "api": "requests", + "name": "AddSceneItem", + "category": "scene items", + "since": "4.9.0", + "returns": [ + { + "type": "int", + "name": "itemId", + "description": "Numerical ID of the created scene item" + } + ], + "params": [ + { + "type": "String", + "name": "sceneName", + "description": "Name of the scene to create the scene item in" + }, + { + "type": "String", + "name": "sourceName", + "description": "Name of the source to be added" + }, + { + "type": "boolean (optional)", + "name": "setVisible", + "description": "Whether to make the sceneitem visible on creation or not. Default `true`" + } + ], + "names": [ + { + "name": "", + "description": "AddSceneItem" + } + ], + "categories": [ + { + "name": "", + "description": "scene items" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "AddSceneItem" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Duplicates a scene item.", + "param": [ + "{String (optional)} `fromScene` Name of the scene to copy the item from. Defaults to the current scene.", + "{String (optional)} `toScene` Name of the scene to create the item in. Defaults to the current scene.", + "{Object} `item` Scene Item to duplicate from the source scene (required)", + "{String} `item.name` Scene Item name (prefer `id`, including both is acceptable).", + "{int} `item.id` Scene Item ID." + ], + "return": [ + "{String} `scene` Name of the scene where the new item was created", + "{Object} `item` New item info", + "{int} `item.id` New item ID", + "{String} `item.name` New item name" + ], + "api": "requests", + "name": "DuplicateSceneItem", + "category": "scene items", + "since": "4.5.0", + "returns": [ + { + "type": "String", + "name": "scene", + "description": "Name of the scene where the new item was created" + }, + { + "type": "Object", + "name": "item", + "description": "New item info" + }, + { + "type": "int", + "name": "item.id", + "description": "New item ID" + }, + { + "type": "String", + "name": "item.name", + "description": "New item name" + } + ], + "params": [ + { + "type": "String (optional)", + "name": "fromScene", + "description": "Name of the scene to copy the item from. Defaults to the current scene." + }, + { + "type": "String (optional)", + "name": "toScene", + "description": "Name of the scene to create the item in. Defaults to the current scene." + }, + { + "type": "Object", + "name": "item", + "description": "Scene Item to duplicate from the source scene (required)" + }, + { + "type": "String", + "name": "item.name", + "description": "Scene Item name (prefer `id`, including both is acceptable)." + }, + { + "type": "int", + "name": "item.id", + "description": "Scene Item ID." + } + ], + "names": [ + { + "name": "", + "description": "DuplicateSceneItem" + } + ], + "categories": [ + { + "name": "", + "description": "scene items" + } + ], + "sinces": [ + { + "name": "", + "description": "4.5.0" + } + ], + "heading": { + "level": 2, + "text": "DuplicateSceneItem" + }, + "lead": "", + "type": "class", + "examples": [] + } + ], + "scenes": [ + { + "subheads": [], + "description": "Switch to the specified scene.", + "param": "{String} `scene-name` Name of the scene to switch to.", + "api": "requests", + "name": "SetCurrentScene", + "category": "scenes", + "since": "0.3", + "params": [ + { + "type": "String", + "name": "scene-name", + "description": "Name of the scene to switch to." + } + ], + "names": [ + { + "name": "", + "description": "SetCurrentScene" + } + ], + "categories": [ + { + "name": "", + "description": "scenes" + } + ], + "sinces": [ + { + "name": "", + "description": "0.3" + } + ], + "heading": { + "level": 2, + "text": "SetCurrentScene" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Get the current scene's name and source items.", + "return": [ + "{String} `name` Name of the currently active scene.", + "{Array} `sources` Ordered list of the current scene's source items." + ], + "api": "requests", + "name": "GetCurrentScene", + "category": "scenes", + "since": "0.3", + "returns": [ + { + "type": "String", + "name": "name", + "description": "Name of the currently active scene." + }, + { + "type": "Array", + "name": "sources", + "description": "Ordered list of the current scene's source items." + } + ], + "names": [ + { + "name": "", + "description": "GetCurrentScene" + } + ], + "categories": [ + { + "name": "", + "description": "scenes" + } + ], + "sinces": [ + { + "name": "", + "description": "0.3" + } + ], + "heading": { + "level": 2, + "text": "GetCurrentScene" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Get a list of scenes in the currently active profile.", + "return": [ + "{String} `current-scene` Name of the currently active scene.", + "{Array} `scenes` Ordered list of the current profile's scenes (See [GetCurrentScene](#getcurrentscene) for more information)." + ], + "api": "requests", + "name": "GetSceneList", + "category": "scenes", + "since": "0.3", + "returns": [ + { + "type": "String", + "name": "current-scene", + "description": "Name of the currently active scene." + }, + { + "type": "Array", + "name": "scenes", + "description": "Ordered list of the current profile's scenes (See [GetCurrentScene](#getcurrentscene) for more information)." + } + ], + "names": [ + { + "name": "", + "description": "GetSceneList" + } + ], + "categories": [ + { + "name": "", + "description": "scenes" + } + ], + "sinces": [ + { + "name": "", + "description": "0.3" + } + ], + "heading": { + "level": 2, + "text": "GetSceneList" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Create a new scene scene.", + "param": "{String} `sceneName` Name of the scene to create.", + "api": "requests", + "name": "CreateScene", + "category": "scenes", + "since": "4.9.0", + "params": [ + { + "type": "String", + "name": "sceneName", + "description": "Name of the scene to create." + } + ], + "names": [ + { + "name": "", + "description": "CreateScene" + } + ], + "categories": [ + { + "name": "", + "description": "scenes" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "CreateScene" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Changes the order of scene items in the requested scene.", + "param": [ + "{String (optional)} `scene` Name of the scene to reorder (defaults to current).", + "{Array} `items` Ordered list of objects with name and/or id specified. Id preferred due to uniqueness per scene", + "{int (optional)} `items.*.id` Id of a specific scene item. Unique on a scene by scene basis.", + "{String (optional)} `items.*.name` Name of a scene item. Sufficiently unique if no scene items share sources within the scene." + ], + "api": "requests", + "name": "ReorderSceneItems", + "category": "scenes", + "since": "4.5.0", + "params": [ + { + "type": "String (optional)", + "name": "scene", + "description": "Name of the scene to reorder (defaults to current)." + }, + { + "type": "Array", + "name": "items", + "description": "Ordered list of objects with name and/or id specified. Id preferred due to uniqueness per scene" + }, + { + "type": "int (optional)", + "name": "items.*.id", + "description": "Id of a specific scene item. Unique on a scene by scene basis." + }, + { + "type": "String (optional)", + "name": "items.*.name", + "description": "Name of a scene item. Sufficiently unique if no scene items share sources within the scene." + } + ], + "names": [ + { + "name": "", + "description": "ReorderSceneItems" + } + ], + "categories": [ + { + "name": "", + "description": "scenes" + } + ], + "sinces": [ + { + "name": "", + "description": "4.5.0" + } + ], + "heading": { + "level": 2, + "text": "ReorderSceneItems" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Set a scene to use a specific transition override.", + "param": [ + "{String} `sceneName` Name of the scene to switch to.", + "{String} `transitionName` Name of the transition to use.", + "{int (Optional)} `transitionDuration` Duration in milliseconds of the transition if transition is not fixed. Defaults to the current duration specified in the UI if there is no current override and this value is not given." + ], + "api": "requests", + "name": "SetSceneTransitionOverride", + "category": "scenes", + "since": "4.8.0", + "params": [ + { + "type": "String", + "name": "sceneName", + "description": "Name of the scene to switch to." + }, + { + "type": "String", + "name": "transitionName", + "description": "Name of the transition to use." + }, + { + "type": "int (Optional)", + "name": "transitionDuration", + "description": "Duration in milliseconds of the transition if transition is not fixed. Defaults to the current duration specified in the UI if there is no current override and this value is not given." + } + ], + "names": [ + { + "name": "", + "description": "SetSceneTransitionOverride" + } + ], + "categories": [ + { + "name": "", + "description": "scenes" + } + ], + "sinces": [ + { + "name": "", + "description": "4.8.0" + } + ], + "heading": { + "level": 2, + "text": "SetSceneTransitionOverride" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Remove any transition override on a scene.", + "param": "{String} `sceneName` Name of the scene to switch to.", + "api": "requests", + "name": "RemoveSceneTransitionOverride", + "category": "scenes", + "since": "4.8.0", + "params": [ + { + "type": "String", + "name": "sceneName", + "description": "Name of the scene to switch to." + } + ], + "names": [ + { + "name": "", + "description": "RemoveSceneTransitionOverride" + } + ], + "categories": [ + { + "name": "", + "description": "scenes" + } + ], + "sinces": [ + { + "name": "", + "description": "4.8.0" + } + ], + "heading": { + "level": 2, + "text": "RemoveSceneTransitionOverride" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Get the current scene transition override.", + "param": "{String} `sceneName` Name of the scene to switch to.", + "return": [ + "{String} `transitionName` Name of the current overriding transition. Empty string if no override is set.", + "{int} `transitionDuration` Transition duration. `-1` if no override is set." + ], + "api": "requests", + "name": "GetSceneTransitionOverride", + "category": "scenes", + "since": "4.8.0", + "returns": [ + { + "type": "String", + "name": "transitionName", + "description": "Name of the current overriding transition. Empty string if no override is set." + }, + { + "type": "int", + "name": "transitionDuration", + "description": "Transition duration. `-1` if no override is set." + } + ], + "params": [ + { + "type": "String", + "name": "sceneName", + "description": "Name of the scene to switch to." + } + ], + "names": [ + { + "name": "", + "description": "GetSceneTransitionOverride" + } + ], + "categories": [ + { + "name": "", + "description": "scenes" + } + ], + "sinces": [ + { + "name": "", + "description": "4.8.0" + } + ], + "heading": { + "level": 2, + "text": "GetSceneTransitionOverride" + }, + "lead": "", "type": "class", "examples": [] } @@ -7649,9 +10140,10 @@ "return": [ "{boolean} `streaming` Current streaming status.", "{boolean} `recording` Current recording status.", + "{boolean} `recording-paused` If recording is paused.", + "{boolean} `preview-only` Always false. Retrocompatibility with OBSRemote.", "{String (optional)} `stream-timecode` Time elapsed since streaming started (only present if currently streaming).", - "{String (optional)} `rec-timecode` Time elapsed since recording started (only present if currently recording).", - "{boolean} `preview-only` Always false. Retrocompatibility with OBSRemote." + "{String (optional)} `rec-timecode` Time elapsed since recording started (only present if currently recording)." ], "api": "requests", "name": "GetStreamingStatus", @@ -7668,6 +10160,16 @@ "name": "recording", "description": "Current recording status." }, + { + "type": "boolean", + "name": "recording-paused", + "description": "If recording is paused." + }, + { + "type": "boolean", + "name": "preview-only", + "description": "Always false. Retrocompatibility with OBSRemote." + }, { "type": "String (optional)", "name": "stream-timecode", @@ -7677,11 +10179,6 @@ "type": "String (optional)", "name": "rec-timecode", "description": "Time elapsed since recording started (only present if currently recording)." - }, - { - "type": "boolean", - "name": "preview-only", - "description": "Always false. Retrocompatibility with OBSRemote." } ], "names": [ @@ -7712,7 +10209,7 @@ }, { "subheads": [], - "description": "Toggle streaming on or off.", + "description": "Toggle streaming on or off (depending on the current stream state).", "api": "requests", "name": "StartStopStreaming", "category": "streaming", @@ -7747,7 +10244,7 @@ "subheads": [], "description": "Start streaming.\nWill return an `error` if streaming is already active.", "param": [ - "{Object (optional)} `stream` Special stream configuration. Please note: these won't be saved to OBS' configuration.", + "{Object (optional)} `stream` Special stream configuration. Note: these won't be saved to OBS' configuration.", "{String (optional)} `stream.type` If specified ensures the type of stream matches the given type (usually 'rtmp_custom' or 'rtmp_common'). If the currently configured stream type does not match the given stream type, all settings must be specified in the `settings` object or an error will occur when starting the stream.", "{Object (optional)} `stream.metadata` Adds the given object parameters as encoded query string parameters to the 'key' of the RTMP stream. Used to pass data to the RTMP service about the streaming. May be any String, Numeric, or Boolean field.", "{Object (optional)} `stream.settings` Settings for the stream.", @@ -7765,7 +10262,7 @@ { "type": "Object (optional)", "name": "stream", - "description": "Special stream configuration. Please note: these won't be saved to OBS' configuration." + "description": "Special stream configuration. Note: these won't be saved to OBS' configuration." }, { "type": "String (optional)", @@ -8066,7 +10563,7 @@ }, { "subheads": [], - "description": "Send the provided text as embedded CEA-608 caption data.\nAs of OBS Studio 23.1, captions are not yet available on Linux.", + "description": "Send the provided text as embedded CEA-608 caption data.", "param": "{String} `text` Captions text", "api": "requests", "name": "SendCaptions", @@ -8361,7 +10858,7 @@ }, { "subheads": [], - "description": "Toggles Studio Mode.", + "description": "Toggles Studio Mode (depending on the current state of studio mode).", "api": "requests", "name": "ToggleStudioMode", "category": "studio mode", @@ -8620,6 +11117,235 @@ "lead": "", "type": "class", "examples": [] + }, + { + "subheads": [], + "description": "Get the position of the current transition.", + "return": "{double} `position` current transition position. This value will be between 0.0 and 1.0. Note: Transition returns 1.0 when not active.", + "api": "requests", + "name": "GetTransitionPosition", + "category": "transitions", + "since": "4.9.0", + "returns": [ + { + "type": "double", + "name": "position", + "description": "current transition position. This value will be between 0.0 and 1.0. Note: Transition returns 1.0 when not active." + } + ], + "names": [ + { + "name": "", + "description": "GetTransitionPosition" + } + ], + "categories": [ + { + "name": "", + "description": "transitions" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "GetTransitionPosition" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Get the current settings of a transition", + "param": "{String} `transitionName` Transition name", + "return": "{Object} `transitionSettings` Current transition settings", + "api": "requests", + "name": "GetTransitionSettings", + "category": "transitions", + "since": "4.9.0", + "returns": [ + { + "type": "Object", + "name": "transitionSettings", + "description": "Current transition settings" + } + ], + "params": [ + { + "type": "String", + "name": "transitionName", + "description": "Transition name" + } + ], + "names": [ + { + "name": "", + "description": "GetTransitionSettings" + } + ], + "categories": [ + { + "name": "", + "description": "transitions" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "GetTransitionSettings" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Change the current settings of a transition", + "param": [ + "{String} `transitionName` Transition name", + "{Object} `transitionSettings` Transition settings (they can be partial)" + ], + "return": "{Object} `transitionSettings` Updated transition settings", + "api": "requests", + "name": "SetTransitionSettings", + "category": "transitions", + "since": "4.9.0", + "returns": [ + { + "type": "Object", + "name": "transitionSettings", + "description": "Updated transition settings" + } + ], + "params": [ + { + "type": "String", + "name": "transitionName", + "description": "Transition name" + }, + { + "type": "Object", + "name": "transitionSettings", + "description": "Transition settings (they can be partial)" + } + ], + "names": [ + { + "name": "", + "description": "SetTransitionSettings" + } + ], + "categories": [ + { + "name": "", + "description": "transitions" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "SetTransitionSettings" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "Release the T-Bar (like a user releasing their mouse button after moving it).\n*YOU MUST CALL THIS if you called `SetTBarPosition` with the `release` parameter set to `false`.*", + "api": "requests", + "name": "ReleaseTBar", + "category": "transitions", + "since": "4.9.0", + "names": [ + { + "name": "", + "description": "ReleaseTBar" + } + ], + "categories": [ + { + "name": "", + "description": "transitions" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "ReleaseTBar" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "\n\nIf your code needs to perform multiple successive T-Bar moves (e.g. : in an animation, or in response to a user moving a T-Bar control in your User Interface), set `release` to false and call `ReleaseTBar` later once the animation/interaction is over.", + "param": [ + "{double} `position` T-Bar position. This value must be between 0.0 and 1.0.", + "{boolean (optional)} `release` Whether or not the T-Bar gets released automatically after setting its new position (like a user releasing their mouse button after moving the T-Bar). Call `ReleaseTBar` manually if you set `release` to false. Defaults to true." + ], + "api": "requests", + "name": "SetTBarPosition", + "category": "transitions", + "since": "4.9.0", + "params": [ + { + "type": "double", + "name": "position", + "description": "T-Bar position. This value must be between 0.0 and 1.0." + }, + { + "type": "boolean (optional)", + "name": "release", + "description": "Whether or not the T-Bar gets released automatically after setting its new position (like a user releasing their mouse button after moving the T-Bar). Call `ReleaseTBar` manually if you set `release` to false. Defaults to true." + } + ], + "names": [ + { + "name": "", + "description": "SetTBarPosition" + } + ], + "categories": [ + { + "name": "", + "description": "transitions" + } + ], + "sinces": [ + { + "name": "", + "description": "4.9.0" + } + ], + "heading": { + "level": 2, + "text": "SetTBarPosition" + }, + "lead": "Set the manual position of the T-Bar (in Studio Mode) to the specified value. Will return an error if OBS is not in studio mode or if the current transition doesn't support T-Bar control.", + "type": "class", + "examples": [] } ] } diff --git a/docs/generated/protocol.md b/docs/generated/protocol.md index 77f780d5..c537330b 100644 --- a/docs/generated/protocol.md +++ b/docs/generated/protocol.md @@ -1,12 +1,14 @@ -# obs-websocket 4.7.0 protocol reference +# obs-websocket 4.9.0 protocol reference # General Introduction Messages are exchanged between the client and the server as JSON objects. -This protocol is based on the original OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio. +This protocol is based on the original OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio. As of v5.0.0, backwards compatability with the protocol will not be kept. # Authentication +**Starting with obs-websocket 4.9, authentication is enabled by default and users are encouraged to configure a password on first run.** + `obs-websocket` uses SHA256 to transmit credentials. A request for [`GetAuthRequired`](#getauthrequired) returns two elements: @@ -35,6 +37,8 @@ auth_response_hash = binary_sha256(auth_response_string) auth_response = base64_encode(auth_response_hash) ``` +You can also refer to any of the client libraries listed on the [README](README.md) for examples of how to authenticate. + @@ -47,6 +51,7 @@ auth_response = base64_encode(auth_response_hash) * [SceneItemTransform](#sceneitemtransform) * [OBSStats](#obsstats) * [Output](#output) + * [ScenesCollection](#scenescollection) * [Scene](#scene) - [Events](#events) * [Scenes](#scenes) @@ -92,6 +97,8 @@ auth_response = base64_encode(auth_response_hash) + [SourceDestroyed](#sourcedestroyed) + [SourceVolumeChanged](#sourcevolumechanged) + [SourceMuteStateChanged](#sourcemutestatechanged) + + [SourceAudioDeactivated](#sourceaudiodeactivated) + + [SourceAudioActivated](#sourceaudioactivated) + [SourceAudioSyncOffsetChanged](#sourceaudiosyncoffsetchanged) + [SourceAudioMixersChanged](#sourceaudiomixerschanged) + [SourceRenamed](#sourcerenamed) @@ -99,6 +106,16 @@ auth_response = base64_encode(auth_response_hash) + [SourceFilterRemoved](#sourcefilterremoved) + [SourceFilterVisibilityChanged](#sourcefiltervisibilitychanged) + [SourceFiltersReordered](#sourcefiltersreordered) + * [Media](#media) + + [MediaPlaying](#mediaplaying) + + [MediaPaused](#mediapaused) + + [MediaRestarted](#mediarestarted) + + [MediaStopped](#mediastopped) + + [MediaNext](#medianext) + + [MediaPrevious](#mediaprevious) + + [MediaStarted](#mediastarted) + + [MediaEnded](#mediaended) + * [Scene Items](#scene-items) + [SourceOrderChanged](#sourceorderchanged) + [SceneItemAdded](#sceneitemadded) + [SceneItemRemoved](#sceneitemremoved) @@ -122,55 +139,36 @@ auth_response = base64_encode(auth_response_hash) + [BroadcastCustomMessage](#broadcastcustommessage-1) + [GetVideoInfo](#getvideoinfo) + [OpenProjector](#openprojector) - * [Outputs](#outputs) - + [ListOutputs](#listoutputs) - + [GetOutputInfo](#getoutputinfo) - + [StartOutput](#startoutput) - + [StopOutput](#stopoutput) - * [Profiles](#profiles-1) - + [SetCurrentProfile](#setcurrentprofile) - + [GetCurrentProfile](#getcurrentprofile) - + [ListProfiles](#listprofiles) - * [Recording](#recording-1) - + [StartStopRecording](#startstoprecording) - + [StartRecording](#startrecording) - + [StopRecording](#stoprecording) - + [PauseRecording](#pauserecording) - + [ResumeRecording](#resumerecording) - + [SetRecordingFolder](#setrecordingfolder) - + [GetRecordingFolder](#getrecordingfolder) - * [Replay Buffer](#replay-buffer-1) - + [StartStopReplayBuffer](#startstopreplaybuffer) - + [StartReplayBuffer](#startreplaybuffer) - + [StopReplayBuffer](#stopreplaybuffer) - + [SaveReplayBuffer](#savereplaybuffer) - * [Scene Collections](#scene-collections) - + [SetCurrentSceneCollection](#setcurrentscenecollection) - + [GetCurrentSceneCollection](#getcurrentscenecollection) - + [ListSceneCollections](#listscenecollections) - * [Scene Items](#scene-items) - + [GetSceneItemProperties](#getsceneitemproperties) - + [SetSceneItemProperties](#setsceneitemproperties) - + [ResetSceneItem](#resetsceneitem) - + [SetSceneItemRender](#setsceneitemrender) - + [SetSceneItemPosition](#setsceneitemposition) - + [SetSceneItemTransform](#setsceneitemtransform) - + [SetSceneItemCrop](#setsceneitemcrop) - + [DeleteSceneItem](#deletesceneitem) - + [DuplicateSceneItem](#duplicatesceneitem) - * [Scenes](#scenes-1) - + [SetCurrentScene](#setcurrentscene) - + [GetCurrentScene](#getcurrentscene) - + [GetSceneList](#getscenelist) - + [ReorderSceneItems](#reordersceneitems) + + [TriggerHotkeyByName](#triggerhotkeybyname) + + [TriggerHotkeyBySequence](#triggerhotkeybysequence) + + [ExecuteBatch](#executebatch) + + [Sleep](#sleep) + * [Media Control](#media-control) + + [PlayPauseMedia](#playpausemedia) + + [RestartMedia](#restartmedia) + + [StopMedia](#stopmedia) + + [NextMedia](#nextmedia) + + [PreviousMedia](#previousmedia) + + [GetMediaDuration](#getmediaduration) + + [GetMediaTime](#getmediatime) + + [SetMediaTime](#setmediatime) + + [ScrubMedia](#scrubmedia) + + [GetMediaState](#getmediastate) * [Sources](#sources-1) + + [GetMediaSourcesList](#getmediasourceslist) + + [CreateSource](#createsource) + [GetSourcesList](#getsourceslist) + [GetSourceTypesList](#getsourcetypeslist) + [GetVolume](#getvolume) + [SetVolume](#setvolume) + + [SetTracks](#settracks) + + [GetTracks](#gettracks) + [GetMute](#getmute) + [SetMute](#setmute) + [ToggleMute](#togglemute) + + [GetSourceActive](#getsourceactive) + + [GetAudioActive](#getaudioactive) + + [SetSourceName](#setsourcename) + [SetSyncOffset](#setsyncoffset) + [GetSyncOffset](#getsyncoffset) + [GetSourceSettings](#getsourcesettings) @@ -190,7 +188,60 @@ auth_response = base64_encode(auth_response_hash) + [MoveSourceFilter](#movesourcefilter) + [SetSourceFilterSettings](#setsourcefiltersettings) + [SetSourceFilterVisibility](#setsourcefiltervisibility) + + [GetAudioMonitorType](#getaudiomonitortype) + + [SetAudioMonitorType](#setaudiomonitortype) + + [GetSourceDefaultSettings](#getsourcedefaultsettings) + [TakeSourceScreenshot](#takesourcescreenshot) + + [RefreshBrowserSource](#refreshbrowsersource) + * [Outputs](#outputs) + + [ListOutputs](#listoutputs) + + [GetOutputInfo](#getoutputinfo) + + [StartOutput](#startoutput) + + [StopOutput](#stopoutput) + * [Profiles](#profiles-1) + + [SetCurrentProfile](#setcurrentprofile) + + [GetCurrentProfile](#getcurrentprofile) + + [ListProfiles](#listprofiles) + * [Recording](#recording-1) + + [GetRecordingStatus](#getrecordingstatus) + + [StartStopRecording](#startstoprecording) + + [StartRecording](#startrecording) + + [StopRecording](#stoprecording) + + [PauseRecording](#pauserecording) + + [ResumeRecording](#resumerecording) + + [SetRecordingFolder](#setrecordingfolder) + + [GetRecordingFolder](#getrecordingfolder) + * [Replay Buffer](#replay-buffer-1) + + [GetReplayBufferStatus](#getreplaybufferstatus) + + [StartStopReplayBuffer](#startstopreplaybuffer) + + [StartReplayBuffer](#startreplaybuffer) + + [StopReplayBuffer](#stopreplaybuffer) + + [SaveReplayBuffer](#savereplaybuffer) + * [Scene Collections](#scene-collections) + + [SetCurrentSceneCollection](#setcurrentscenecollection) + + [GetCurrentSceneCollection](#getcurrentscenecollection) + + [ListSceneCollections](#listscenecollections) + * [Scene Items](#scene-items-1) + + [GetSceneItemList](#getsceneitemlist) + + [GetSceneItemProperties](#getsceneitemproperties) + + [SetSceneItemProperties](#setsceneitemproperties) + + [ResetSceneItem](#resetsceneitem) + + [SetSceneItemRender](#setsceneitemrender) + + [SetSceneItemPosition](#setsceneitemposition) + + [SetSceneItemTransform](#setsceneitemtransform) + + [SetSceneItemCrop](#setsceneitemcrop) + + [DeleteSceneItem](#deletesceneitem) + + [AddSceneItem](#addsceneitem) + + [DuplicateSceneItem](#duplicatesceneitem) + * [Scenes](#scenes-1) + + [SetCurrentScene](#setcurrentscene) + + [GetCurrentScene](#getcurrentscene) + + [GetSceneList](#getscenelist) + + [CreateScene](#createscene) + + [ReorderSceneItems](#reordersceneitems) + + [SetSceneTransitionOverride](#setscenetransitionoverride) + + [RemoveSceneTransitionOverride](#removescenetransitionoverride) + + [GetSceneTransitionOverride](#getscenetransitionoverride) * [Streaming](#streaming-1) + [GetStreamingStatus](#getstreamingstatus) + [StartStopStreaming](#startstopstreaming) @@ -214,6 +265,11 @@ auth_response = base64_encode(auth_response_hash) + [SetCurrentTransition](#setcurrenttransition) + [SetTransitionDuration](#settransitionduration) + [GetTransitionDuration](#gettransitionduration) + + [GetTransitionPosition](#gettransitionposition) + + [GetTransitionSettings](#gettransitionsettings) + + [SetTransitionSettings](#settransitionsettings) + + [ReleaseTBar](#releasetbar) + + [SetTBarPosition](#settbarposition) @@ -243,12 +299,13 @@ These are complex types, such as `Source` and `Scene`, which are used as argumen ## SceneItemTransform | Name | Type | Description | | ---- | :---: | ------------| -| `position.x` | _int_ | The x position of the scene item from the left. | -| `position.y` | _int_ | The y position of the scene item from the top. | +| `position.x` | _double_ | The x position of the scene item from the left. | +| `position.y` | _double_ | The y position of the scene item from the top. | | `position.alignment` | _int_ | The point on the scene item that the item is manipulated from. | | `rotation` | _double_ | The clockwise rotation of the scene item in degrees around the point of alignment. | | `scale.x` | _double_ | The x-scale factor of the scene item. | | `scale.y` | _double_ | The y-scale factor of the scene item. | +| `scale.filter` | _String_ | The scale filter of the source. Can be "OBS_SCALE_DISABLE", "OBS_SCALE_POINT", "OBS_SCALE_BICUBIC", "OBS_SCALE_BILINEAR", "OBS_SCALE_LANCZOS" or "OBS_SCALE_AREA". | | `crop.top` | _int_ | The number of pixels cropped off the top of the scene item before scaling. | | `crop.right` | _int_ | The number of pixels cropped off the right of the scene item before scaling. | | `crop.bottom` | _int_ | The number of pixels cropped off the bottom of the scene item before scaling. | @@ -291,13 +348,17 @@ These are complex types, such as `Source` and `Scene`, which are used as argumen | `flags.encoded` | _boolean_ | Output is encoded | | `flags.multiTrack` | _boolean_ | Output uses several audio tracks | | `flags.service` | _boolean_ | Output uses a service | -| `settings` | _Object_ | Output name | +| `settings` | _Object_ | Output settings | | `active` | _boolean_ | Output status (active or not) | | `reconnecting` | _boolean_ | Output reconnection status (reconnecting or not) | | `congestion` | _double_ | Output congestion | | `totalFrames` | _int_ | Number of frames sent | | `droppedFrames` | _int_ | Number of frames dropped | | `totalBytes` | _int_ | Total bytes sent | +## ScenesCollection +| Name | Type | Description | +| ---- | :---: | ------------| +| `sc-name` | _String_ | Name of the scene collection | ## Scene | Name | Type | Description | | ---- | :---: | ------------| @@ -343,12 +404,16 @@ Indicates a scene change. - Added in v0.3 -The scene list has been modified. -Scenes have been added, removed, or renamed. + + +Note: This event is not fired when the scenes are reordered. **Response Items:** -_No additional response items._ +| Name | Type | Description | +| ---- | :---: | ------------| +| `scenes` | _Array<Scene>_ | Scenes list. | + --- @@ -361,7 +426,10 @@ Triggered when switching to another scene collection or when renaming the curren **Response Items:** -_No additional response items._ +| Name | Type | Description | +| ---- | :---: | ------------| +| `sceneCollection` | _String_ | Name of the new current scene collection. | + --- @@ -374,7 +442,11 @@ Triggered when a scene collection is created, added, renamed, or removed. **Response Items:** -_No additional response items._ +| Name | Type | Description | +| ---- | :---: | ------------| +| `sceneCollections` | _Array<Object>_ | Scene collections list. | +| `sceneCollections.*.name` | _String_ | Scene collection name. | + --- @@ -406,7 +478,11 @@ Transitions have been added, removed, or renamed. **Response Items:** -_No additional response items._ +| Name | Type | Description | +| ---- | :---: | ------------| +| `transitions` | _Array<Object>_ | Transitions list. | +| `transitions.*.name` | _String_ | Transition name. | + --- @@ -440,7 +516,7 @@ A transition (other than "cut") has begun. | `name` | _String_ | Transition name. | | `type` | _String_ | Transition type. | | `duration` | _int_ | Transition duration (in milliseconds). Will be -1 for any transition with a fixed duration, such as a Stinger, due to limitations of the OBS API. | -| `from-scene` | _String_ | Source scene of the transition | +| `from-scene` | _String (optional)_ | Source scene of the transition | | `to-scene` | _String_ | Destination scene of the transition | @@ -452,7 +528,7 @@ A transition (other than "cut") has begun. - Added in v4.8.0 A transition (other than "cut") has ended. -Please note that the `from-scene` field is not available in TransitionEnd. +Note: The `from-scene` field is not available in TransitionEnd. **Response Items:** @@ -480,7 +556,7 @@ A stinger transition has finished playing its video. | `name` | _String_ | Transition name. | | `type` | _String_ | Transition type. | | `duration` | _int_ | Transition duration (in milliseconds). | -| `from-scene` | _String_ | Source scene of the transition | +| `from-scene` | _String (optional)_ | Source scene of the transition | | `to-scene` | _String_ | Destination scene of the transition | @@ -497,7 +573,10 @@ Triggered when switching to another profile or when renaming the current profile **Response Items:** -_No additional response items._ +| Name | Type | Description | +| ---- | :---: | ------------| +| `profile` | _String_ | Name of the new current profile. | + --- @@ -510,7 +589,11 @@ Triggered when a profile is created, added, renamed, or removed. **Response Items:** -_No additional response items._ +| Name | Type | Description | +| ---- | :---: | ------------| +| `profiles` | _Array<Object>_ | Profiles list. | +| `profiles.*.name` | _String_ | Profile name. | + --- @@ -579,7 +662,7 @@ _No additional response items._ - Added in v0.3 -Emit every 2 seconds. +Emitted every 2 seconds when stream is active. **Response Items:** @@ -615,7 +698,10 @@ Emit every 2 seconds. - Added in v0.3 -A request to start recording has been issued. + + +Note: `recordingFilename` is not provided in this event because this information +is not available at the time this event is emitted. **Response Items:** @@ -632,7 +718,10 @@ Recording started successfully. **Response Items:** -_No additional response items._ +| Name | Type | Description | +| ---- | :---: | ------------| +| `recordingFilename` | _String_ | Absolute path to the file of the current recording. | + --- @@ -645,7 +734,10 @@ A request to stop recording has been issued. **Response Items:** -_No additional response items._ +| Name | Type | Description | +| ---- | :---: | ------------| +| `recordingFilename` | _String_ | Absolute path to the file of the current recording. | + --- @@ -658,7 +750,10 @@ Recording stopped successfully. **Response Items:** -_No additional response items._ +| Name | Type | Description | +| ---- | :---: | ------------| +| `recordingFilename` | _String_ | Absolute path to the file of the current recording. | + --- @@ -762,7 +857,7 @@ _No additional response items._ ### Heartbeat -- Added in v +- Added in vv0.3 Emitted every 2 seconds after enabling it by calling SetHeartbeat. @@ -791,7 +886,7 @@ Emitted every 2 seconds after enabling it by calling SetHeartbeat. - Added in v4.7.0 -A custom broadcast message was received +A custom broadcast message, sent by the server, requested by one of the websocket clients. **Response Items:** @@ -855,6 +950,7 @@ The volume of a source has changed. | ---- | :---: | ------------| | `sourceName` | _String_ | Source name | | `volume` | _float_ | Source volume | +| `volumeDb` | _float_ | Source volume in Decibel | --- @@ -874,6 +970,38 @@ A source has been muted or unmuted. | `muted` | _boolean_ | Mute status of the source | +--- + +### SourceAudioDeactivated + + +- Added in v4.9.0 + +A source has removed audio. + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name | + + +--- + +### SourceAudioActivated + + +- Added in v4.9.0 + +A source has added audio. + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name | + + --- ### SourceAudioSyncOffsetChanged @@ -926,6 +1054,7 @@ A source has been renamed. | ---- | :---: | ------------| | `previousName` | _String_ | Previous source name | | `newName` | _String_ | New source name | +| `sourceType` | _String_ | Type of source (input, scene, filter, transition) | --- @@ -1000,16 +1129,173 @@ Filters in a source have been reordered. | `filters` | _Array<Object>_ | Ordered Filters list | | `filters.*.name` | _String_ | Filter name | | `filters.*.type` | _String_ | Filter type | +| `filters.*.enabled` | _boolean_ | Filter visibility status | --- +## Media + +### MediaPlaying + + +- Added in v4.9.0 + + + +Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name | +| `sourceKind` | _String_ | The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) | + + +--- + +### MediaPaused + + +- Added in v4.9.0 + + + +Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name | +| `sourceKind` | _String_ | The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) | + + +--- + +### MediaRestarted + + +- Added in v4.9.0 + + + +Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name | +| `sourceKind` | _String_ | The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) | + + +--- + +### MediaStopped + + +- Added in v4.9.0 + + + +Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name | +| `sourceKind` | _String_ | The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) | + + +--- + +### MediaNext + + +- Added in v4.9.0 + + + +Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name | +| `sourceKind` | _String_ | The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) | + + +--- + +### MediaPrevious + + +- Added in v4.9.0 + + + +Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name | +| `sourceKind` | _String_ | The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) | + + +--- + +### MediaStarted + + +- Added in v4.9.0 + + + +Note: These events are emitted by the OBS sources themselves. For example when the media file starts playing. The behavior depends on the type of media source being used. + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name | +| `sourceKind` | _String_ | The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) | + + +--- + +### MediaEnded + + +- Added in v4.9.0 + + + +Note: These events are emitted by the OBS sources themselves. For example when the media file ends. The behavior depends on the type of media source being used. + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name | +| `sourceKind` | _String_ | The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) | + + +--- + +## Scene Items + ### SourceOrderChanged - Added in v4.0.0 -Scene items have been reordered. +Scene items within a scene have been reordered. **Response Items:** @@ -1028,7 +1314,7 @@ Scene items have been reordered. - Added in v4.0.0 -An item has been added to the current scene. +A scene item has been added to a scene. **Response Items:** @@ -1046,7 +1332,7 @@ An item has been added to the current scene. - Added in v4.0.0 -An item has been removed from the current scene. +A scene item has been removed from a scene. **Response Items:** @@ -1064,7 +1350,7 @@ An item has been removed from the current scene. - Added in v4.0.0 -An item's visibility has been toggled. +A scene item's visibility has been toggled. **Response Items:** @@ -1081,9 +1367,9 @@ An item's visibility has been toggled. ### SceneItemLockChanged -- Unreleased +- Added in v4.8.0 -An item's locked status has been toggled. +A scene item's locked status has been toggled. **Response Items:** @@ -1102,7 +1388,7 @@ An item's locked status has been toggled. - Added in v4.6.0 -An item's transform has been changed. +A scene item's transform has been changed. **Response Items:** @@ -1198,7 +1484,7 @@ Requests are sent by the client and require at least the following two fields: Once a request is sent, the server will return a JSON response with at least the following fields: - `message-id` _String_: The client defined identifier specified in the request. - `status` _String_: Response status, will be one of the following: `ok`, `error` -- `error` _String_: An error message accompanying an `error` status. +- `error` _String (Optional)_: An error message accompanying an `error` status. Additional information may be required/returned depending on the request type. See below for more information. @@ -1274,6 +1560,7 @@ _No additional response items._ ### SetHeartbeat +- **⚠️ Deprecated. Since 4.9.0. Please poll the appropriate data using requests. Will be removed in v5.0.0. ⚠️** - Added in v4.3.0 @@ -1347,7 +1634,7 @@ _No specified parameters._ | Name | Type | Description | | ---- | :---: | ------------| -| `stats` | _OBSStats_ | OBS stats | +| `stats` | _OBSStats_ | [OBS stats](#obsstats) | --- @@ -1404,7 +1691,7 @@ _No specified parameters._ ### OpenProjector -- Unreleased +- Added in v4.8.0 Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 or newer. @@ -1412,9 +1699,9 @@ Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 | Name | Type | Description | | ---- | :---: | ------------| -| `type` | _String (Optional)_ | Type of projector: Preview (default), Source, Scene, StudioProgram, or Multiview (case insensitive). | +| `type` | _String (Optional)_ | Type of projector: `Preview` (default), `Source`, `Scene`, `StudioProgram`, or `Multiview` (case insensitive). | | `monitor` | _int (Optional)_ | Monitor to open the projector on. If -1 or omitted, opens a window. | -| `geometry` | _String (Optional)_ | Size and position of the projector window (only if monitor is -1). Encoded in Base64 using Qt's geometry encoding (https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors. | +| `geometry` | _String (Optional)_ | Size and position of the projector window (only if monitor is -1). Encoded in Base64 using [Qt's geometry encoding](https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors. | | `name` | _String (Optional)_ | Name of the source or scene to be displayed (ignored for other projector types). | @@ -1424,63 +1711,18 @@ _No additional response items._ --- -## Outputs - -### ListOutputs +### TriggerHotkeyByName -- Added in v4.7.0 +- Added in v4.9.0 -List existing outputs - -**Request Fields:** - -_No specified parameters._ - -**Response Items:** - -| Name | Type | Description | -| ---- | :---: | ------------| -| `outputs` | _Array<Output>_ | Outputs list | - - ---- - -### GetOutputInfo - - -- Added in v4.7.0 - -Get information about a single output +Executes hotkey routine, identified by hotkey unique name **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| -| `outputName` | _String_ | Output name | - - -**Response Items:** - -| Name | Type | Description | -| ---- | :---: | ------------| -| `outputInfo` | _Output_ | Output info | - - ---- - -### StartOutput - - -- Added in v4.7.0 - -Start an output - -**Request Fields:** - -| Name | Type | Description | -| ---- | :---: | ------------| -| `outputName` | _String_ | Output name | +| `hotkeyName` | _String_ | Unique name of the hotkey, as defined when registering the hotkey (e.g. "ReplayBuffer.Save") | **Response Items:** @@ -1489,19 +1731,23 @@ _No additional response items._ --- -### StopOutput +### TriggerHotkeyBySequence -- Added in v4.7.0 +- Added in v4.9.0 -Stop an output +Executes hotkey routine, identified by bound combination of keys. A single key combination might trigger multiple hotkey routines depending on user settings **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| -| `outputName` | _String_ | Output name | -| `force` | _boolean (optional)_ | Force stop (default: false) | +| `keyId` | _String_ | Main key identifier (e.g. `OBS_KEY_A` for key "A"). Available identifiers [here](https://github.com/obsproject/obs-studio/blob/master/libobs/obs-hotkeys.h) | +| `keyModifiers` | _Object (Optional)_ | Optional key modifiers object. False entries can be ommitted | +| `keyModifiers.shift` | _boolean_ | Trigger Shift Key | +| `keyModifiers.alt` | _boolean_ | Trigger Alt Key | +| `keyModifiers.control` | _boolean_ | Trigger Control (Ctrl) Key | +| `keyModifiers.command` | _boolean_ | Trigger Command Key (Mac) | **Response Items:** @@ -1510,20 +1756,47 @@ _No additional response items._ --- -## Profiles - -### SetCurrentProfile +### ExecuteBatch -- Added in v4.0.0 +- Added in v4.9.0 -Set the currently active profile. +Executes a list of requests sequentially (one-by-one on the same thread). **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| -| `profile-name` | _String_ | Name of the desired profile. | +| `requests` | _Array<Object>_ | Array of requests to perform. Executed in order. | +| `requests.*.request-type` | _String_ | Request type. Eg. `GetVersion`. | +| `requests.*.message-id` | _String (Optional)_ | ID of the individual request. Can be any string and not required to be unique. Defaults to empty string if not specified. | +| `abortOnFail` | _boolean (Optional)_ | Stop processing batch requests if one returns a failure. | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `results` | _Array<Object>_ | Batch requests results, ordered sequentially. | +| `results.*.message-id` | _String_ | ID of the individual request which was originally provided by the client. | +| `results.*.status` | _String_ | Status response as string. Either `ok` or `error`. | +| `results.*.error` | _String (Optional)_ | Error message accompanying an `error` status. | + + +--- + +### Sleep + + +- Unreleased + +Waits for the specified duration. Designed to be used in `ExecuteBatch` operations. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sleepMillis` | _int_ | Delay in milliseconds to wait before continuing. | **Response Items:** @@ -1532,153 +1805,22 @@ _No additional response items._ --- -### GetCurrentProfile +## Media Control +### PlayPauseMedia -- Added in v4.0.0 -Get the name of the current profile. +- Added in v4.9.0 -**Request Fields:** - -_No specified parameters._ - -**Response Items:** - -| Name | Type | Description | -| ---- | :---: | ------------| -| `profile-name` | _String_ | Name of the currently active profile. | - - ---- - -### ListProfiles - - -- Added in v4.0.0 - -Get a list of available profiles. - -**Request Fields:** - -_No specified parameters._ - -**Response Items:** - -| Name | Type | Description | -| ---- | :---: | ------------| -| `profiles` | _Array<Object>_ | List of available profiles. | - - ---- - -## Recording - -### StartStopRecording - - -- Added in v0.3 - -Toggle recording on or off. - -**Request Fields:** - -_No specified parameters._ - -**Response Items:** - -_No additional response items._ - ---- - -### StartRecording - - -- Added in v4.1.0 - -Start recording. -Will return an `error` if recording is already active. - -**Request Fields:** - -_No specified parameters._ - -**Response Items:** - -_No additional response items._ - ---- - -### StopRecording - - -- Added in v4.1.0 - -Stop recording. -Will return an `error` if recording is not active. - -**Request Fields:** - -_No specified parameters._ - -**Response Items:** - -_No additional response items._ - ---- - -### PauseRecording - - -- Added in v4.7.0 - -Pause the current recording. -Returns an error if recording is not active or already paused. - -**Request Fields:** - -_No specified parameters._ - -**Response Items:** - -_No additional response items._ - ---- - -### ResumeRecording - - -- Added in v4.7.0 - -Resume/unpause the current recording (if paused). -Returns an error if recording is not active or not paused. - -**Request Fields:** - -_No specified parameters._ - -**Response Items:** - -_No additional response items._ - ---- - -### SetRecordingFolder - - -- Added in v4.1.0 - - - -Please note: if `SetRecordingFolder` is called while a recording is -in progress, the change won't be applied immediately and will be -effective on the next recording. +Pause or play a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +Note :Leaving out `playPause` toggles the current pause state **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| -| `rec-folder` | _String_ | Path of the recording folder. | +| `sourceName` | _String_ | Source name. | +| `playPause` | _boolean_ | (optional) Whether to pause or play the source. `false` for play, `true` for pause. | **Response Items:** @@ -1687,117 +1829,18 @@ _No additional response items._ --- -### GetRecordingFolder +### RestartMedia -- Added in v4.1.0 +- Added in v4.9.0 -Get the path of the current recording folder. - -**Request Fields:** - -_No specified parameters._ - -**Response Items:** - -| Name | Type | Description | -| ---- | :---: | ------------| -| `rec-folder` | _String_ | Path of the recording folder. | - - ---- - -## Replay Buffer - -### StartStopReplayBuffer - - -- Added in v4.2.0 - -Toggle the Replay Buffer on/off. - -**Request Fields:** - -_No specified parameters._ - -**Response Items:** - -_No additional response items._ - ---- - -### StartReplayBuffer - - -- Added in v4.2.0 - -Start recording into the Replay Buffer. -Will return an `error` if the Replay Buffer is already active or if the -"Save Replay Buffer" hotkey is not set in OBS' settings. -Setting this hotkey is mandatory, even when triggering saves only -through obs-websocket. - -**Request Fields:** - -_No specified parameters._ - -**Response Items:** - -_No additional response items._ - ---- - -### StopReplayBuffer - - -- Added in v4.2.0 - -Stop recording into the Replay Buffer. -Will return an `error` if the Replay Buffer is not active. - -**Request Fields:** - -_No specified parameters._ - -**Response Items:** - -_No additional response items._ - ---- - -### SaveReplayBuffer - - -- Added in v4.2.0 - -Flush and save the contents of the Replay Buffer to disk. This is -basically the same as triggering the "Save Replay Buffer" hotkey. -Will return an `error` if the Replay Buffer is not active. - -**Request Fields:** - -_No specified parameters._ - -**Response Items:** - -_No additional response items._ - ---- - -## Scene Collections - -### SetCurrentSceneCollection - - -- Added in v4.0.0 - -Change the active scene collection. +Restart a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| -| `sc-name` | _String_ | Name of the desired scene collection. | +| `sourceName` | _String_ | Source name. | **Response Items:** @@ -1806,127 +1849,18 @@ _No additional response items._ --- -### GetCurrentSceneCollection +### StopMedia -- Added in v4.0.0 +- Added in v4.9.0 -Get the name of the current scene collection. - -**Request Fields:** - -_No specified parameters._ - -**Response Items:** - -| Name | Type | Description | -| ---- | :---: | ------------| -| `sc-name` | _String_ | Name of the currently active scene collection. | - - ---- - -### ListSceneCollections - - -- Added in v4.0.0 - -List available scene collections - -**Request Fields:** - -_No specified parameters._ - -**Response Items:** - -| Name | Type | Description | -| ---- | :---: | ------------| -| `scene-collections` | _Array<String>_ | Scene collections list | - - ---- - -## Scene Items - -### GetSceneItemProperties - - -- Added in v4.3.0 - -Gets the scene specific properties of the specified source item. -Coordinates are relative to the item's parent (the scene or group it belongs to). +Stop a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| -| `scene-name` | _String (optional)_ | the name of the scene that the source item belongs to. Defaults to the current scene. | -| `item` | _String_ | The name of the source. | - - -**Response Items:** - -| Name | Type | Description | -| ---- | :---: | ------------| -| `name` | _String_ | The name of the source. | -| `position.x` | _int_ | The x position of the source from the left. | -| `position.y` | _int_ | The y position of the source from the top. | -| `position.alignment` | _int_ | The point on the source that the item is manipulated from. | -| `rotation` | _double_ | The clockwise rotation of the item in degrees around the point of alignment. | -| `scale.x` | _double_ | The x-scale factor of the source. | -| `scale.y` | _double_ | The y-scale factor of the source. | -| `crop.top` | _int_ | The number of pixels cropped off the top of the source before scaling. | -| `crop.right` | _int_ | The number of pixels cropped off the right of the source before scaling. | -| `crop.bottom` | _int_ | The number of pixels cropped off the bottom of the source before scaling. | -| `crop.left` | _int_ | The number of pixels cropped off the left of the source before scaling. | -| `visible` | _bool_ | If the source is visible. | -| `muted` | _bool_ | If the source is muted. | -| `locked` | _bool_ | If the source's transform is locked. | -| `bounds.type` | _String_ | Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE". | -| `bounds.alignment` | _int_ | Alignment of the bounding box. | -| `bounds.x` | _double_ | Width of the bounding box. | -| `bounds.y` | _double_ | Height of the bounding box. | -| `sourceWidth` | _int_ | Base width (without scaling) of the source | -| `sourceHeight` | _int_ | Base source (without scaling) of the source | -| `width` | _double_ | Scene item width (base source width multiplied by the horizontal scaling factor) | -| `height` | _double_ | Scene item height (base source height multiplied by the vertical scaling factor) | -| `alignment` | _int_ | The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis. | -| `parentGroupName` | _String (optional)_ | Name of the item's parent (if this item belongs to a group) | -| `groupChildren` | _Array<SceneItemTransform> (optional)_ | List of children (if this item is a group) | - - ---- - -### SetSceneItemProperties - - -- Added in v4.3.0 - -Sets the scene specific properties of a source. Unspecified properties will remain unchanged. -Coordinates are relative to the item's parent (the scene or group it belongs to). - -**Request Fields:** - -| Name | Type | Description | -| ---- | :---: | ------------| -| `scene-name` | _String (optional)_ | the name of the scene that the source item belongs to. Defaults to the current scene. | -| `item` | _String_ | The name of the source. | -| `position.x` | _int (optional)_ | The new x position of the source. | -| `position.y` | _int (optional)_ | The new y position of the source. | -| `position.alignment` | _int (optional)_ | The new alignment of the source. | -| `rotation` | _double (optional)_ | The new clockwise rotation of the item in degrees. | -| `scale.x` | _double (optional)_ | The new x scale of the item. | -| `scale.y` | _double (optional)_ | The new y scale of the item. | -| `crop.top` | _int (optional)_ | The new amount of pixels cropped off the top of the source before scaling. | -| `crop.bottom` | _int (optional)_ | The new amount of pixels cropped off the bottom of the source before scaling. | -| `crop.left` | _int (optional)_ | The new amount of pixels cropped off the left of the source before scaling. | -| `crop.right` | _int (optional)_ | The new amount of pixels cropped off the right of the source before scaling. | -| `visible` | _bool (optional)_ | The new visibility of the source. 'true' shows source, 'false' hides source. | -| `locked` | _bool (optional)_ | The new locked status of the source. 'true' keeps it in its current position, 'false' allows movement. | -| `bounds.type` | _String (optional)_ | The new bounds type of the source. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE". | -| `bounds.alignment` | _int (optional)_ | The new alignment of the bounding box. (0-2, 4-6, 8-10) | -| `bounds.x` | _double (optional)_ | The new width of the bounding box. | -| `bounds.y` | _double (optional)_ | The new height of the bounding box. | +| `sourceName` | _String_ | Source name. | **Response Items:** @@ -1935,19 +1869,18 @@ _No additional response items._ --- -### ResetSceneItem +### NextMedia -- Added in v4.2.0 +- Added in v4.9.0 -Reset a scene item. +Skip to the next media item in the playlist. Supports only vlc media source (as of OBS v25.0.8) **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| -| `scene-name` | _String (optional)_ | Name of the scene the source belongs to. Defaults to the current scene. | -| `item` | _String_ | Name of the source item. | +| `sourceName` | _String_ | Source name. | **Response Items:** @@ -1956,21 +1889,18 @@ _No additional response items._ --- -### SetSceneItemRender +### PreviousMedia -- **⚠️ Deprecated. Since 4.3.0. Prefer the use of SetSceneItemProperties. ⚠️** -- Added in v0.3 +- Added in v4.9.0 -Show or hide a specified source item in a specified scene. +Go to the previous media item in the playlist. Supports only vlc media source (as of OBS v25.0.8) **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| -| `source` | _String_ | Scene item name in the specified scene. | -| `render` | _boolean_ | true = shown ; false = hidden | -| `scene-name` | _String (optional)_ | Name of the scene where the source resides. Defaults to the currently active scene. | +| `sourceName` | _String_ | Source name. | **Response Items:** @@ -1979,22 +1909,66 @@ _No additional response items._ --- -### SetSceneItemPosition +### GetMediaDuration -- **⚠️ Deprecated. Since 4.3.0. Prefer the use of SetSceneItemProperties. ⚠️** -- Added in v4.0.0 +- Added in v4.9.0 -Sets the coordinates of a specified source item. +Get the length of media in milliseconds. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +Note: For some reason, for the first 5 or so seconds that the media is playing, the total duration can be off by upwards of 50ms. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| -| `scene-name` | _String (optional)_ | The name of the scene that the source item belongs to. Defaults to the current scene. | -| `item` | _String_ | The name of the source item. | -| `x` | _double_ | X coordinate. | -| `y` | _double_ | Y coordinate. | +| `sourceName` | _String_ | Source name. | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `mediaDuration` | _int_ | The total length of media in milliseconds.. | + + +--- + +### GetMediaTime + + +- Added in v4.9.0 + +Get the current timestamp of media in milliseconds. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name. | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `timestamp` | _int_ | The time in milliseconds since the start of the media. | + + +--- + +### SetMediaTime + + +- Added in v4.9.0 + +Set the timestamp of a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name. | +| `timestamp` | _int_ | Milliseconds to set the timestamp to. | **Response Items:** @@ -2003,23 +1977,20 @@ _No additional response items._ --- -### SetSceneItemTransform +### ScrubMedia -- **⚠️ Deprecated. Since 4.3.0. Prefer the use of SetSceneItemProperties. ⚠️** -- Added in v4.0.0 +- Added in v4.9.0 -Set the transform of the specified source item. +Scrub media using a supplied offset. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +Note: Due to processing/network delays, this request is not perfect. The processing rate of this request has also not been tested. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| -| `scene-name` | _String (optional)_ | The name of the scene that the source item belongs to. Defaults to the current scene. | -| `item` | _String_ | The name of the source item. | -| `x-scale` | _double_ | Width scale factor. | -| `y-scale` | _double_ | Height scale factor. | -| `rotation` | _double_ | Source item rotation (in degrees). | +| `sourceName` | _String_ | Source name. | +| `timeOffset` | _int_ | Millisecond offset (positive or negative) to offset the current media position. | **Response Items:** @@ -2028,174 +1999,81 @@ _No additional response items._ --- -### SetSceneItemCrop +### GetMediaState -- **⚠️ Deprecated. Since 4.3.0. Prefer the use of SetSceneItemProperties. ⚠️** -- Added in v4.1.0 +- Added in v4.9.0 -Sets the crop coordinates of the specified source item. +Get the current playing state of a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| -| `scene-name` | _String (optional)_ | the name of the scene that the source item belongs to. Defaults to the current scene. | -| `item` | _String_ | The name of the source. | -| `top` | _int_ | Pixel position of the top of the source item. | -| `bottom` | _int_ | Pixel position of the bottom of the source item. | -| `left` | _int_ | Pixel position of the left of the source item. | -| `right` | _int_ | Pixel position of the right of the source item. | - - -**Response Items:** - -_No additional response items._ - ---- - -### DeleteSceneItem - - -- Added in v4.5.0 - -Deletes a scene item. - -**Request Fields:** - -| Name | Type | Description | -| ---- | :---: | ------------| -| `scene` | _String (optional)_ | Name of the scene the source belongs to. Defaults to the current scene. | -| `item` | _Object_ | item to delete (required) | -| `item.name` | _String_ | name of the scene item (prefer `id`, including both is acceptable). | -| `item.id` | _int_ | id of the scene item. | - - -**Response Items:** - -_No additional response items._ - ---- - -### DuplicateSceneItem - - -- Added in v4.5.0 - -Duplicates a scene item. - -**Request Fields:** - -| Name | Type | Description | -| ---- | :---: | ------------| -| `fromScene` | _String (optional)_ | Name of the scene to copy the item from. Defaults to the current scene. | -| `toScene` | _String (optional)_ | Name of the scene to create the item in. Defaults to the current scene. | -| `item` | _Object_ | item to duplicate (required) | -| `item.name` | _String_ | name of the scene item (prefer `id`, including both is acceptable). | -| `item.id` | _int_ | id of the scene item. | +| `sourceName` | _String_ | Source name. | **Response Items:** | Name | Type | Description | | ---- | :---: | ------------| -| `scene` | _String_ | Name of the scene where the new item was created | -| `item` | _Object_ | New item info | -| `item.id` | _int_ | New item ID | -| `item.name` | _String_ | New item name | +| `mediaState` | _String_ | The media state of the provided source. States: `none`, `playing`, `opening`, `buffering`, `paused`, `stopped`, `ended`, `error`, `unknown` | ---- - -## Scenes - -### SetCurrentScene - - -- Added in v0.3 - -Switch to the specified scene. - -**Request Fields:** - -| Name | Type | Description | -| ---- | :---: | ------------| -| `scene-name` | _String_ | Name of the scene to switch to. | - - -**Response Items:** - -_No additional response items._ - ---- - -### GetCurrentScene - - -- Added in v0.3 - -Get the current scene's name and source items. - -**Request Fields:** - -_No specified parameters._ - -**Response Items:** - -| Name | Type | Description | -| ---- | :---: | ------------| -| `name` | _String_ | Name of the currently active scene. | -| `sources` | _Array<SceneItem>_ | Ordered list of the current scene's source items. | - - ---- - -### GetSceneList - - -- Added in v0.3 - -Get a list of scenes in the currently active profile. - -**Request Fields:** - -_No specified parameters._ - -**Response Items:** - -| Name | Type | Description | -| ---- | :---: | ------------| -| `current-scene` | _String_ | Name of the currently active scene. | -| `scenes` | _Array<Scene>_ | Ordered list of the current profile's scenes (See `[GetCurrentScene](#getcurrentscene)` for more information). | - - ---- - -### ReorderSceneItems - - -- Added in v4.5.0 - -Changes the order of scene items in the requested scene. - -**Request Fields:** - -| Name | Type | Description | -| ---- | :---: | ------------| -| `scene` | _String (optional)_ | Name of the scene to reorder (defaults to current). | -| `items` | _Array<Scene>_ | Ordered list of objects with name and/or id specified. Id preferred due to uniqueness per scene | -| `items[].id` | _int (optional)_ | Id of a specific scene item. Unique on a scene by scene basis. | -| `items[].name` | _String (optional)_ | Name of a scene item. Sufficiently unique if no scene items share sources within the scene. | - - -**Response Items:** - -_No additional response items._ - --- ## Sources +### GetMediaSourcesList + + +- Added in v4.9.0 + +List the media state of all media sources (vlc and media source) + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `mediaSources` | _Array<Object>_ | Array of sources | +| `mediaSources.*.sourceName` | _String_ | Unique source name | +| `mediaSources.*.sourceKind` | _String_ | Unique source internal type (a.k.a `ffmpeg_source` or `vlc_source`) | +| `mediaSources.*.mediaState` | _String_ | The current state of media for that source. States: `none`, `playing`, `opening`, `buffering`, `paused`, `stopped`, `ended`, `error`, `unknown` | + + +--- + +### CreateSource + + +- Added in v4.9.0 + +Create a source and add it as a sceneitem to a scene. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name. | +| `sourceKind` | _String_ | Source kind, Eg. `vlc_source`. | +| `sceneName` | _String_ | Scene to add the new source to. | +| `sourceSettings` | _Object (optional)_ | Source settings data. | +| `setVisible` | _boolean (optional)_ | Set the created SceneItem as visible or not. Defaults to true | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `itemId` | _int_ | ID of the SceneItem in the scene. | + + +--- + ### GetSourcesList @@ -2213,7 +2091,7 @@ _No specified parameters._ | ---- | :---: | ------------| | `sources` | _Array<Object>_ | Array of sources | | `sources.*.name` | _String_ | Unique source name | -| `sources.*.typeId` | _String_ | Non-unique source internal type (a.k.a type id) | +| `sources.*.typeId` | _String_ | Non-unique source internal type (a.k.a kind) | | `sources.*.type` | _String_ | Source type. Value is one of the following: "input", "filter", "transition", "scene" or "unknown" | @@ -2256,13 +2134,14 @@ _No specified parameters._ - Added in v4.0.0 -Get the volume of the specified source. +Get the volume of the specified source. Default response uses mul format, NOT SLIDER PERCENTAGE. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Source name. | +| `useDecibel` | _boolean (optional)_ | Output volume in decibels of attenuation instead of amplitude/mul. | **Response Items:** @@ -2270,7 +2149,7 @@ Get the volume of the specified source. | Name | Type | Description | | ---- | :---: | ------------| | `name` | _String_ | Source name. | -| `volume` | _double_ | Volume of the source. Between `0.0` and `1.0`. | +| `volume` | _double_ | Volume of the source. Between `0.0` and `20.0` if using mul, under `26.0` if using dB. | | `muted` | _boolean_ | Indicates whether the source is muted. | @@ -2281,20 +2160,71 @@ Get the volume of the specified source. - Added in v4.0.0 -Set the volume of the specified source. +Set the volume of the specified source. Default request format uses mul, NOT SLIDER PERCENTAGE. **Request Fields:** | Name | Type | Description | | ---- | :---: | ------------| | `source` | _String_ | Source name. | -| `volume` | _double_ | Desired volume. Must be between `0.0` and `1.0`. | +| `volume` | _double_ | Desired volume. Must be between `0.0` and `20.0` for mul, and under 26.0 for dB. OBS will interpret dB values under -100.0 as Inf. Note: The OBS volume sliders only reach a maximum of 1.0mul/0.0dB, however OBS actually supports larger values. | +| `useDecibel` | _boolean (optional)_ | Interperet `volume` data as decibels instead of amplitude/mul. | **Response Items:** _No additional response items._ +--- + +### SetTracks + + +- Unreleased + +Changes whether an audio track is active for a source. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name. | +| `track` | _int_ | Audio tracks 1-6. | +| `active` | _boolean_ | Whether audio track is active or not. | + + +**Response Items:** + +_No additional response items._ + +--- + +### GetTracks + + +- Unreleased + +Gets whether an audio track is active for a source. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name. | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `track1` | _boolean_ | | +| `track2` | _boolean_ | | +| `track3` | _boolean_ | | +| `track4` | _boolean_ | | +| `track5` | _boolean_ | | +| `track6` | _boolean_ | | + + --- ### GetMute @@ -2356,6 +2286,75 @@ Inverts the mute status of a specified source. | `source` | _String_ | Source name. | +**Response Items:** + +_No additional response items._ + +--- + +### GetSourceActive + + +- Unreleased + +Get the source's active status of a specified source (if it is showing in the final mix). + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name. | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceActive` | _boolean_ | Source active status of the source. | + + +--- + +### GetAudioActive + + +- Added in v4.9.0 + +Get the audio's active status of a specified source. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name. | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `audioActive` | _boolean_ | Audio active status of the source. | + + +--- + +### SetSourceName + + +- Added in v4.8.0 + + + +Note: If the new name already exists as a source, obs-websocket will return an error. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name. | +| `newName` | _String_ | New source name. | + + **Response Items:** _No additional response items._ @@ -2480,8 +2479,8 @@ Get the current properties of a Text GDI Plus source. | ---- | :---: | ------------| | `source` | _String_ | Source name. | | `align` | _String_ | Text Alignment ("left", "center", "right"). | -| `bk-color` | _int_ | Background color. | -| `bk-opacity` | _int_ | Background opacity (0-100). | +| `bk_color` | _int_ | Background color. | +| `bk_opacity` | _int_ | Background opacity (0-100). | | `chatlog` | _boolean_ | Chat log. | | `chatlog_lines` | _int_ | Chat log lines. | | `color` | _int_ | Text color. | @@ -2523,8 +2522,8 @@ Set the current properties of a Text GDI Plus source. | ---- | :---: | ------------| | `source` | _String_ | Name of the source. | | `align` | _String (optional)_ | Text Alignment ("left", "center", "right"). | -| `bk-color` | _int (optional)_ | Background color. | -| `bk-opacity` | _int (optional)_ | Background opacity (0-100). | +| `bk_color` | _int (optional)_ | Background color. | +| `bk_opacity` | _int (optional)_ | Background opacity (0-100). | | `chatlog` | _boolean (optional)_ | Chat log. | | `chatlog_lines` | _int (optional)_ | Chat log lines. | | `color` | _int (optional)_ | Text color. | @@ -2633,6 +2632,7 @@ _No additional response items._ ### GetBrowserSourceProperties +- **⚠️ Deprecated. Since 4.8.0. Prefer the use of GetSourceSettings. Will be removed in v5.0.0 ⚠️** - Added in v4.1.0 @@ -2664,6 +2664,7 @@ Get current properties for a Browser Source. ### SetBrowserSourceProperties +- **⚠️ Deprecated. Since 4.8.0. Prefer the use of SetSourceSettings. Will be removed in v5.0.0 ⚠️** - Added in v4.1.0 @@ -2899,6 +2900,74 @@ Change the visibility/enabled state of a filter _No additional response items._ +--- + +### GetAudioMonitorType + + +- Added in v4.8.0 + +Get the audio monitoring type of the specified source. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name. | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `monitorType` | _String_ | The monitor type in use. Options: `none`, `monitorOnly`, `monitorAndOutput`. | + + +--- + +### SetAudioMonitorType + + +- Added in v4.8.0 + +Set the audio monitoring type of the specified source. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name. | +| `monitorType` | _String_ | The monitor type to use. Options: `none`, `monitorOnly`, `monitorAndOutput`. | + + +**Response Items:** + +_No additional response items._ + +--- + +### GetSourceDefaultSettings + + +- Added in v4.9.0 + +Get the default settings for a given source type. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceKind` | _String_ | Source kind. Also called "source id" in libobs terminology. | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceKind` | _String_ | Source kind. Same value as the `sourceKind` parameter. | +| `defaultSettings` | _Object_ | Settings object for source. | + + --- ### TakeSourceScreenshot @@ -2917,9 +2986,11 @@ preserved if only one of these two parameters is specified. | Name | Type | Description | | ---- | :---: | ------------| -| `sourceName` | _String_ | Source name. Note that, since scenes are also sources, you can also provide a scene name. | +| `sourceName` | _String (optional)_ | Source name. Note: Since scenes are also sources, you can also provide a scene name. If not provided, the currently active scene is used. | | `embedPictureFormat` | _String (optional)_ | Format of the Data URI encoded picture. Can be "png", "jpg", "jpeg" or "bmp" (or any other value supported by Qt's Image module) | | `saveToFilePath` | _String (optional)_ | Full file path (file extension included) where the captured image is to be saved. Can be in a format different from `pictureFormat`. Can be a relative path. | +| `fileFormat` | _String (optional)_ | Format to save the image file as (one of the values provided in the `supported-image-export-formats` response field of `GetVersion`). If not specified, tries to guess based on file extension. | +| `compressionQuality` | _int (optional)_ | Compression ratio between -1 and 100 to write the image with. -1 is automatic, 1 is smallest file/most compression, 100 is largest file/least compression. Varies with image type. | | `width` | _int (optional)_ | Screenshot width. Defaults to the source's base width. | | `height` | _int (optional)_ | Screenshot height. Defaults to the source's base height. | @@ -2933,6 +3004,991 @@ preserved if only one of these two parameters is specified. | `imageFile` | _String_ | Absolute path to the saved image file (if `saveToFilePath` was specified in the request) | +--- + +### RefreshBrowserSource + + +- Added in v4.9.0 + +Refreshes the specified browser source. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sourceName` | _String_ | Source name. | + + +**Response Items:** + +_No additional response items._ + +--- + +## Outputs + +### ListOutputs + + +- Added in v4.7.0 + +List existing outputs + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `outputs` | _Array<Output>_ | Outputs list | + + +--- + +### GetOutputInfo + + +- Added in v4.7.0 + +Get information about a single output + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `outputName` | _String_ | Output name | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `outputInfo` | _Output_ | Output info | + + +--- + +### StartOutput + + +- Added in v4.7.0 + + + +Note: Controlling outputs is an experimental feature of obs-websocket. Some plugins which add outputs to OBS may not function properly when they are controlled in this way. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `outputName` | _String_ | Output name | + + +**Response Items:** + +_No additional response items._ + +--- + +### StopOutput + + +- Added in v4.7.0 + + + +Note: Controlling outputs is an experimental feature of obs-websocket. Some plugins which add outputs to OBS may not function properly when they are controlled in this way. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `outputName` | _String_ | Output name | +| `force` | _boolean (optional)_ | Force stop (default: false) | + + +**Response Items:** + +_No additional response items._ + +--- + +## Profiles + +### SetCurrentProfile + + +- Added in v4.0.0 + +Set the currently active profile. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `profile-name` | _String_ | Name of the desired profile. | + + +**Response Items:** + +_No additional response items._ + +--- + +### GetCurrentProfile + + +- Added in v4.0.0 + +Get the name of the current profile. + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `profile-name` | _String_ | Name of the currently active profile. | + + +--- + +### ListProfiles + + +- Added in v4.0.0 + +Get a list of available profiles. + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `profiles` | _Array<Object>_ | List of available profiles. | +| `profiles.*.profile-name` | _String_ | Filter name | + + +--- + +## Recording + +### GetRecordingStatus + + +- Added in v4.9.0 + +Get current recording status. + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `isRecording` | _boolean_ | Current recording status. | +| `isRecordingPaused` | _boolean_ | Whether the recording is paused or not. | +| `recordTimecode` | _String (optional)_ | Time elapsed since recording started (only present if currently recording). | +| `recordingFilename` | _String (optional)_ | Absolute path to the recording file (only present if currently recording). | + + +--- + +### StartStopRecording + + +- Added in v0.3 + +Toggle recording on or off (depending on the current recording state). + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +_No additional response items._ + +--- + +### StartRecording + + +- Added in v4.1.0 + +Start recording. +Will return an `error` if recording is already active. + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +_No additional response items._ + +--- + +### StopRecording + + +- Added in v4.1.0 + +Stop recording. +Will return an `error` if recording is not active. + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +_No additional response items._ + +--- + +### PauseRecording + + +- Added in v4.7.0 + +Pause the current recording. +Returns an error if recording is not active or already paused. + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +_No additional response items._ + +--- + +### ResumeRecording + + +- Added in v4.7.0 + +Resume/unpause the current recording (if paused). +Returns an error if recording is not active or not paused. + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +_No additional response items._ + +--- + +### SetRecordingFolder + + +- Added in v4.1.0 + + + +Note: If `SetRecordingFolder` is called while a recording is +in progress, the change won't be applied immediately and will be +effective on the next recording. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `rec-folder` | _String_ | Path of the recording folder. | + + +**Response Items:** + +_No additional response items._ + +--- + +### GetRecordingFolder + + +- Added in v4.1.0 + +Get the path of the current recording folder. + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `rec-folder` | _String_ | Path of the recording folder. | + + +--- + +## Replay Buffer + +### GetReplayBufferStatus + + +- Added in v4.9.0 + +Get the status of the OBS replay buffer. + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `isReplayBufferActive` | _boolean_ | Current recording status. | + + +--- + +### StartStopReplayBuffer + + +- Added in v4.2.0 + +Toggle the Replay Buffer on/off (depending on the current state of the replay buffer). + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +_No additional response items._ + +--- + +### StartReplayBuffer + + +- Added in v4.2.0 + +Start recording into the Replay Buffer. +Will return an `error` if the Replay Buffer is already active or if the +"Save Replay Buffer" hotkey is not set in OBS' settings. +Setting this hotkey is mandatory, even when triggering saves only +through obs-websocket. + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +_No additional response items._ + +--- + +### StopReplayBuffer + + +- Added in v4.2.0 + +Stop recording into the Replay Buffer. +Will return an `error` if the Replay Buffer is not active. + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +_No additional response items._ + +--- + +### SaveReplayBuffer + + +- Added in v4.2.0 + +Flush and save the contents of the Replay Buffer to disk. This is +basically the same as triggering the "Save Replay Buffer" hotkey. +Will return an `error` if the Replay Buffer is not active. + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +_No additional response items._ + +--- + +## Scene Collections + +### SetCurrentSceneCollection + + +- Added in v4.0.0 + +Change the active scene collection. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sc-name` | _String_ | Name of the desired scene collection. | + + +**Response Items:** + +_No additional response items._ + +--- + +### GetCurrentSceneCollection + + +- Added in v4.0.0 + +Get the name of the current scene collection. + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sc-name` | _String_ | Name of the currently active scene collection. | + + +--- + +### ListSceneCollections + + +- Added in v4.0.0 + +List available scene collections + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `scene-collections` | _Array<ScenesCollection>_ | Scene collections list | + + +--- + +## Scene Items + +### GetSceneItemList + + +- Added in v4.9.0 + +Get a list of all scene items in a scene. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sceneName` | _String (optional)_ | Name of the scene to get the list of scene items from. Defaults to the current scene if not specified. | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sceneName` | _String_ | Name of the requested (or current) scene | +| `sceneItems` | _Array<Object>_ | Array of scene items | +| `sceneItems.*.itemId` | _int_ | Unique item id of the source item | +| `sceneItems.*.sourceKind` | _String_ | ID if the scene item's source. For example `vlc_source` or `image_source` | +| `sceneItems.*.sourceName` | _String_ | Name of the scene item's source | +| `sceneItems.*.sourceType` | _String_ | Type of the scene item's source. Either `input`, `group`, or `scene` | + + +--- + +### GetSceneItemProperties + + +- Added in v4.3.0 + +Gets the scene specific properties of the specified source item. +Coordinates are relative to the item's parent (the scene or group it belongs to). + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `scene-name` | _String (optional)_ | Name of the scene the scene item belongs to. Defaults to the current scene. | +| `item` | _String \| Object_ | Scene Item name (if this field is a string) or specification (if it is an object). | +| `item.name` | _String (optional)_ | Scene Item name (if the `item` field is an object) | +| `item.id` | _int (optional)_ | Scene Item ID (if the `item` field is an object) | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `name` | _String_ | Scene Item name. | +| `itemId` | _int_ | Scene Item ID. | +| `position.x` | _double_ | The x position of the source from the left. | +| `position.y` | _double_ | The y position of the source from the top. | +| `position.alignment` | _int_ | The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis. | +| `rotation` | _double_ | The clockwise rotation of the item in degrees around the point of alignment. | +| `scale.x` | _double_ | The x-scale factor of the source. | +| `scale.y` | _double_ | The y-scale factor of the source. | +| `scale.filter` | _String_ | The scale filter of the source. Can be "OBS_SCALE_DISABLE", "OBS_SCALE_POINT", "OBS_SCALE_BICUBIC", "OBS_SCALE_BILINEAR", "OBS_SCALE_LANCZOS" or "OBS_SCALE_AREA". | +| `crop.top` | _int_ | The number of pixels cropped off the top of the source before scaling. | +| `crop.right` | _int_ | The number of pixels cropped off the right of the source before scaling. | +| `crop.bottom` | _int_ | The number of pixels cropped off the bottom of the source before scaling. | +| `crop.left` | _int_ | The number of pixels cropped off the left of the source before scaling. | +| `visible` | _bool_ | If the source is visible. | +| `muted` | _bool_ | If the source is muted. | +| `locked` | _bool_ | If the source's transform is locked. | +| `bounds.type` | _String_ | Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE". | +| `bounds.alignment` | _int_ | Alignment of the bounding box. | +| `bounds.x` | _double_ | Width of the bounding box. | +| `bounds.y` | _double_ | Height of the bounding box. | +| `sourceWidth` | _int_ | Base width (without scaling) of the source | +| `sourceHeight` | _int_ | Base source (without scaling) of the source | +| `width` | _double_ | Scene item width (base source width multiplied by the horizontal scaling factor) | +| `height` | _double_ | Scene item height (base source height multiplied by the vertical scaling factor) | +| `parentGroupName` | _String (optional)_ | Name of the item's parent (if this item belongs to a group) | +| `groupChildren` | _Array<SceneItemTransform> (optional)_ | List of children (if this item is a group) | + + +--- + +### SetSceneItemProperties + + +- Added in v4.3.0 + +Sets the scene specific properties of a source. Unspecified properties will remain unchanged. +Coordinates are relative to the item's parent (the scene or group it belongs to). + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `scene-name` | _String (optional)_ | Name of the scene the source item belongs to. Defaults to the current scene. | +| `item` | _String \| Object_ | Scene Item name (if this field is a string) or specification (if it is an object). | +| `item.name` | _String (optional)_ | Scene Item name (if the `item` field is an object) | +| `item.id` | _int (optional)_ | Scene Item ID (if the `item` field is an object) | +| `position.x` | _double (optional)_ | The new x position of the source. | +| `position.y` | _double (optional)_ | The new y position of the source. | +| `position.alignment` | _int (optional)_ | The new alignment of the source. | +| `rotation` | _double (optional)_ | The new clockwise rotation of the item in degrees. | +| `scale.x` | _double (optional)_ | The new x scale of the item. | +| `scale.y` | _double (optional)_ | The new y scale of the item. | +| `scale.filter` | _String (optional)_ | The new scale filter of the source. Can be "OBS_SCALE_DISABLE", "OBS_SCALE_POINT", "OBS_SCALE_BICUBIC", "OBS_SCALE_BILINEAR", "OBS_SCALE_LANCZOS" or "OBS_SCALE_AREA". | +| `crop.top` | _int (optional)_ | The new amount of pixels cropped off the top of the source before scaling. | +| `crop.bottom` | _int (optional)_ | The new amount of pixels cropped off the bottom of the source before scaling. | +| `crop.left` | _int (optional)_ | The new amount of pixels cropped off the left of the source before scaling. | +| `crop.right` | _int (optional)_ | The new amount of pixels cropped off the right of the source before scaling. | +| `visible` | _bool (optional)_ | The new visibility of the source. 'true' shows source, 'false' hides source. | +| `locked` | _bool (optional)_ | The new locked status of the source. 'true' keeps it in its current position, 'false' allows movement. | +| `bounds.type` | _String (optional)_ | The new bounds type of the source. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE". | +| `bounds.alignment` | _int (optional)_ | The new alignment of the bounding box. (0-2, 4-6, 8-10) | +| `bounds.x` | _double (optional)_ | The new width of the bounding box. | +| `bounds.y` | _double (optional)_ | The new height of the bounding box. | + + +**Response Items:** + +_No additional response items._ + +--- + +### ResetSceneItem + + +- Added in v4.2.0 + +Reset a scene item. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `scene-name` | _String (optional)_ | Name of the scene the scene item belongs to. Defaults to the current scene. | +| `item` | _String \| Object_ | Scene Item name (if this field is a string) or specification (if it is an object). | +| `item.name` | _String (optional)_ | Scene Item name (if the `item` field is an object) | +| `item.id` | _int (optional)_ | Scene Item ID (if the `item` field is an object) | + + +**Response Items:** + +_No additional response items._ + +--- + +### SetSceneItemRender + + +- Added in v0.3 + +Show or hide a specified source item in a specified scene. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `scene-name` | _String (optional)_ | Name of the scene the scene item belongs to. Defaults to the currently active scene. | +| `source` | _String (optional)_ | Scene Item name. | +| `item` | _int (optional)_ | Scene Item id | +| `render` | _boolean_ | true = shown ; false = hidden | + + +**Response Items:** + +_No additional response items._ + +--- + +### SetSceneItemPosition + +- **⚠️ Deprecated. Since 4.3.0. Prefer the use of SetSceneItemProperties. ⚠️** + +- Added in v4.0.0 + +Sets the coordinates of a specified source item. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `scene-name` | _String (optional)_ | Name of the scene the scene item belongs to. Defaults to the current scene. | +| `item` | _String_ | Scene Item name. | +| `x` | _double_ | X coordinate. | +| `y` | _double_ | Y coordinate. | + + +**Response Items:** + +_No additional response items._ + +--- + +### SetSceneItemTransform + +- **⚠️ Deprecated. Since 4.3.0. Prefer the use of SetSceneItemProperties. ⚠️** + +- Added in v4.0.0 + +Set the transform of the specified source item. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `scene-name` | _String (optional)_ | Name of the scene the scene item belongs to. Defaults to the current scene. | +| `item` | _String_ | Scene Item name. | +| `x-scale` | _double_ | Width scale factor. | +| `y-scale` | _double_ | Height scale factor. | +| `rotation` | _double_ | Source item rotation (in degrees). | + + +**Response Items:** + +_No additional response items._ + +--- + +### SetSceneItemCrop + +- **⚠️ Deprecated. Since 4.3.0. Prefer the use of SetSceneItemProperties. ⚠️** + +- Added in v4.1.0 + +Sets the crop coordinates of the specified source item. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `scene-name` | _String (optional)_ | Name of the scene the scene item belongs to. Defaults to the current scene. | +| `item` | _String_ | Scene Item name. | +| `top` | _int_ | Pixel position of the top of the source item. | +| `bottom` | _int_ | Pixel position of the bottom of the source item. | +| `left` | _int_ | Pixel position of the left of the source item. | +| `right` | _int_ | Pixel position of the right of the source item. | + + +**Response Items:** + +_No additional response items._ + +--- + +### DeleteSceneItem + + +- Added in v4.5.0 + +Deletes a scene item. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `scene` | _String (optional)_ | Name of the scene the scene item belongs to. Defaults to the current scene. | +| `item` | _Object_ | Scene item to delete (required) | +| `item.name` | _String_ | Scene Item name (prefer `id`, including both is acceptable). | +| `item.id` | _int_ | Scene Item ID. | + + +**Response Items:** + +_No additional response items._ + +--- + +### AddSceneItem + + +- Added in v4.9.0 + +Creates a scene item in a scene. In other words, this is how you add a source into a scene. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sceneName` | _String_ | Name of the scene to create the scene item in | +| `sourceName` | _String_ | Name of the source to be added | +| `setVisible` | _boolean (optional)_ | Whether to make the sceneitem visible on creation or not. Default `true` | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `itemId` | _int_ | Numerical ID of the created scene item | + + +--- + +### DuplicateSceneItem + + +- Added in v4.5.0 + +Duplicates a scene item. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `fromScene` | _String (optional)_ | Name of the scene to copy the item from. Defaults to the current scene. | +| `toScene` | _String (optional)_ | Name of the scene to create the item in. Defaults to the current scene. | +| `item` | _Object_ | Scene Item to duplicate from the source scene (required) | +| `item.name` | _String_ | Scene Item name (prefer `id`, including both is acceptable). | +| `item.id` | _int_ | Scene Item ID. | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `scene` | _String_ | Name of the scene where the new item was created | +| `item` | _Object_ | New item info | +| `item.id` | _int_ | New item ID | +| `item.name` | _String_ | New item name | + + +--- + +## Scenes + +### SetCurrentScene + + +- Added in v0.3 + +Switch to the specified scene. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `scene-name` | _String_ | Name of the scene to switch to. | + + +**Response Items:** + +_No additional response items._ + +--- + +### GetCurrentScene + + +- Added in v0.3 + +Get the current scene's name and source items. + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `name` | _String_ | Name of the currently active scene. | +| `sources` | _Array<SceneItem>_ | Ordered list of the current scene's source items. | + + +--- + +### GetSceneList + + +- Added in v0.3 + +Get a list of scenes in the currently active profile. + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `current-scene` | _String_ | Name of the currently active scene. | +| `scenes` | _Array<Scene>_ | Ordered list of the current profile's scenes (See [GetCurrentScene](#getcurrentscene) for more information). | + + +--- + +### CreateScene + + +- Added in v4.9.0 + +Create a new scene scene. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sceneName` | _String_ | Name of the scene to create. | + + +**Response Items:** + +_No additional response items._ + +--- + +### ReorderSceneItems + + +- Added in v4.5.0 + +Changes the order of scene items in the requested scene. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `scene` | _String (optional)_ | Name of the scene to reorder (defaults to current). | +| `items` | _Array<Scene>_ | Ordered list of objects with name and/or id specified. Id preferred due to uniqueness per scene | +| `items.*.id` | _int (optional)_ | Id of a specific scene item. Unique on a scene by scene basis. | +| `items.*.name` | _String (optional)_ | Name of a scene item. Sufficiently unique if no scene items share sources within the scene. | + + +**Response Items:** + +_No additional response items._ + +--- + +### SetSceneTransitionOverride + + +- Added in v4.8.0 + +Set a scene to use a specific transition override. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sceneName` | _String_ | Name of the scene to switch to. | +| `transitionName` | _String_ | Name of the transition to use. | +| `transitionDuration` | _int (Optional)_ | Duration in milliseconds of the transition if transition is not fixed. Defaults to the current duration specified in the UI if there is no current override and this value is not given. | + + +**Response Items:** + +_No additional response items._ + +--- + +### RemoveSceneTransitionOverride + + +- Added in v4.8.0 + +Remove any transition override on a scene. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sceneName` | _String_ | Name of the scene to switch to. | + + +**Response Items:** + +_No additional response items._ + +--- + +### GetSceneTransitionOverride + + +- Added in v4.8.0 + +Get the current scene transition override. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `sceneName` | _String_ | Name of the scene to switch to. | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `transitionName` | _String_ | Name of the current overriding transition. Empty string if no override is set. | +| `transitionDuration` | _int_ | Transition duration. `-1` if no override is set. | + + --- ## Streaming @@ -2954,9 +4010,10 @@ _No specified parameters._ | ---- | :---: | ------------| | `streaming` | _boolean_ | Current streaming status. | | `recording` | _boolean_ | Current recording status. | +| `recording-paused` | _boolean_ | If recording is paused. | +| `preview-only` | _boolean_ | Always false. Retrocompatibility with OBSRemote. | | `stream-timecode` | _String (optional)_ | Time elapsed since streaming started (only present if currently streaming). | | `rec-timecode` | _String (optional)_ | Time elapsed since recording started (only present if currently recording). | -| `preview-only` | _boolean_ | Always false. Retrocompatibility with OBSRemote. | --- @@ -2966,7 +4023,7 @@ _No specified parameters._ - Added in v0.3 -Toggle streaming on or off. +Toggle streaming on or off (depending on the current stream state). **Request Fields:** @@ -2990,7 +4047,7 @@ Will return an `error` if streaming is already active. | Name | Type | Description | | ---- | :---: | ------------| -| `stream` | _Object (optional)_ | Special stream configuration. Please note: these won't be saved to OBS' configuration. | +| `stream` | _Object (optional)_ | Special stream configuration. Note: these won't be saved to OBS' configuration. | | `stream.type` | _String (optional)_ | If specified ensures the type of stream matches the given type (usually 'rtmp_custom' or 'rtmp_common'). If the currently configured stream type does not match the given stream type, all settings must be specified in the `settings` object or an error will occur when starting the stream. | | `stream.metadata` | _Object (optional)_ | Adds the given object parameters as encoded query string parameters to the 'key' of the RTMP stream. Used to pass data to the RTMP service about the streaming. May be any String, Numeric, or Boolean field. | | `stream.settings` | _Object (optional)_ | Settings for the stream. | @@ -3101,7 +4158,6 @@ _No additional response items._ - Added in v4.6.0 Send the provided text as embedded CEA-608 caption data. -As of OBS Studio 23.1, captions are not yet available on Linux. **Request Fields:** @@ -3243,7 +4299,7 @@ _No additional response items._ - Added in v4.1.0 -Toggles Studio Mode. +Toggles Studio Mode (depending on the current state of studio mode). **Request Fields:** @@ -3360,3 +4416,111 @@ _No specified parameters._ --- +### GetTransitionPosition + + +- Added in v4.9.0 + +Get the position of the current transition. + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `position` | _double_ | current transition position. This value will be between 0.0 and 1.0. Note: Transition returns 1.0 when not active. | + + +--- + +### GetTransitionSettings + + +- Added in v4.9.0 + +Get the current settings of a transition + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `transitionName` | _String_ | Transition name | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `transitionSettings` | _Object_ | Current transition settings | + + +--- + +### SetTransitionSettings + + +- Added in v4.9.0 + +Change the current settings of a transition + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `transitionName` | _String_ | Transition name | +| `transitionSettings` | _Object_ | Transition settings (they can be partial) | + + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `transitionSettings` | _Object_ | Updated transition settings | + + +--- + +### ReleaseTBar + + +- Added in v4.9.0 + +Release the T-Bar (like a user releasing their mouse button after moving it). +*YOU MUST CALL THIS if you called `SetTBarPosition` with the `release` parameter set to `false`.* + +**Request Fields:** + +_No specified parameters._ + +**Response Items:** + +_No additional response items._ + +--- + +### SetTBarPosition + + +- Added in v4.9.0 + + + +If your code needs to perform multiple successive T-Bar moves (e.g. : in an animation, or in response to a user moving a T-Bar control in your User Interface), set `release` to false and call `ReleaseTBar` later once the animation/interaction is over. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `position` | _double_ | T-Bar position. This value must be between 0.0 and 1.0. | +| `release` | _boolean (optional)_ | Whether or not the T-Bar gets released automatically after setting its new position (like a user releasing their mouse button after moving the T-Bar). Call `ReleaseTBar` manually if you set `release` to false. Defaults to true. | + + +**Response Items:** + +_No additional response items._ + +--- + diff --git a/docs/partials/introduction.md b/docs/partials/introduction.md index c8de4503..9fbd30d4 100644 --- a/docs/partials/introduction.md +++ b/docs/partials/introduction.md @@ -1,10 +1,12 @@ -# obs-websocket 4.7.0 protocol reference +# obs-websocket 4.9.0 protocol reference # General Introduction Messages are exchanged between the client and the server as JSON objects. -This protocol is based on the original OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio. +This protocol is based on the original OBS Remote protocol created by Bill Hamilton, with new commands specific to OBS Studio. As of v5.0.0, backwards compatability with the protocol will not be kept. # Authentication +**Starting with obs-websocket 4.9, authentication is enabled by default and users are encouraged to configure a password on first run.** + `obs-websocket` uses SHA256 to transmit credentials. A request for [`GetAuthRequired`](#getauthrequired) returns two elements: @@ -32,3 +34,5 @@ auth_response_string = secret + challenge auth_response_hash = binary_sha256(auth_response_string) auth_response = base64_encode(auth_response_hash) ``` + +You can also refer to any of the client libraries listed on the [README](README.md) for examples of how to authenticate. diff --git a/docs/partials/requestsHeader.md b/docs/partials/requestsHeader.md index 457ad61b..b73efc0c 100644 --- a/docs/partials/requestsHeader.md +++ b/docs/partials/requestsHeader.md @@ -6,6 +6,6 @@ Requests are sent by the client and require at least the following two fields: Once a request is sent, the server will return a JSON response with at least the following fields: - `message-id` _String_: The client defined identifier specified in the request. - `status` _String_: Response status, will be one of the following: `ok`, `error` -- `error` _String_: An error message accompanying an `error` status. +- `error` _String (Optional)_: An error message accompanying an `error` status. Additional information may be required/returned depending on the request type. See below for more information. diff --git a/installer/installer.iss b/installer/installer.iss index b605b4a6..0109c12d 100644 --- a/installer/installer.iss +++ b/installer/installer.iss @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "obs-websocket" -#define MyAppVersion "4.7.0" +#define MyAppVersion "4.9.0" #define MyAppPublisher "Stephane Lepin" #define MyAppURL "http://github.com/Palakis/obs-websocket" @@ -23,6 +23,7 @@ DefaultGroupName={#MyAppName} OutputBaseFilename=obs-websocket-Windows-Installer Compression=lzma SolidCompression=yes +DirExistsWarning=no [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" diff --git a/src/Config.cpp b/src/Config.cpp index 4ed30608..f3c615e6 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -18,19 +18,26 @@ with this program. If not, see #include +#include #include #include #include +#include +#include +#include #define SECTION_NAME "WebsocketAPI" #define PARAM_ENABLE "ServerEnabled" #define PARAM_PORT "ServerPort" +#define PARAM_LOCKTOIPV4 "LockToIPv4" #define PARAM_DEBUG "DebugEnabled" #define PARAM_ALERT "AlertsEnabled" #define PARAM_AUTHREQUIRED "AuthRequired" #define PARAM_SECRET "AuthSecret" #define PARAM_SALT "AuthSalt" +#define GLOBAL_AUTH_SETUP_PROMPTED "AuthSetupPrompted" + #include "Utils.h" #include "WSServer.h" @@ -41,9 +48,10 @@ with this program. If not, see Config::Config() : ServerEnabled(true), ServerPort(4444), + LockToIPv4(false), DebugEnabled(false), AlertsEnabled(true), - AuthRequired(false), + AuthRequired(true), Secret(""), Salt(""), SettingsLoaded(false) @@ -67,6 +75,7 @@ void Config::Load() ServerEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ENABLE); ServerPort = config_get_uint(obsConfig, SECTION_NAME, PARAM_PORT); + LockToIPv4 = config_get_bool(obsConfig, SECTION_NAME, PARAM_LOCKTOIPV4); DebugEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_DEBUG); AlertsEnabled = config_get_bool(obsConfig, SECTION_NAME, PARAM_ALERT); @@ -82,6 +91,7 @@ void Config::Save() config_set_bool(obsConfig, SECTION_NAME, PARAM_ENABLE, ServerEnabled); config_set_uint(obsConfig, SECTION_NAME, PARAM_PORT, ServerPort); + config_set_bool(obsConfig, SECTION_NAME, PARAM_LOCKTOIPV4, LockToIPv4); config_set_bool(obsConfig, SECTION_NAME, PARAM_DEBUG, DebugEnabled); config_set_bool(obsConfig, SECTION_NAME, PARAM_ALERT, AlertsEnabled); @@ -104,6 +114,8 @@ void Config::SetDefaults() SECTION_NAME, PARAM_ENABLE, ServerEnabled); config_set_default_uint(obsConfig, SECTION_NAME, PARAM_PORT, ServerPort); + config_set_default_bool(obsConfig, + SECTION_NAME, PARAM_LOCKTOIPV4, LockToIPv4); config_set_default_bool(obsConfig, SECTION_NAME, PARAM_DEBUG, DebugEnabled); @@ -124,6 +136,70 @@ config_t* Config::GetConfigStore() return obs_frontend_get_profile_config(); } +void Config::MigrateFromGlobalSettings() +{ + config_t* source = obs_frontend_get_global_config(); + config_t* destination = obs_frontend_get_profile_config(); + + if(config_has_user_value(source, SECTION_NAME, PARAM_ENABLE)) { + bool value = config_get_bool(source, SECTION_NAME, PARAM_ENABLE); + config_set_bool(destination, SECTION_NAME, PARAM_ENABLE, value); + + config_remove_value(source, SECTION_NAME, PARAM_ENABLE); + } + + if(config_has_user_value(source, SECTION_NAME, PARAM_PORT)) { + uint64_t value = config_get_uint(source, SECTION_NAME, PARAM_PORT); + config_set_uint(destination, SECTION_NAME, PARAM_PORT, value); + + config_remove_value(source, SECTION_NAME, PARAM_PORT); + } + + if(config_has_user_value(source, SECTION_NAME, PARAM_LOCKTOIPV4)) { + bool value = config_get_bool(source, SECTION_NAME, PARAM_LOCKTOIPV4); + config_set_bool(destination, SECTION_NAME, PARAM_LOCKTOIPV4, value); + + config_remove_value(source, SECTION_NAME, PARAM_LOCKTOIPV4); + } + + if(config_has_user_value(source, SECTION_NAME, PARAM_DEBUG)) { + bool value = config_get_bool(source, SECTION_NAME, PARAM_DEBUG); + config_set_bool(destination, SECTION_NAME, PARAM_DEBUG, value); + + config_remove_value(source, SECTION_NAME, PARAM_DEBUG); + } + + if(config_has_user_value(source, SECTION_NAME, PARAM_ALERT)) { + bool value = config_get_bool(source, SECTION_NAME, PARAM_ALERT); + config_set_bool(destination, SECTION_NAME, PARAM_ALERT, value); + + config_remove_value(source, SECTION_NAME, PARAM_ALERT); + } + + if(config_has_user_value(source, SECTION_NAME, PARAM_AUTHREQUIRED)) { + bool value = config_get_bool(source, SECTION_NAME, PARAM_AUTHREQUIRED); + config_set_bool(destination, SECTION_NAME, PARAM_AUTHREQUIRED, value); + + config_remove_value(source, SECTION_NAME, PARAM_AUTHREQUIRED); + } + + if(config_has_user_value(source, SECTION_NAME, PARAM_SECRET)) { + const char* value = config_get_string(source, SECTION_NAME, PARAM_SECRET); + config_set_string(destination, SECTION_NAME, PARAM_SECRET, value); + + config_remove_value(source, SECTION_NAME, PARAM_SECRET); + } + + if(config_has_user_value(source, SECTION_NAME, PARAM_SALT)) { + const char* value = config_get_string(source, SECTION_NAME, PARAM_SALT); + config_set_string(destination, SECTION_NAME, PARAM_SALT, value); + + config_remove_value(source, SECTION_NAME, PARAM_SALT); + } + + config_save(destination); +} + QString Config::GenerateSalt() { // Generate 32 random chars @@ -205,16 +281,17 @@ void Config::OnFrontendEvent(enum obs_frontend_event event, void* param) bool previousEnabled = config->ServerEnabled; uint64_t previousPort = config->ServerPort; + bool previousLock = config->LockToIPv4; config->SetDefaults(); config->Load(); - if (config->ServerEnabled != previousEnabled || config->ServerPort != previousPort) { + if (config->ServerEnabled != previousEnabled || config->ServerPort != previousPort || config->LockToIPv4 != previousLock) { auto server = GetServer(); server->stop(); if (config->ServerEnabled) { - server->start(config->ServerPort); + server->start(config->ServerPort, config->LockToIPv4); if (previousEnabled != config->ServerEnabled) { Utils::SysTrayNotify(startMessage, QSystemTrayIcon::MessageIcon::Information); @@ -226,61 +303,50 @@ void Config::OnFrontendEvent(enum obs_frontend_event event, void* param) } } } + else if (event == OBS_FRONTEND_EVENT_FINISHED_LOADING) { + FirstRunPasswordSetup(); + } } -void Config::MigrateFromGlobalSettings() +void Config::FirstRunPasswordSetup() { - config_t* source = obs_frontend_get_global_config(); - config_t* destination = obs_frontend_get_profile_config(); - - if(config_has_user_value(source, SECTION_NAME, PARAM_ENABLE)) { - bool value = config_get_bool(source, SECTION_NAME, PARAM_ENABLE); - config_set_bool(destination, SECTION_NAME, PARAM_ENABLE, value); - - config_remove_value(source, SECTION_NAME, PARAM_ENABLE); + // check if we already showed the auth setup prompt to the user, independently of the current settings (tied to the current profile) + config_t* globalConfig = obs_frontend_get_global_config(); + bool alreadyPrompted = config_get_bool(globalConfig, SECTION_NAME, GLOBAL_AUTH_SETUP_PROMPTED); + if (alreadyPrompted) { + return; } - if(config_has_user_value(source, SECTION_NAME, PARAM_PORT)) { - uint64_t value = config_get_uint(source, SECTION_NAME, PARAM_PORT); - config_set_uint(destination, SECTION_NAME, PARAM_PORT, value); + // lift the flag up and save it + config_set_bool(globalConfig, SECTION_NAME, GLOBAL_AUTH_SETUP_PROMPTED, true); + config_save(globalConfig); - config_remove_value(source, SECTION_NAME, PARAM_PORT); + // check if the password is already set + auto config = GetConfig(); + if (!config) { + return; } - if(config_has_user_value(source, SECTION_NAME, PARAM_DEBUG)) { - bool value = config_get_bool(source, SECTION_NAME, PARAM_DEBUG); - config_set_bool(destination, SECTION_NAME, PARAM_DEBUG, value); - - config_remove_value(source, SECTION_NAME, PARAM_DEBUG); + if (!(config->Secret.isEmpty()) && !(config->Salt.isEmpty())) { + return; } - if(config_has_user_value(source, SECTION_NAME, PARAM_ALERT)) { - bool value = config_get_bool(source, SECTION_NAME, PARAM_ALERT); - config_set_bool(destination, SECTION_NAME, PARAM_ALERT, value); + obs_frontend_push_ui_translation(obs_module_get_string); + QString dialogTitle = QObject::tr("OBSWebsocket.InitialPasswordSetup.Title"); + QString dialogText = QObject::tr("OBSWebsocket.InitialPasswordSetup.Text"); + QString dismissedText = QObject::tr("OBSWebsocket.InitialPasswordSetup.DismissedText"); + obs_frontend_pop_ui_translation(); - config_remove_value(source, SECTION_NAME, PARAM_ALERT); + auto mainWindow = reinterpret_cast( + obs_frontend_get_main_window() + ); + + QMessageBox::StandardButton response = QMessageBox::question(mainWindow, dialogTitle, dialogText); + if (response == QMessageBox::Yes) { + ShowPasswordSetting(); } - - if(config_has_user_value(source, SECTION_NAME, PARAM_AUTHREQUIRED)) { - bool value = config_get_bool(source, SECTION_NAME, PARAM_AUTHREQUIRED); - config_set_bool(destination, SECTION_NAME, PARAM_AUTHREQUIRED, value); - - config_remove_value(source, SECTION_NAME, PARAM_AUTHREQUIRED); + else { + // tell the user they still can set the password later in our settings dialog + QMessageBox::information(mainWindow, dialogTitle, dismissedText); } - - if(config_has_user_value(source, SECTION_NAME, PARAM_SECRET)) { - const char* value = config_get_string(source, SECTION_NAME, PARAM_SECRET); - config_set_string(destination, SECTION_NAME, PARAM_SECRET, value); - - config_remove_value(source, SECTION_NAME, PARAM_SECRET); - } - - if(config_has_user_value(source, SECTION_NAME, PARAM_SALT)) { - const char* value = config_get_string(source, SECTION_NAME, PARAM_SALT); - config_set_string(destination, SECTION_NAME, PARAM_SALT, value); - - config_remove_value(source, SECTION_NAME, PARAM_SALT); - } - - config_save(destination); } diff --git a/src/Config.h b/src/Config.h index 0845f874..1aa29ebf 100644 --- a/src/Config.h +++ b/src/Config.h @@ -42,6 +42,7 @@ class Config { bool ServerEnabled; uint64_t ServerPort; + bool LockToIPv4; bool DebugEnabled; bool AlertsEnabled; @@ -54,4 +55,5 @@ class Config { private: static void OnFrontendEvent(enum obs_frontend_event event, void* param); + static void FirstRunPasswordSetup(); }; diff --git a/src/ConnectionProperties.cpp b/src/ConnectionProperties.cpp index 485123de..aa7c4525 100644 --- a/src/ConnectionProperties.cpp +++ b/src/ConnectionProperties.cpp @@ -19,16 +19,16 @@ with this program. If not, see #include "ConnectionProperties.h" ConnectionProperties::ConnectionProperties() - : _authenticated(false) + : _authenticated(false) { } bool ConnectionProperties::isAuthenticated() { - return _authenticated.load(); + return _authenticated.load(); } void ConnectionProperties::setAuthenticated(bool authenticated) { - _authenticated.store(authenticated); + _authenticated.store(authenticated); } \ No newline at end of file diff --git a/src/ConnectionProperties.h b/src/ConnectionProperties.h index a766ef6a..8bb87a27 100644 --- a/src/ConnectionProperties.h +++ b/src/ConnectionProperties.h @@ -23,9 +23,9 @@ with this program. If not, see class ConnectionProperties { public: - explicit ConnectionProperties(); - bool isAuthenticated(); - void setAuthenticated(bool authenticated); + explicit ConnectionProperties(); + bool isAuthenticated(); + void setAuthenticated(bool authenticated); private: - std::atomic _authenticated; + std::atomic _authenticated; }; \ No newline at end of file diff --git a/src/Utils.cpp b/src/Utils.cpp index 978162be..3a6266b7 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -24,6 +24,7 @@ with this program. If not, see #include #include #include +#include #include "obs-websocket.h" @@ -51,6 +52,43 @@ obs_bounds_type getBoundsTypeFromName(QString name) { return boundTypeNames.key(name); } +const QHash scaleTypeNames = { + { OBS_SCALE_DISABLE, "OBS_SCALE_DISABLE" }, + { OBS_SCALE_POINT, "OBS_SCALE_POINT" }, + { OBS_SCALE_BICUBIC, "OBS_SCALE_BICUBIC" }, + { OBS_SCALE_BILINEAR, "OBS_SCALE_BILINEAR" }, + { OBS_SCALE_LANCZOS, "OBS_SCALE_LANCZOS" }, + { OBS_SCALE_AREA, "OBS_SCALE_AREA" }, +}; + +QString getScaleNameFromType(obs_scale_type type) { + QString fallback = scaleTypeNames.value(OBS_SCALE_DISABLE); + return scaleTypeNames.value(type, fallback); +} + +obs_scale_type getFilterTypeFromName(QString name) { + return scaleTypeNames.key(name); +} + +bool Utils::StringInStringList(char** strings, const char* string) { + if (!strings) { + return false; + } + + size_t index = 0; + while (strings[index] != NULL) { + char* value = strings[index]; + + if (strcmp(value, string) == 0) { + return true; + } + + index++; + } + + return false; +} + obs_data_array_t* Utils::StringListToArray(char** strings, const char* key) { obs_data_array_t* list = obs_data_array_create(); @@ -180,34 +218,6 @@ obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) { return data; } -obs_sceneitem_t* Utils::GetSceneItemFromItem(obs_scene_t* scene, obs_data_t* itemInfo) { - if (!scene) { - return nullptr; - } - - OBSDataItemAutoRelease idInfoItem = obs_data_item_byname(itemInfo, "id"); - int id = obs_data_item_get_int(idInfoItem); - - OBSDataItemAutoRelease nameInfoItem = obs_data_item_byname(itemInfo, "name"); - const char* name = obs_data_item_get_string(nameInfoItem); - - if (idInfoItem) { - obs_sceneitem_t* sceneItem = GetSceneItemFromId(scene, id); - obs_source_t* sceneItemSource = obs_sceneitem_get_source(sceneItem); - - QString sceneItemName = obs_source_get_name(sceneItemSource); - if (nameInfoItem && (QString(name) != sceneItemName)) { - return nullptr; - } - - return sceneItem; - } else if (nameInfoItem) { - return GetSceneItemFromName(scene, name); - } - - return nullptr; -} - obs_sceneitem_t* Utils::GetSceneItemFromName(obs_scene_t* scene, QString name) { if (!scene) { return nullptr; @@ -297,6 +307,49 @@ obs_sceneitem_t* Utils::GetSceneItemFromId(obs_scene_t* scene, int64_t id) { return search.result; } +obs_sceneitem_t* Utils::GetSceneItemFromItem(obs_scene_t* scene, obs_data_t* itemInfo) { + if (!scene) { + return nullptr; + } + + OBSDataItemAutoRelease idInfoItem = obs_data_item_byname(itemInfo, "id"); + int id = obs_data_item_get_int(idInfoItem); + + OBSDataItemAutoRelease nameInfoItem = obs_data_item_byname(itemInfo, "name"); + const char* name = obs_data_item_get_string(nameInfoItem); + + if (idInfoItem) { + obs_sceneitem_t* sceneItem = GetSceneItemFromId(scene, id); + obs_source_t* sceneItemSource = obs_sceneitem_get_source(sceneItem); + + QString sceneItemName = obs_source_get_name(sceneItemSource); + if (nameInfoItem && (QString(name) != sceneItemName)) { + return nullptr; + } + + return sceneItem; + } else if (nameInfoItem) { + return GetSceneItemFromName(scene, name); + } + + return nullptr; +} + +obs_sceneitem_t* Utils::GetSceneItemFromRequestField(obs_scene_t* scene, obs_data_item_t* dataItem) +{ + enum obs_data_type dataType = obs_data_item_gettype(dataItem); + + if (dataType == OBS_DATA_OBJECT) { + OBSDataAutoRelease itemData = obs_data_item_get_obj(dataItem); + return GetSceneItemFromItem(scene, itemData); + } else if (dataType == OBS_DATA_STRING) { + QString name = obs_data_item_get_string(dataItem); + return GetSceneItemFromName(scene, name); + } + + return nullptr; +} + bool Utils::IsValidAlignment(const uint32_t alignment) { switch (alignment) { case OBS_ALIGN_CENTER: @@ -470,16 +523,13 @@ QString Utils::OBSVersionString() { } QSystemTrayIcon* Utils::GetTrayIcon() { - QMainWindow* main = (QMainWindow*)obs_frontend_get_main_window(); - if (!main) return nullptr; - - QList trays = main->findChildren(); - return trays.isEmpty() ? nullptr : trays.first(); + void* systemTray = obs_frontend_get_system_tray(); + return reinterpret_cast(systemTray); } -void Utils::SysTrayNotify(QString text, - QSystemTrayIcon::MessageIcon icon, QString title) { - if (!GetConfig()->AlertsEnabled || +void Utils::SysTrayNotify(QString text, QSystemTrayIcon::MessageIcon icon, QString title) { + auto config = GetConfig(); + if ((config && !config->AlertsEnabled) || !QSystemTrayIcon::isSystemTrayAvailable() || !QSystemTrayIcon::supportsMessages()) { @@ -665,16 +715,40 @@ bool Utils::SetFilenameFormatting(const char* filenameFormatting) { return true; } +const char* Utils::GetCurrentRecordingFilename() +{ + OBSOutputAutoRelease recordingOutput = obs_frontend_get_recording_output(); + if (!recordingOutput) { + return nullptr; + } + + OBSDataAutoRelease settings = obs_output_get_settings(recordingOutput); + + // mimicks the behavior of BasicOutputHandler::GetRecordingFilename : + // try to fetch the path from the "url" property, then try "path" if the first one + // didn't yield any result + OBSDataItemAutoRelease item = obs_data_item_byname(settings, "url"); + if (!item) { + item = obs_data_item_byname(settings, "path"); + if (!item) { + return nullptr; + } + } + + return obs_data_item_get_string(item); +} + // Transform properties copy-pasted from WSRequestHandler_SceneItems.cpp because typedefs can't be extended yet /** * @typedef {Object} `SceneItemTransform` - * @property {int} `position.x` The x position of the scene item from the left. - * @property {int} `position.y` The y position of the scene item from the top. + * @property {double} `position.x` The x position of the scene item from the left. + * @property {double} `position.y` The y position of the scene item from the top. * @property {int} `position.alignment` The point on the scene item that the item is manipulated from. * @property {double} `rotation` The clockwise rotation of the scene item in degrees around the point of alignment. * @property {double} `scale.x` The x-scale factor of the scene item. * @property {double} `scale.y` The y-scale factor of the scene item. + * @property {String} `scale.filter` The scale filter of the source. Can be "OBS_SCALE_DISABLE", "OBS_SCALE_POINT", "OBS_SCALE_BICUBIC", "OBS_SCALE_BILINEAR", "OBS_SCALE_LANCZOS" or "OBS_SCALE_AREA". * @property {int} `crop.top` The number of pixels cropped off the top of the scene item before scaling. * @property {int} `crop.right` The number of pixels cropped off the right of the scene item before scaling. * @property {int} `crop.bottom` The number of pixels cropped off the bottom of the scene item before scaling. @@ -718,12 +792,16 @@ obs_data_t* Utils::GetSceneItemPropertiesData(obs_sceneitem_t* sceneItem) { uint32_t boundsAlignment = obs_sceneitem_get_bounds_alignment(sceneItem); QString boundsTypeName = getBoundsNameFromType(boundsType); + obs_scale_type scaleFilter = obs_sceneitem_get_scale_filter(sceneItem); + QString scaleFilterName = getScaleNameFromType(scaleFilter); + OBSDataAutoRelease posData = obs_data_create(); obs_data_set_double(posData, "x", pos.x); obs_data_set_double(posData, "y", pos.y); obs_data_set_int(posData, "alignment", alignment); OBSDataAutoRelease scaleData = obs_data_create(); + obs_data_set_string(scaleData, "filter", scaleFilterName.toUtf8()); obs_data_set_double(scaleData, "x", scale.x); obs_data_set_double(scaleData, "y", scale.y); @@ -844,3 +922,45 @@ QString Utils::nsToTimestamp(uint64_t ns) return QString::asprintf("%02" PRIu64 ":%02" PRIu64 ":%02" PRIu64 ".%03" PRIu64, hoursPart, minutesPart, secsPart, msPart); } + +void Utils::AddSourceHelper(void *_data, obs_scene_t *scene) +{ + auto *data = reinterpret_cast(_data); + data->sceneItem = obs_scene_add(scene, data->source); + obs_sceneitem_set_visible(data->sceneItem, data->setVisible); +} + +obs_data_t *Utils::OBSDataGetDefaults(obs_data_t *data) +{ + obs_data_t *returnData = obs_data_create(); + obs_data_item_t *item = NULL; + + for (item = obs_data_first(data); item; obs_data_item_next(&item)) { + enum obs_data_type type = obs_data_item_gettype(item); + const char *name = obs_data_item_get_name(item); + + if (type == OBS_DATA_STRING) { + const char *val = obs_data_item_get_string(item); + obs_data_set_string(returnData, name, val); + } else if (type == OBS_DATA_NUMBER) { + enum obs_data_number_type type = obs_data_item_numtype(item); + if (type == OBS_DATA_NUM_INT) { + long long val = obs_data_item_get_int(item); + obs_data_set_int(returnData, name, val); + } else { + double val = obs_data_item_get_double(item); + obs_data_set_double(returnData, name, val); + } + } else if (type == OBS_DATA_BOOLEAN) { + bool val = obs_data_item_get_bool(item); + obs_data_set_bool(returnData, name, val); + } else if (type == OBS_DATA_OBJECT) { + OBSDataAutoRelease obj = obs_data_item_get_obj(item); + obs_data_set_obj(returnData, name, obj); + } else if (type == OBS_DATA_ARRAY) { + OBSDataArrayAutoRelease array = obs_data_item_get_array(item); + obs_data_set_array(returnData, name, array); + } + } + return returnData; +} diff --git a/src/Utils.h b/src/Utils.h index 5934ad4e..ba227f15 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -35,15 +35,17 @@ typedef void(*PauseRecordingFunction)(bool); typedef bool(*RecordingPausedFunction)(); namespace Utils { + bool StringInStringList(char** strings, const char* string); obs_data_array_t* StringListToArray(char** strings, const char* key); obs_data_array_t* GetSceneItems(obs_source_t* source); obs_data_t* GetSceneItemData(obs_sceneitem_t* item); - // These two functions support nested lookup into groups + // These functions support nested lookup into groups obs_sceneitem_t* GetSceneItemFromName(obs_scene_t* scene, QString name); obs_sceneitem_t* GetSceneItemFromId(obs_scene_t* scene, int64_t id); - obs_sceneitem_t* GetSceneItemFromItem(obs_scene_t* scene, obs_data_t* item); + obs_sceneitem_t* GetSceneItemFromRequestField(obs_scene_t* scene, obs_data_item_t* dataItem); + obs_scene_t* GetSceneFromNameOrCurrent(QString sceneName); obs_data_t* GetSceneItemPropertiesData(obs_sceneitem_t* item); @@ -83,5 +85,15 @@ namespace Utils { const char* GetFilenameFormatting(); bool SetFilenameFormatting(const char* filenameFormatting); + const char* GetCurrentRecordingFilename(); + QString nsToTimestamp(uint64_t ns); + struct AddSourceData { + obs_source_t *source; + obs_sceneitem_t *sceneItem; + bool setVisible; + }; + void AddSourceHelper(void *_data, obs_scene_t *scene); + + obs_data_t *OBSDataGetDefaults(obs_data_t *data); }; diff --git a/src/WSEvents.cpp b/src/WSEvents.cpp index 6010275f..da1bf052 100644 --- a/src/WSEvents.cpp +++ b/src/WSEvents.cpp @@ -124,7 +124,7 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private case OBS_FRONTEND_EVENT_FINISHED_LOADING: owner->hookTransitionPlaybackEvents(); break; - + case OBS_FRONTEND_EVENT_SCENE_CHANGED: owner->OnSceneChange(); break; @@ -233,6 +233,7 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private case OBS_FRONTEND_EVENT_EXIT: owner->unhookTransitionPlaybackEvents(); owner->OnExit(); + owner->_srv->stop(); break; } } @@ -240,10 +241,17 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private void WSEvents::broadcastUpdate(const char* updateType, obs_data_t* additionalFields = nullptr) { - uint64_t streamTime = getStreamingTime(); - uint64_t recordingTime = getRecordingTime(); - RpcEvent event(QString(updateType), streamTime, recordingTime, additionalFields); + std::optional streamTime; + if (obs_frontend_streaming_active()) { + streamTime = std::make_optional(getStreamingTime()); + } + std::optional recordingTime; + if (obs_frontend_recording_active()) { + recordingTime = std::make_optional(getRecordingTime()); + } + + RpcEvent event(QString(updateType), streamTime, recordingTime, additionalFields); _srv->broadcast(event); } @@ -264,11 +272,22 @@ void WSEvents::connectSourceSignals(obs_source_t* source) { signal_handler_connect(sh, "volume", OnSourceVolumeChange, this); signal_handler_connect(sh, "audio_sync", OnSourceAudioSyncOffsetChanged, this); signal_handler_connect(sh, "audio_mixers", OnSourceAudioMixersChanged, this); + signal_handler_connect(sh, "audio_activate", OnSourceAudioActivated, this); + signal_handler_connect(sh, "audio_deactivate", OnSourceAudioDeactivated, this); signal_handler_connect(sh, "filter_add", OnSourceFilterAdded, this); signal_handler_connect(sh, "filter_remove", OnSourceFilterRemoved, this); signal_handler_connect(sh, "reorder_filters", OnSourceFilterOrderChanged, this); + signal_handler_connect(sh, "media_play", OnMediaPlaying, this); + signal_handler_connect(sh, "media_pause", OnMediaPaused, this); + signal_handler_connect(sh, "media_restart", OnMediaRestarted, this); + signal_handler_connect(sh, "media_stopped", OnMediaStopped, this); + signal_handler_connect(sh, "media_next", OnMediaNext, this); + signal_handler_connect(sh, "media_previous", OnMediaPrevious, this); + signal_handler_connect(sh, "media_started", OnMediaStarted, this); + signal_handler_connect(sh, "media_ended", OnMediaEnded, this); + if (sourceType == OBS_SOURCE_TYPE_SCENE) { signal_handler_connect(sh, "reorder", OnSceneReordered, this); signal_handler_connect(sh, "item_add", OnSceneItemAdd, this); @@ -296,6 +315,8 @@ void WSEvents::disconnectSourceSignals(obs_source_t* source) { signal_handler_disconnect(sh, "volume", OnSourceVolumeChange, this); signal_handler_disconnect(sh, "audio_sync", OnSourceAudioSyncOffsetChanged, this); signal_handler_disconnect(sh, "audio_mixers", OnSourceAudioMixersChanged, this); + signal_handler_disconnect(sh, "audio_activate", OnSourceAudioActivated, this); + signal_handler_disconnect(sh, "audio_deactivate", OnSourceAudioDeactivated, this); signal_handler_disconnect(sh, "filter_add", OnSourceFilterAdded, this); signal_handler_disconnect(sh, "filter_remove", OnSourceFilterRemoved, this); @@ -315,6 +336,15 @@ void WSEvents::disconnectSourceSignals(obs_source_t* source) { signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this); signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this); signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this); + + signal_handler_disconnect(sh, "media_play", OnMediaPlaying, this); + signal_handler_disconnect(sh, "media_pause", OnMediaPaused, this); + signal_handler_disconnect(sh, "media_restart", OnMediaRestarted, this); + signal_handler_disconnect(sh, "media_stopped", OnMediaStopped, this); + signal_handler_disconnect(sh, "media_next", OnMediaNext, this); + signal_handler_disconnect(sh, "media_previous", OnMediaPrevious, this); + signal_handler_disconnect(sh, "media_started", OnMediaStarted, this); + signal_handler_disconnect(sh, "media_ended", OnMediaEnded, this); } void WSEvents::connectFilterSignals(obs_source_t* filter) { @@ -341,7 +371,7 @@ void WSEvents::hookTransitionPlaybackEvents() { obs_frontend_source_list transitions = {}; obs_frontend_get_transitions(&transitions); - for (int i = 0; i < transitions.sources.num; i++) { + for (uint i = 0; i < transitions.sources.num; i++) { obs_source_t* transition = transitions.sources.array[i]; signal_handler_t* sh = obs_source_get_signal_handler(transition); signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this); @@ -359,7 +389,7 @@ void WSEvents::unhookTransitionPlaybackEvents() { obs_frontend_source_list transitions = {}; obs_frontend_get_transitions(&transitions); - for (int i = 0; i < transitions.sources.num; i++) { + for (uint i = 0; i < transitions.sources.num; i++) { obs_source_t* transition = transitions.sources.array[i]; signal_handler_t* sh = obs_source_get_signal_handler(transition); signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this); @@ -400,6 +430,16 @@ QString WSEvents::getRecordingTimecode() { return Utils::nsToTimestamp(getRecordingTime()); } +OBSDataAutoRelease getMediaSourceData(calldata_t* data) { + OBSDataAutoRelease fields = obs_data_create(); + OBSSource source = calldata_get_pointer(data, "source"); + + obs_data_set_string(fields, "sourceName", obs_source_get_name(source)); + obs_data_set_string(fields, "sourceKind", obs_source_get_id(source)); + + return fields; +} + /** * Indicates a scene change. * @@ -426,25 +466,37 @@ void WSEvents::OnSceneChange() { * The scene list has been modified. * Scenes have been added, removed, or renamed. * + * Note: This event is not fired when the scenes are reordered. + * + * @return {Array} `scenes` Scenes list. + * * @api events * @name ScenesChanged * @category scenes * @since 0.3 */ void WSEvents::OnSceneListChange() { - broadcastUpdate("ScenesChanged"); + OBSDataArrayAutoRelease scenes = Utils::GetScenes(); + + OBSDataAutoRelease fields = obs_data_create(); + obs_data_set_array(fields, "scenes", scenes); + broadcastUpdate("ScenesChanged", fields); } /** * Triggered when switching to another scene collection or when renaming the current scene collection. * + * @return {String} `sceneCollection` Name of the new current scene collection. + * * @api events * @name SceneCollectionChanged * @category scenes * @since 4.0.0 */ void WSEvents::OnSceneCollectionChange() { - broadcastUpdate("SceneCollectionChanged"); + OBSDataAutoRelease fields = obs_data_create(); + obs_data_set_string(fields, "sceneCollection", obs_frontend_get_current_scene_collection()); + broadcastUpdate("SceneCollectionChanged", fields); OnTransitionListChange(); OnTransitionChange(); @@ -456,13 +508,23 @@ void WSEvents::OnSceneCollectionChange() { /** * Triggered when a scene collection is created, added, renamed, or removed. * + * @return {Array} `sceneCollections` Scene collections list. + * @return {String} `sceneCollections.*.name` Scene collection name. + * * @api events * @name SceneCollectionListChanged * @category scenes * @since 4.0.0 */ void WSEvents::OnSceneCollectionListChange() { - broadcastUpdate("SceneCollectionListChanged"); + char** sceneCollections = obs_frontend_get_scene_collections(); + OBSDataArrayAutoRelease sceneCollectionsList = + Utils::StringListToArray(sceneCollections, "name"); + bfree(sceneCollections); + + OBSDataAutoRelease fields = obs_data_create(); + obs_data_set_array(fields, "sceneCollections", sceneCollectionsList); + broadcastUpdate("SceneCollectionListChanged", fields); } /** @@ -489,37 +551,68 @@ void WSEvents::OnTransitionChange() { * The list of available transitions has been modified. * Transitions have been added, removed, or renamed. * + * @return {Array} `transitions` Transitions list. + * @return {String} `transitions.*.name` Transition name. + * * @api events * @name TransitionListChanged * @category transitions * @since 4.0.0 */ void WSEvents::OnTransitionListChange() { - broadcastUpdate("TransitionListChanged"); + obs_frontend_source_list transitionList = {}; + obs_frontend_get_transitions(&transitionList); + + OBSDataArrayAutoRelease transitions = obs_data_array_create(); + for (size_t i = 0; i < transitionList.sources.num; i++) { + OBSSource transition = transitionList.sources.array[i]; + + OBSDataAutoRelease obj = obs_data_create(); + obs_data_set_string(obj, "name", obs_source_get_name(transition)); + obs_data_array_push_back(transitions, obj); + } + obs_frontend_source_list_free(&transitionList); + + OBSDataAutoRelease fields = obs_data_create(); + obs_data_set_array(fields, "transitions", transitions); + broadcastUpdate("TransitionListChanged", fields); } /** * Triggered when switching to another profile or when renaming the current profile. * + * @return {String} `profile` Name of the new current profile. + * * @api events * @name ProfileChanged * @category profiles * @since 4.0.0 */ void WSEvents::OnProfileChange() { - broadcastUpdate("ProfileChanged"); + OBSDataAutoRelease fields = obs_data_create(); + obs_data_set_string(fields, "profile", obs_frontend_get_current_profile()); + broadcastUpdate("ProfileChanged", fields); } /** * Triggered when a profile is created, added, renamed, or removed. * + * @return {Array} `profiles` Profiles list. + * @return {String} `profiles.*.name` Profile name. + * * @api events * @name ProfileListChanged * @category profiles * @since 4.0.0 */ void WSEvents::OnProfileListChange() { - broadcastUpdate("ProfileListChanged"); + char** profiles = obs_frontend_get_profiles(); + OBSDataArrayAutoRelease profilesList = Utils::StringListToArray(profiles, "name"); + bfree(profiles); + + OBSDataAutoRelease fields = obs_data_create(); + obs_data_set_array(fields, "profiles", profilesList); + broadcastUpdate("ProfileListChanged", fields); } /** @@ -587,6 +680,9 @@ void WSEvents::OnStreamStopped() { /** * A request to start recording has been issued. * + * Note: `recordingFilename` is not provided in this event because this information + * is not available at the time this event is emitted. + * * @api events * @name RecordingStarting * @category recording @@ -599,37 +695,49 @@ void WSEvents::OnRecordingStarting() { /** * Recording started successfully. * + * @return {String} `recordingFilename` Absolute path to the file of the current recording. + * * @api events * @name RecordingStarted * @category recording * @since 0.3 */ void WSEvents::OnRecordingStarted() { - broadcastUpdate("RecordingStarted"); + OBSDataAutoRelease data = obs_data_create(); + obs_data_set_string(data, "recordingFilename", Utils::GetCurrentRecordingFilename()); + broadcastUpdate("RecordingStarted", data); } /** * A request to stop recording has been issued. * + * @return {String} `recordingFilename` Absolute path to the file of the current recording. + * * @api events * @name RecordingStopping * @category recording * @since 0.3 */ void WSEvents::OnRecordingStopping() { - broadcastUpdate("RecordingStopping"); + OBSDataAutoRelease data = obs_data_create(); + obs_data_set_string(data, "recordingFilename", Utils::GetCurrentRecordingFilename()); + broadcastUpdate("RecordingStopping", data); } /** * Recording stopped successfully. * + * @return {String} `recordingFilename` Absolute path to the file of the current recording. + * * @api events * @name RecordingStopped * @category recording * @since 0.3 */ void WSEvents::OnRecordingStopped() { - broadcastUpdate("RecordingStopped"); + OBSDataAutoRelease data = obs_data_create(); + obs_data_set_string(data, "recordingFilename", Utils::GetCurrentRecordingFilename()); + broadcastUpdate("RecordingStopped", data); } /** @@ -717,7 +825,7 @@ void WSEvents::OnExit() { } /** - * Emit every 2 seconds. + * Emitted every 2 seconds when stream is active. * * @return {boolean} `streaming` Current streaming state. * @return {boolean} `recording` Current recording state. @@ -824,6 +932,7 @@ void WSEvents::StreamStatus() { * @api events * @name Heartbeat * @category general + * @since v0.3 */ void WSEvents::Heartbeat() { @@ -890,10 +999,10 @@ void WSEvents::TransitionDurationChanged(int ms) { * * @return {String} `name` Transition name. * @return {String} `type` Transition type. - * @return {int} `duration` Transition duration (in milliseconds). - * Will be -1 for any transition with a fixed duration, + * @return {int} `duration` Transition duration (in milliseconds). + * Will be -1 for any transition with a fixed duration, * such as a Stinger, due to limitations of the OBS API. - * @return {String} `from-scene` Source scene of the transition + * @return {String (optional)} `from-scene` Source scene of the transition * @return {String} `to-scene` Destination scene of the transition * * @api events @@ -915,7 +1024,7 @@ void WSEvents::OnTransitionBegin(void* param, calldata_t* data) { /** * A transition (other than "cut") has ended. -* Please note that the `from-scene` field is not available in TransitionEnd. +* Note: The `from-scene` field is not available in TransitionEnd. * * @return {String} `name` Transition name. * @return {String} `type` Transition type. @@ -945,7 +1054,7 @@ void WSEvents::OnTransitionEnd(void* param, calldata_t* data) { * @return {String} `name` Transition name. * @return {String} `type` Transition type. * @return {int} `duration` Transition duration (in milliseconds). -* @return {String} `from-scene` Source scene of the transition +* @return {String (optional)} `from-scene` Source scene of the transition * @return {String} `to-scene` Destination scene of the transition * * @api events @@ -1036,6 +1145,7 @@ void WSEvents::OnSourceDestroy(void* param, calldata_t* data) { * * @return {String} `sourceName` Source name * @return {float} `volume` Source volume + * @return {float} `volumeDb` Source volume in Decibel * * @api events * @name SourceVolumeChanged @@ -1055,9 +1165,15 @@ void WSEvents::OnSourceVolumeChange(void* param, calldata_t* data) { return; } + double volumeDb = obs_mul_to_db(volume); + if (volumeDb == -INFINITY) { + volumeDb = -100.0; + } + OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "sourceName", obs_source_get_name(source)); obs_data_set_double(fields, "volume", volume); + obs_data_set_double(fields, "volumeDb", volumeDb); self->broadcastUpdate("SourceVolumeChanged", fields); } @@ -1091,6 +1207,52 @@ void WSEvents::OnSourceMuteStateChange(void* param, calldata_t* data) { self->broadcastUpdate("SourceMuteStateChanged", fields); } +/** + * A source has removed audio. + * + * @return {String} `sourceName` Source name + * + * @api events + * @name SourceAudioDeactivated + * @category sources + * @since 4.9.0 + */ +void WSEvents::OnSourceAudioDeactivated(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSSource source = calldata_get_pointer(data, "source"); + if (!source) { + return; + } + + OBSDataAutoRelease fields = obs_data_create(); + obs_data_set_string(fields, "sourceName", obs_source_get_name(source)); + self->broadcastUpdate("SourceAudioDeactivated", fields); +} + +/** + * A source has added audio. + * + * @return {String} `sourceName` Source name + * + * @api events + * @name SourceAudioActivated + * @category sources + * @since 4.9.0 + */ +void WSEvents::OnSourceAudioActivated(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSSource source = calldata_get_pointer(data, "source"); + if (!source) { + return; + } + + OBSDataAutoRelease fields = obs_data_create(); + obs_data_set_string(fields, "sourceName", obs_source_get_name(source)); + self->broadcastUpdate("SourceAudioActivated", fields); +} + /** * The audio sync offset of a source has changed. * @@ -1170,6 +1332,7 @@ void WSEvents::OnSourceAudioMixersChanged(void* param, calldata_t* data) { * * @return {String} `previousName` Previous source name * @return {String} `newName` New source name + * @return {String} `sourceType` Type of source (input, scene, filter, transition) * * @api events * @name SourceRenamed @@ -1194,6 +1357,8 @@ void WSEvents::OnSourceRename(void* param, calldata_t* data) { OBSDataAutoRelease fields = obs_data_create(); obs_data_set_string(fields, "previousName", previousName); obs_data_set_string(fields, "newName", newName); + obs_data_set_string(fields, "sourceType", + sourceTypeToString(obs_source_get_type(source))); // TODO: Split into dedicated events for source/scene. Only doing it this way for backwards compatability until 5.0 self->broadcastUpdate("SourceRenamed", fields); } @@ -1222,7 +1387,7 @@ void WSEvents::OnSourceFilterAdded(void* param, calldata_t* data) { if (!filter) { return; } - + self->connectFilterSignals(filter); OBSDataAutoRelease filterSettings = obs_source_get_settings(filter); @@ -1306,6 +1471,7 @@ void WSEvents::OnSourceFilterVisibilityChanged(void* param, calldata_t* data) { * @return {Array} `filters` Ordered Filters list * @return {String} `filters.*.name` Filter name * @return {String} `filters.*.type` Filter type + * @return {boolean} `filters.*.enabled` Filter visibility status * * @api events * @name SourceFiltersReordered @@ -1329,7 +1495,175 @@ void WSEvents::OnSourceFilterOrderChanged(void* param, calldata_t* data) { } /** - * Scene items have been reordered. + * A media source has started playing. + * + * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + * + * @return {String} `sourceName` Source name + * @return {String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) + * + * @api events + * @name MediaPlaying + * @category media + * @since 4.9.0 + */ +void WSEvents::OnMediaPlaying(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSDataAutoRelease fields = getMediaSourceData(data); + + self->broadcastUpdate("MediaPlaying", fields); +} + +/** + * A media source has been paused. + * + * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + * + * @return {String} `sourceName` Source name + * @return {String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) + * + * @api events + * @name MediaPaused + * @category media + * @since 4.9.0 + */ +void WSEvents::OnMediaPaused(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSDataAutoRelease fields = getMediaSourceData(data); + + self->broadcastUpdate("MediaPaused", fields); +} + +/** + * A media source has been restarted. + * + * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + * + * @return {String} `sourceName` Source name + * @return {String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) + * + * @api events + * @name MediaRestarted + * @category media + * @since 4.9.0 + */ +void WSEvents::OnMediaRestarted(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSDataAutoRelease fields = getMediaSourceData(data); + + self->broadcastUpdate("MediaRestarted", fields); +} + +/** + * A media source has been stopped. + * + * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + * + * @return {String} `sourceName` Source name + * @return {String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) + * + * @api events + * @name MediaStopped + * @category media + * @since 4.9.0 + */ +void WSEvents::OnMediaStopped(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSDataAutoRelease fields = getMediaSourceData(data); + + self->broadcastUpdate("MediaStopped", fields); +} + +/** + * A media source has gone to the next item in the playlist. + * + * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + * + * @return {String} `sourceName` Source name + * @return {String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) + * + * @api events + * @name MediaNext + * @category media + * @since 4.9.0 + */ +void WSEvents::OnMediaNext(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSDataAutoRelease fields = getMediaSourceData(data); + + self->broadcastUpdate("MediaNext", fields); +} + +/** + * A media source has gone to the previous item in the playlist. + * + * Note: This event is only emitted when something actively controls the media/VLC source. In other words, the source will never emit this on its own naturally. + * + * @return {String} `sourceName` Source name + * @return {String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) + * + * @api events + * @name MediaPrevious + * @category media + * @since 4.9.0 + */ +void WSEvents::OnMediaPrevious(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSDataAutoRelease fields = getMediaSourceData(data); + + self->broadcastUpdate("MediaPrevious", fields); +} + +/** + * A media source has been started. + * + * Note: These events are emitted by the OBS sources themselves. For example when the media file starts playing. The behavior depends on the type of media source being used. + * + * @return {String} `sourceName` Source name + * @return {String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) + * + * @api events + * @name MediaStarted + * @category media + * @since 4.9.0 + */ +void WSEvents::OnMediaStarted(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSDataAutoRelease fields = getMediaSourceData(data); + + self->broadcastUpdate("MediaStarted", fields); +} + +/** + * A media source has ended. + * + * Note: These events are emitted by the OBS sources themselves. For example when the media file ends. The behavior depends on the type of media source being used. + * + * @return {String} `sourceName` Source name + * @return {String} `sourceKind` The ID type of the source (Eg. `vlc_source` or `ffmpeg_source`) + * + * @api events + * @name MediaEnded + * @category media + * @since 4.9.0 + */ +void WSEvents::OnMediaEnded(void* param, calldata_t* data) { + auto self = reinterpret_cast(param); + + OBSDataAutoRelease fields = getMediaSourceData(data); + + self->broadcastUpdate("MediaEnded", fields); +} + +/** + * Scene items within a scene have been reordered. * * @return {String} `scene-name` Name of the scene where items have been reordered. * @return {Array} `scene-items` Ordered list of scene items @@ -1338,7 +1672,7 @@ void WSEvents::OnSourceFilterOrderChanged(void* param, calldata_t* data) { * * @api events * @name SourceOrderChanged - * @category sources + * @category scene items * @since 4.0.0 */ void WSEvents::OnSceneReordered(void* param, calldata_t* data) { @@ -1372,7 +1706,7 @@ void WSEvents::OnSceneReordered(void* param, calldata_t* data) { } /** - * An item has been added to the current scene. + * A scene item has been added to a scene. * * @return {String} `scene-name` Name of the scene. * @return {String} `item-name` Name of the item added to the scene. @@ -1380,7 +1714,7 @@ void WSEvents::OnSceneReordered(void* param, calldata_t* data) { * * @api events * @name SceneItemAdded - * @category sources + * @category scene items * @since 4.0.0 */ void WSEvents::OnSceneItemAdd(void* param, calldata_t* data) { @@ -1405,7 +1739,7 @@ void WSEvents::OnSceneItemAdd(void* param, calldata_t* data) { } /** - * An item has been removed from the current scene. + * A scene item has been removed from a scene. * * @return {String} `scene-name` Name of the scene. * @return {String} `item-name` Name of the item removed from the scene. @@ -1413,7 +1747,7 @@ void WSEvents::OnSceneItemAdd(void* param, calldata_t* data) { * * @api events * @name SceneItemRemoved - * @category sources + * @category scene items * @since 4.0.0 */ void WSEvents::OnSceneItemDelete(void* param, calldata_t* data) { @@ -1438,7 +1772,7 @@ void WSEvents::OnSceneItemDelete(void* param, calldata_t* data) { } /** - * An item's visibility has been toggled. + * A scene item's visibility has been toggled. * * @return {String} `scene-name` Name of the scene. * @return {String} `item-name` Name of the item in the scene. @@ -1447,7 +1781,7 @@ void WSEvents::OnSceneItemDelete(void* param, calldata_t* data) { * * @api events * @name SceneItemVisibilityChanged - * @category sources + * @category scene items * @since 4.0.0 */ void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) { @@ -1476,7 +1810,7 @@ void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) { } /** - * An item's locked status has been toggled. + * A scene item's locked status has been toggled. * * @return {String} `scene-name` Name of the scene. * @return {String} `item-name` Name of the item in the scene. @@ -1485,8 +1819,8 @@ void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) { * * @api events * @name SceneItemLockChanged - * @category sources - * @since unreleased + * @category scene items + * @since 4.8.0 */ void WSEvents::OnSceneItemLockChanged(void* param, calldata_t* data) { auto instance = reinterpret_cast(param); @@ -1514,7 +1848,7 @@ void WSEvents::OnSceneItemLockChanged(void* param, calldata_t* data) { } /** - * An item's transform has been changed. + * A scene item's transform has been changed. * * @return {String} `scene-name` Name of the scene. * @return {String} `item-name` Name of the item in the scene. @@ -1523,7 +1857,7 @@ void WSEvents::OnSceneItemLockChanged(void* param, calldata_t* data) { * * @api events * @name SceneItemTransformChanged - * @category sources + * @category scene items * @since 4.6.0 */ void WSEvents::OnSceneItemTransform(void* param, calldata_t* data) { @@ -1559,7 +1893,7 @@ void WSEvents::OnSceneItemTransform(void* param, calldata_t* data) { * * @api events * @name SceneItemSelected - * @category sources + * @category scene items * @since 4.6.0 */ void WSEvents::OnSceneItemSelected(void* param, calldata_t* data) { @@ -1594,7 +1928,7 @@ void WSEvents::OnSceneItemSelected(void* param, calldata_t* data) { * * @api events * @name SceneItemDeselected - * @category sources + * @category scene items * @since 4.6.0 */ void WSEvents::OnSceneItemDeselected(void* param, calldata_t* data) { @@ -1665,7 +1999,7 @@ void WSEvents::OnStudioModeSwitched(bool checked) { } /** - * A custom broadcast message was received + * A custom broadcast message, sent by the server, requested by one of the websocket clients. * * @return {String} `realm` Identifier provided by the sender * @return {Object} `data` User-defined data diff --git a/src/WSEvents.h b/src/WSEvents.h index 06ce7f2c..dc814e83 100644 --- a/src/WSEvents.h +++ b/src/WSEvents.h @@ -126,6 +126,8 @@ private: static void OnSourceMuteStateChange(void* param, calldata_t* data); static void OnSourceAudioSyncOffsetChanged(void* param, calldata_t* data); static void OnSourceAudioMixersChanged(void* param, calldata_t* data); + static void OnSourceAudioActivated(void* param, calldata_t* data); + static void OnSourceAudioDeactivated(void* param, calldata_t* data); static void OnSourceRename(void* param, calldata_t* data); @@ -133,6 +135,15 @@ private: static void OnSourceFilterRemoved(void* param, calldata_t* data); static void OnSourceFilterVisibilityChanged(void* param, calldata_t* data); static void OnSourceFilterOrderChanged(void* param, calldata_t* data); + + static void OnMediaPlaying(void* param, calldata_t* data); + static void OnMediaPaused(void* param, calldata_t* data); + static void OnMediaRestarted(void* param, calldata_t* data); + static void OnMediaStopped(void* param, calldata_t* data); + static void OnMediaNext(void* param, calldata_t* data); + static void OnMediaPrevious(void* param, calldata_t* data); + static void OnMediaStarted(void* param, calldata_t* data); + static void OnMediaEnded(void* param, calldata_t* data); static void OnSceneReordered(void* param, calldata_t* data); static void OnSceneItemAdd(void* param, calldata_t* data); diff --git a/src/WSRequestHandler.cpp b/src/WSRequestHandler.cpp index f2ae6c47..78706ea1 100644 --- a/src/WSRequestHandler.cpp +++ b/src/WSRequestHandler.cpp @@ -28,77 +28,118 @@ using namespace std::placeholders; -const QHash WSRequestHandler::messageMap { +const QHash WSRequestHandler::messageMap{ + // Category: General { "GetVersion", &WSRequestHandler::GetVersion }, { "GetAuthRequired", &WSRequestHandler::GetAuthRequired }, { "Authenticate", &WSRequestHandler::Authenticate }, - - { "GetStats", &WSRequestHandler::GetStats }, { "SetHeartbeat", &WSRequestHandler::SetHeartbeat }, - { "GetVideoInfo", &WSRequestHandler::GetVideoInfo }, - { "OpenProjector", &WSRequestHandler::OpenProjector }, - { "SetFilenameFormatting", &WSRequestHandler::SetFilenameFormatting }, { "GetFilenameFormatting", &WSRequestHandler::GetFilenameFormatting }, - + { "GetStats", &WSRequestHandler::GetStats }, { "BroadcastCustomMessage", &WSRequestHandler::BroadcastCustomMessage }, + { "GetVideoInfo", &WSRequestHandler::GetVideoInfo }, + { "OpenProjector", &WSRequestHandler::OpenProjector }, + { "TriggerHotkeyByName", &WSRequestHandler::TriggerHotkeyByName }, + { "TriggerHotkeyBySequence", &WSRequestHandler::TriggerHotkeyBySequence }, + { "ExecuteBatch", &WSRequestHandler::ExecuteBatch }, + { "Sleep", &WSRequestHandler::Sleep }, - { "SetCurrentScene", &WSRequestHandler::SetCurrentScene }, - { "GetCurrentScene", &WSRequestHandler::GetCurrentScene }, - { "GetSceneList", &WSRequestHandler::GetSceneList }, + // Category: Media Control + { "PlayPauseMedia", &WSRequestHandler::PlayPauseMedia }, + { "RestartMedia", &WSRequestHandler::RestartMedia }, + { "StopMedia", &WSRequestHandler::StopMedia }, + { "NextMedia", &WSRequestHandler::NextMedia }, + { "PreviousMedia", &WSRequestHandler::PreviousMedia }, + { "GetMediaDuration", &WSRequestHandler::GetMediaDuration }, + { "GetMediaTime", &WSRequestHandler::GetMediaTime }, + { "SetMediaTime", &WSRequestHandler::SetMediaTime }, + { "ScrubMedia", &WSRequestHandler::ScrubMedia }, + { "GetMediaState", &WSRequestHandler::GetMediaState }, + { "GetMediaSourcesList", &WSRequestHandler::GetMediaSourcesList }, - { "SetSourceRender", &WSRequestHandler::SetSceneItemRender }, // Retrocompat - { "SetSceneItemRender", &WSRequestHandler::SetSceneItemRender }, - { "SetSceneItemPosition", &WSRequestHandler::SetSceneItemPosition }, - { "SetSceneItemTransform", &WSRequestHandler::SetSceneItemTransform }, - { "SetSceneItemCrop", &WSRequestHandler::SetSceneItemCrop }, - { "GetSceneItemProperties", &WSRequestHandler::GetSceneItemProperties }, - { "SetSceneItemProperties", &WSRequestHandler::SetSceneItemProperties }, - { "ResetSceneItem", &WSRequestHandler::ResetSceneItem }, - { "DeleteSceneItem", &WSRequestHandler::DeleteSceneItem }, - { "DuplicateSceneItem", &WSRequestHandler::DuplicateSceneItem }, - { "ReorderSceneItems", &WSRequestHandler::ReorderSceneItems }, + // Category: Outputs + { "ListOutputs", &WSRequestHandler::ListOutputs }, + { "GetOutputInfo", &WSRequestHandler::GetOutputInfo }, + { "StartOutput", &WSRequestHandler::StartOutput }, + { "StopOutput", &WSRequestHandler::StopOutput }, - { "GetStreamingStatus", &WSRequestHandler::GetStreamingStatus }, - { "StartStopStreaming", &WSRequestHandler::StartStopStreaming }, + // Category: Profiles + { "SetCurrentProfile", &WSRequestHandler::SetCurrentProfile }, + { "GetCurrentProfile", &WSRequestHandler::GetCurrentProfile }, + { "ListProfiles", &WSRequestHandler::ListProfiles }, + + // Category: Recording + { "GetRecordingStatus", &WSRequestHandler::GetRecordingStatus }, { "StartStopRecording", &WSRequestHandler::StartStopRecording }, - - { "StartStreaming", &WSRequestHandler::StartStreaming }, - { "StopStreaming", &WSRequestHandler::StopStreaming }, - { "StartRecording", &WSRequestHandler::StartRecording }, { "StopRecording", &WSRequestHandler::StopRecording }, { "PauseRecording", &WSRequestHandler::PauseRecording }, { "ResumeRecording", &WSRequestHandler::ResumeRecording }, + { "SetRecordingFolder", &WSRequestHandler::SetRecordingFolder }, + { "GetRecordingFolder", &WSRequestHandler::GetRecordingFolder }, + // Category: Replay Buffer + { "GetReplayBufferStatus", &WSRequestHandler::GetReplayBufferStatus }, { "StartStopReplayBuffer", &WSRequestHandler::StartStopReplayBuffer }, { "StartReplayBuffer", &WSRequestHandler::StartReplayBuffer }, { "StopReplayBuffer", &WSRequestHandler::StopReplayBuffer }, { "SaveReplayBuffer", &WSRequestHandler::SaveReplayBuffer }, - { "SetRecordingFolder", &WSRequestHandler::SetRecordingFolder }, - { "GetRecordingFolder", &WSRequestHandler::GetRecordingFolder }, + // Category: Scene Collections + { "SetCurrentSceneCollection", &WSRequestHandler::SetCurrentSceneCollection }, + { "GetCurrentSceneCollection", &WSRequestHandler::GetCurrentSceneCollection }, + { "ListSceneCollections", &WSRequestHandler::ListSceneCollections }, - { "GetTransitionList", &WSRequestHandler::GetTransitionList }, - { "GetCurrentTransition", &WSRequestHandler::GetCurrentTransition }, - { "SetCurrentTransition", &WSRequestHandler::SetCurrentTransition }, - { "SetTransitionDuration", &WSRequestHandler::SetTransitionDuration }, - { "GetTransitionDuration", &WSRequestHandler::GetTransitionDuration }, + // Category: Scene Items + { "GetSceneItemList", &WSRequestHandler::GetSceneItemList }, + { "GetSceneItemProperties", &WSRequestHandler::GetSceneItemProperties }, + { "SetSceneItemProperties", &WSRequestHandler::SetSceneItemProperties }, + { "ResetSceneItem", &WSRequestHandler::ResetSceneItem }, + { "SetSceneItemRender", &WSRequestHandler::SetSceneItemRender }, + { "SetSceneItemPosition", &WSRequestHandler::SetSceneItemPosition }, + { "SetSceneItemTransform", &WSRequestHandler::SetSceneItemTransform }, + { "SetSceneItemCrop", &WSRequestHandler::SetSceneItemCrop }, + { "SetSourceRender", &WSRequestHandler::SetSceneItemRender }, // Retrocompat TODO: Remove in 5.0.0 + { "DeleteSceneItem", &WSRequestHandler::DeleteSceneItem }, + { "AddSceneItem", &WSRequestHandler::AddSceneItem }, + { "DuplicateSceneItem", &WSRequestHandler::DuplicateSceneItem }, - { "SetVolume", &WSRequestHandler::SetVolume }, - { "GetVolume", &WSRequestHandler::GetVolume }, - { "ToggleMute", &WSRequestHandler::ToggleMute }, - { "SetMute", &WSRequestHandler::SetMute }, - { "GetMute", &WSRequestHandler::GetMute }, - { "SetSyncOffset", &WSRequestHandler::SetSyncOffset }, - { "GetSyncOffset", &WSRequestHandler::GetSyncOffset }, - { "GetSpecialSources", &WSRequestHandler::GetSpecialSources }, + // Category: Scenes + { "SetCurrentScene", &WSRequestHandler::SetCurrentScene }, + { "GetCurrentScene", &WSRequestHandler::GetCurrentScene }, + { "GetSceneList", &WSRequestHandler::GetSceneList }, + { "CreateScene", &WSRequestHandler::CreateScene }, + { "ReorderSceneItems", &WSRequestHandler::ReorderSceneItems }, + { "SetSceneTransitionOverride", &WSRequestHandler::SetSceneTransitionOverride }, + { "RemoveSceneTransitionOverride", &WSRequestHandler::RemoveSceneTransitionOverride }, + { "GetSceneTransitionOverride", &WSRequestHandler::GetSceneTransitionOverride }, + + // Category: Sources + { "CreateSource", &WSRequestHandler::CreateSource }, { "GetSourcesList", &WSRequestHandler::GetSourcesList }, { "GetSourceTypesList", &WSRequestHandler::GetSourceTypesList }, + { "GetVolume", &WSRequestHandler::GetVolume }, + { "SetVolume", &WSRequestHandler::SetVolume }, + { "SetAudioTracks", &WSRequestHandler::SetAudioTracks }, + { "GetAudioTracks", &WSRequestHandler::GetAudioTracks }, + { "GetMute", &WSRequestHandler::GetMute }, + { "SetMute", &WSRequestHandler::SetMute }, + { "ToggleMute", &WSRequestHandler::ToggleMute }, + { "GetSourceActive", &WSRequestHandler::GetSourceActive }, + { "GetAudioActive", &WSRequestHandler::GetAudioActive }, + { "SetSourceName", &WSRequestHandler::SetSourceName }, + { "SetSyncOffset", &WSRequestHandler::SetSyncOffset }, + { "GetSyncOffset", &WSRequestHandler::GetSyncOffset }, { "GetSourceSettings", &WSRequestHandler::GetSourceSettings }, { "SetSourceSettings", &WSRequestHandler::SetSourceSettings }, - { "TakeSourceScreenshot", &WSRequestHandler::TakeSourceScreenshot }, - + { "GetTextGDIPlusProperties", &WSRequestHandler::GetTextGDIPlusProperties }, + { "SetTextGDIPlusProperties", &WSRequestHandler::SetTextGDIPlusProperties }, + { "GetTextFreetype2Properties", &WSRequestHandler::GetTextFreetype2Properties }, + { "SetTextFreetype2Properties", &WSRequestHandler::SetTextFreetype2Properties }, + { "GetBrowserSourceProperties", &WSRequestHandler::GetBrowserSourceProperties }, + { "SetBrowserSourceProperties", &WSRequestHandler::SetBrowserSourceProperties }, + { "GetSpecialSources", &WSRequestHandler::GetSpecialSources }, { "GetSourceFilters", &WSRequestHandler::GetSourceFilters }, { "GetSourceFilterInfo", &WSRequestHandler::GetSourceFilterInfo }, { "AddFilterToSource", &WSRequestHandler::AddFilterToSource }, @@ -107,22 +148,23 @@ const QHash WSRequestHandler::messageMap { { "MoveSourceFilter", &WSRequestHandler::MoveSourceFilter }, { "SetSourceFilterSettings", &WSRequestHandler::SetSourceFilterSettings }, { "SetSourceFilterVisibility", &WSRequestHandler::SetSourceFilterVisibility }, + { "GetAudioMonitorType", &WSRequestHandler::GetAudioMonitorType }, + { "SetAudioMonitorType", &WSRequestHandler::SetAudioMonitorType }, + { "GetSourceDefaultSettings", &WSRequestHandler::GetSourceDefaultSettings }, + { "TakeSourceScreenshot", &WSRequestHandler::TakeSourceScreenshot }, + { "RefreshBrowserSource", &WSRequestHandler::RefreshBrowserSource }, - { "SetCurrentSceneCollection", &WSRequestHandler::SetCurrentSceneCollection }, - { "GetCurrentSceneCollection", &WSRequestHandler::GetCurrentSceneCollection }, - { "ListSceneCollections", &WSRequestHandler::ListSceneCollections }, - - { "SetCurrentProfile", &WSRequestHandler::SetCurrentProfile }, - { "GetCurrentProfile", &WSRequestHandler::GetCurrentProfile }, - { "ListProfiles", &WSRequestHandler::ListProfiles }, - + // Category: Streaming + { "GetStreamingStatus", &WSRequestHandler::GetStreamingStatus }, + { "StartStopStreaming", &WSRequestHandler::StartStopStreaming }, + { "StartStreaming", &WSRequestHandler::StartStreaming }, + { "StopStreaming", &WSRequestHandler::StopStreaming }, { "SetStreamSettings", &WSRequestHandler::SetStreamSettings }, { "GetStreamSettings", &WSRequestHandler::GetStreamSettings }, { "SaveStreamSettings", &WSRequestHandler::SaveStreamSettings }, -#if BUILD_CAPTIONS { "SendCaptions", &WSRequestHandler::SendCaptions }, -#endif + // Category: Studio Mode { "GetStudioModeStatus", &WSRequestHandler::GetStudioModeStatus }, { "GetPreviewScene", &WSRequestHandler::GetPreviewScene }, { "SetPreviewScene", &WSRequestHandler::SetPreviewScene }, @@ -131,19 +173,17 @@ const QHash WSRequestHandler::messageMap { { "DisableStudioMode", &WSRequestHandler::DisableStudioMode }, { "ToggleStudioMode", &WSRequestHandler::ToggleStudioMode }, - { "SetTextGDIPlusProperties", &WSRequestHandler::SetTextGDIPlusProperties }, - { "GetTextGDIPlusProperties", &WSRequestHandler::GetTextGDIPlusProperties }, - - { "SetTextFreetype2Properties", &WSRequestHandler::SetTextFreetype2Properties }, - { "GetTextFreetype2Properties", &WSRequestHandler::GetTextFreetype2Properties }, - - { "GetBrowserSourceProperties", &WSRequestHandler::GetBrowserSourceProperties }, - { "SetBrowserSourceProperties", &WSRequestHandler::SetBrowserSourceProperties }, - - { "ListOutputs", &WSRequestHandler::ListOutputs }, - { "GetOutputInfo", &WSRequestHandler::GetOutputInfo }, - { "StartOutput", &WSRequestHandler::StartOutput }, - { "StopOutput", &WSRequestHandler::StopOutput } + // Category: Transitions + { "GetTransitionList", &WSRequestHandler::GetTransitionList }, + { "GetCurrentTransition", &WSRequestHandler::GetCurrentTransition }, + { "SetCurrentTransition", &WSRequestHandler::SetCurrentTransition }, + { "SetTransitionDuration", &WSRequestHandler::SetTransitionDuration }, + { "GetTransitionDuration", &WSRequestHandler::GetTransitionDuration }, + { "GetTransitionPosition", &WSRequestHandler::GetTransitionPosition }, + { "GetTransitionSettings", &WSRequestHandler::GetTransitionSettings }, + { "SetTransitionSettings", &WSRequestHandler::SetTransitionSettings }, + { "ReleaseTBar", &WSRequestHandler::ReleaseTBar }, + { "SetTBarPosition", &WSRequestHandler::SetTBarPosition } }; const QSet WSRequestHandler::authNotRequired { @@ -157,8 +197,9 @@ WSRequestHandler::WSRequestHandler(ConnectionProperties& connProperties) : { } -RpcResponse WSRequestHandler::processRequest(const RpcRequest& request){ - if (GetConfig()->AuthRequired +RpcResponse WSRequestHandler::processRequest(const RpcRequest& request) { + auto config = GetConfig(); + if ((config && config->AuthRequired) && (!authNotRequired.contains(request.methodName())) && (!_connProperties.isAuthenticated())) { diff --git a/src/WSRequestHandler.h b/src/WSRequestHandler.h index d17384ee..bd57a731 100644 --- a/src/WSRequestHandler.h +++ b/src/WSRequestHandler.h @@ -47,73 +47,116 @@ class WSRequestHandler { static const QHash messageMap; static const QSet authNotRequired; + // Category: General RpcResponse GetVersion(const RpcRequest&); RpcResponse GetAuthRequired(const RpcRequest&); RpcResponse Authenticate(const RpcRequest&); - - RpcResponse GetStats(const RpcRequest&); RpcResponse SetHeartbeat(const RpcRequest&); - RpcResponse GetVideoInfo(const RpcRequest&); - RpcResponse OpenProjector(const RpcRequest&); - RpcResponse SetFilenameFormatting(const RpcRequest&); RpcResponse GetFilenameFormatting(const RpcRequest&); - + RpcResponse GetStats(const RpcRequest&); RpcResponse BroadcastCustomMessage(const RpcRequest&); + RpcResponse GetVideoInfo(const RpcRequest&); + RpcResponse OpenProjector(const RpcRequest&); + RpcResponse TriggerHotkeyByName(const RpcRequest&); + RpcResponse TriggerHotkeyBySequence(const RpcRequest&); + RpcResponse ExecuteBatch(const RpcRequest&); + RpcResponse Sleep(const RpcRequest&); - RpcResponse SetCurrentScene(const RpcRequest&); - RpcResponse GetCurrentScene(const RpcRequest&); - RpcResponse GetSceneList(const RpcRequest&); + // Category: Media Control + RpcResponse PlayPauseMedia(const RpcRequest&); + RpcResponse RestartMedia(const RpcRequest&); + RpcResponse StopMedia(const RpcRequest&); + RpcResponse NextMedia(const RpcRequest&); + RpcResponse PreviousMedia(const RpcRequest&); + RpcResponse GetMediaDuration(const RpcRequest&); + RpcResponse GetMediaTime(const RpcRequest&); + RpcResponse SetMediaTime(const RpcRequest&); + RpcResponse ScrubMedia(const RpcRequest&); + RpcResponse GetMediaState(const RpcRequest&); + RpcResponse GetMediaSourcesList(const RpcRequest&); - RpcResponse SetSceneItemRender(const RpcRequest&); - RpcResponse SetSceneItemPosition(const RpcRequest&); - RpcResponse SetSceneItemTransform(const RpcRequest&); - RpcResponse SetSceneItemCrop(const RpcRequest&); - RpcResponse GetSceneItemProperties(const RpcRequest&); - RpcResponse SetSceneItemProperties(const RpcRequest&); - RpcResponse ResetSceneItem(const RpcRequest&); - RpcResponse DuplicateSceneItem(const RpcRequest&); - RpcResponse DeleteSceneItem(const RpcRequest&); - RpcResponse ReorderSceneItems(const RpcRequest&); + // Category: Outputs + RpcResponse ListOutputs(const RpcRequest&); + RpcResponse GetOutputInfo(const RpcRequest&); + RpcResponse StartOutput(const RpcRequest&); + RpcResponse StopOutput(const RpcRequest&); - RpcResponse GetStreamingStatus(const RpcRequest&); - RpcResponse StartStopStreaming(const RpcRequest&); + // Category: Profiles + RpcResponse SetCurrentProfile(const RpcRequest&); + RpcResponse GetCurrentProfile(const RpcRequest&); + RpcResponse ListProfiles(const RpcRequest&); + + // Category: Recording + RpcResponse GetRecordingStatus(const RpcRequest&); RpcResponse StartStopRecording(const RpcRequest&); - - RpcResponse StartStreaming(const RpcRequest&); - RpcResponse StopStreaming(const RpcRequest&); - RpcResponse StartRecording(const RpcRequest&); RpcResponse StopRecording(const RpcRequest&); RpcResponse PauseRecording(const RpcRequest&); RpcResponse ResumeRecording(const RpcRequest&); + RpcResponse SetRecordingFolder(const RpcRequest&); + RpcResponse GetRecordingFolder(const RpcRequest&); + // Category: Replay Buffer + RpcResponse GetReplayBufferStatus(const RpcRequest&); RpcResponse StartStopReplayBuffer(const RpcRequest&); RpcResponse StartReplayBuffer(const RpcRequest&); RpcResponse StopReplayBuffer(const RpcRequest&); RpcResponse SaveReplayBuffer(const RpcRequest&); - RpcResponse SetRecordingFolder(const RpcRequest&); - RpcResponse GetRecordingFolder(const RpcRequest&); + // Category: Scene Collections + RpcResponse SetCurrentSceneCollection(const RpcRequest&); + RpcResponse GetCurrentSceneCollection(const RpcRequest&); + RpcResponse ListSceneCollections(const RpcRequest&); - RpcResponse GetTransitionList(const RpcRequest&); - RpcResponse GetCurrentTransition(const RpcRequest&); - RpcResponse SetCurrentTransition(const RpcRequest&); + // Category: Scene Items + RpcResponse GetSceneItemList(const RpcRequest&); + RpcResponse GetSceneItemProperties(const RpcRequest&); + RpcResponse SetSceneItemProperties(const RpcRequest&); + RpcResponse ResetSceneItem(const RpcRequest&); + RpcResponse SetSceneItemRender(const RpcRequest&); + RpcResponse SetSceneItemPosition(const RpcRequest&); + RpcResponse SetSceneItemTransform(const RpcRequest&); + RpcResponse SetSceneItemCrop(const RpcRequest&); + RpcResponse DeleteSceneItem(const RpcRequest&); + RpcResponse AddSceneItem(const RpcRequest&); + RpcResponse DuplicateSceneItem(const RpcRequest&); - RpcResponse SetVolume(const RpcRequest&); - RpcResponse GetVolume(const RpcRequest&); - RpcResponse ToggleMute(const RpcRequest&); - RpcResponse SetMute(const RpcRequest&); - RpcResponse GetMute(const RpcRequest&); - RpcResponse SetSyncOffset(const RpcRequest&); - RpcResponse GetSyncOffset(const RpcRequest&); - RpcResponse GetSpecialSources(const RpcRequest&); + // Category: Scenes + RpcResponse SetCurrentScene(const RpcRequest&); + RpcResponse GetCurrentScene(const RpcRequest&); + RpcResponse GetSceneList(const RpcRequest&); + RpcResponse CreateScene(const RpcRequest&); + RpcResponse ReorderSceneItems(const RpcRequest&); + RpcResponse SetSceneTransitionOverride(const RpcRequest&); + RpcResponse RemoveSceneTransitionOverride(const RpcRequest&); + RpcResponse GetSceneTransitionOverride(const RpcRequest&); + + // Category: Sources + RpcResponse CreateSource(const RpcRequest&); RpcResponse GetSourcesList(const RpcRequest&); RpcResponse GetSourceTypesList(const RpcRequest&); + RpcResponse GetVolume(const RpcRequest&); + RpcResponse SetVolume(const RpcRequest&); + RpcResponse SetAudioTracks(const RpcRequest&); + RpcResponse GetAudioTracks(const RpcRequest&); + RpcResponse GetMute(const RpcRequest&); + RpcResponse SetMute(const RpcRequest&); + RpcResponse ToggleMute(const RpcRequest&); + RpcResponse GetSourceActive(const RpcRequest&); + RpcResponse GetAudioActive(const RpcRequest&); + RpcResponse SetSourceName(const RpcRequest&); + RpcResponse SetSyncOffset(const RpcRequest&); + RpcResponse GetSyncOffset(const RpcRequest&); RpcResponse GetSourceSettings(const RpcRequest&); RpcResponse SetSourceSettings(const RpcRequest&); - RpcResponse TakeSourceScreenshot(const RpcRequest&); - + RpcResponse GetTextGDIPlusProperties(const RpcRequest&); + RpcResponse SetTextGDIPlusProperties(const RpcRequest&); + RpcResponse GetTextFreetype2Properties(const RpcRequest&); + RpcResponse SetTextFreetype2Properties(const RpcRequest&); + RpcResponse GetBrowserSourceProperties(const RpcRequest&); + RpcResponse SetBrowserSourceProperties(const RpcRequest&); + RpcResponse GetSpecialSources(const RpcRequest&); RpcResponse GetSourceFilters(const RpcRequest&); RpcResponse GetSourceFilterInfo(const RpcRequest&); RpcResponse AddFilterToSource(const RpcRequest&); @@ -122,25 +165,23 @@ class WSRequestHandler { RpcResponse MoveSourceFilter(const RpcRequest&); RpcResponse SetSourceFilterSettings(const RpcRequest&); RpcResponse SetSourceFilterVisibility(const RpcRequest&); + RpcResponse GetAudioMonitorType(const RpcRequest&); + RpcResponse SetAudioMonitorType(const RpcRequest&); + RpcResponse GetSourceDefaultSettings(const RpcRequest&); + RpcResponse TakeSourceScreenshot(const RpcRequest&); + RpcResponse RefreshBrowserSource(const RpcRequest&); - RpcResponse SetCurrentSceneCollection(const RpcRequest&); - RpcResponse GetCurrentSceneCollection(const RpcRequest&); - RpcResponse ListSceneCollections(const RpcRequest&); - - RpcResponse SetCurrentProfile(const RpcRequest&); - RpcResponse GetCurrentProfile(const RpcRequest&); - RpcResponse ListProfiles(const RpcRequest&); - + // Category: Streaming + RpcResponse GetStreamingStatus(const RpcRequest&); + RpcResponse StartStopStreaming(const RpcRequest&); + RpcResponse StartStreaming(const RpcRequest&); + RpcResponse StopStreaming(const RpcRequest&); RpcResponse SetStreamSettings(const RpcRequest&); RpcResponse GetStreamSettings(const RpcRequest&); RpcResponse SaveStreamSettings(const RpcRequest&); -#if BUILD_CAPTIONS RpcResponse SendCaptions(const RpcRequest&); -#endif - - RpcResponse SetTransitionDuration(const RpcRequest&); - RpcResponse GetTransitionDuration(const RpcRequest&); + // Category: Studio Mode RpcResponse GetStudioModeStatus(const RpcRequest&); RpcResponse GetPreviewScene(const RpcRequest&); RpcResponse SetPreviewScene(const RpcRequest&); @@ -149,17 +190,15 @@ class WSRequestHandler { RpcResponse DisableStudioMode(const RpcRequest&); RpcResponse ToggleStudioMode(const RpcRequest&); - RpcResponse SetTextGDIPlusProperties(const RpcRequest&); - RpcResponse GetTextGDIPlusProperties(const RpcRequest&); - - RpcResponse SetTextFreetype2Properties(const RpcRequest&); - RpcResponse GetTextFreetype2Properties(const RpcRequest&); - - RpcResponse SetBrowserSourceProperties(const RpcRequest&); - RpcResponse GetBrowserSourceProperties(const RpcRequest&); - - RpcResponse ListOutputs(const RpcRequest&); - RpcResponse GetOutputInfo(const RpcRequest&); - RpcResponse StartOutput(const RpcRequest&); - RpcResponse StopOutput(const RpcRequest&); + // Category: Transitions + RpcResponse GetTransitionList(const RpcRequest&); + RpcResponse GetCurrentTransition(const RpcRequest&); + RpcResponse SetCurrentTransition(const RpcRequest&); + RpcResponse SetTransitionDuration(const RpcRequest&); + RpcResponse GetTransitionDuration(const RpcRequest&); + RpcResponse GetTransitionPosition(const RpcRequest&); + RpcResponse GetTransitionSettings(const RpcRequest&); + RpcResponse SetTransitionSettings(const RpcRequest&); + RpcResponse ReleaseTBar(const RpcRequest&); + RpcResponse SetTBarPosition(const RpcRequest&); }; diff --git a/src/WSRequestHandler_General.cpp b/src/WSRequestHandler_General.cpp index 56eeec8b..b08023d0 100644 --- a/src/WSRequestHandler_General.cpp +++ b/src/WSRequestHandler_General.cpp @@ -7,6 +7,7 @@ #include "Config.h" #include "Utils.h" #include "WSEvents.h" +#include "protocol/OBSRemoteProtocol.h" #define CASE(x) case x: return #x; const char *describe_output_format(int format) { @@ -114,13 +115,13 @@ RpcResponse WSRequestHandler::GetVersion(const RpcRequest& request) { * @since 0.3 */ RpcResponse WSRequestHandler::GetAuthRequired(const RpcRequest& request) { - bool authRequired = GetConfig()->AuthRequired; + auto config = GetConfig(); + bool authRequired = (config && config->AuthRequired); OBSDataAutoRelease data = obs_data_create(); obs_data_set_bool(data, "authRequired", authRequired); if (authRequired) { - auto config = GetConfig(); obs_data_set_string(data, "challenge", config->SessionChallenge.toUtf8()); obs_data_set_string(data, "salt", @@ -154,7 +155,8 @@ RpcResponse WSRequestHandler::Authenticate(const RpcRequest& request) { return request.failed("auth not specified!"); } - if (GetConfig()->CheckAuth(auth) == false) { + auto config = GetConfig(); + if (!config || (config->CheckAuth(auth) == false)) { return request.failed("Authentication Failed."); } @@ -171,6 +173,7 @@ RpcResponse WSRequestHandler::Authenticate(const RpcRequest& request) { * @name SetHeartbeat * @category general * @since 4.3.0 + * @deprecated Since 4.9.0. Please poll the appropriate data using requests. Will be removed in v5.0.0. */ RpcResponse WSRequestHandler::SetHeartbeat(const RpcRequest& request) { if (!request.hasField("enable")) { @@ -231,7 +234,7 @@ RpcResponse WSRequestHandler::GetFilenameFormatting(const RpcRequest& request) { /** * Get OBS stats (almost the same info as provided in OBS' stats window) * - * @return {OBSStats} `stats` OBS stats + * @return {OBSStats} `stats` [OBS stats](#obsstats) * * @api requests * @name GetStats @@ -319,16 +322,16 @@ RpcResponse WSRequestHandler::GetVideoInfo(const RpcRequest& request) { /** * Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 or newer. - * - * @param {String (Optional)} `type` Type of projector: Preview (default), Source, Scene, StudioProgram, or Multiview (case insensitive). + * + * @param {String (Optional)} `type` Type of projector: `Preview` (default), `Source`, `Scene`, `StudioProgram`, or `Multiview` (case insensitive). * @param {int (Optional)} `monitor` Monitor to open the projector on. If -1 or omitted, opens a window. - * @param {String (Optional)} `geometry` Size and position of the projector window (only if monitor is -1). Encoded in Base64 using Qt's geometry encoding (https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors. + * @param {String (Optional)} `geometry` Size and position of the projector window (only if monitor is -1). Encoded in Base64 using [Qt's geometry encoding](https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors. * @param {String (Optional)} `name` Name of the source or scene to be displayed (ignored for other projector types). - * + * * @api requests * @name OpenProjector * @category general - * @since unreleased + * @since 4.8.0 */ RpcResponse WSRequestHandler::OpenProjector(const RpcRequest& request) { const char* type = obs_data_get_string(request.parameters(), "type"); @@ -344,3 +347,151 @@ RpcResponse WSRequestHandler::OpenProjector(const RpcRequest& request) { obs_frontend_open_projector(type, monitor, geometry, name); return request.success(); } + +/** +* Executes hotkey routine, identified by hotkey unique name +* +* @param {String} `hotkeyName` Unique name of the hotkey, as defined when registering the hotkey (e.g. "ReplayBuffer.Save") +* +* @api requests +* @name TriggerHotkeyByName +* @category general +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::TriggerHotkeyByName(const RpcRequest& request) { + const char* name = obs_data_get_string(request.parameters(), "hotkeyName"); + + obs_hotkey_t* hk = Utils::FindHotkeyByName(name); + if (!hk) { + return request.failed("hotkey not found"); + } + obs_hotkey_trigger_routed_callback(obs_hotkey_get_id(hk), true); + return request.success(); +} + +/** +* Executes hotkey routine, identified by bound combination of keys. A single key combination might trigger multiple hotkey routines depending on user settings +* +* @param {String} `keyId` Main key identifier (e.g. `OBS_KEY_A` for key "A"). Available identifiers [here](https://github.com/obsproject/obs-studio/blob/master/libobs/obs-hotkeys.h) +* @param {Object (Optional)} `keyModifiers` Optional key modifiers object. False entries can be ommitted +* @param {boolean} `keyModifiers.shift` Trigger Shift Key +* @param {boolean} `keyModifiers.alt` Trigger Alt Key +* @param {boolean} `keyModifiers.control` Trigger Control (Ctrl) Key +* @param {boolean} `keyModifiers.command` Trigger Command Key (Mac) +* +* @api requests +* @name TriggerHotkeyBySequence +* @category general +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::TriggerHotkeyBySequence(const RpcRequest& request) { + if (!request.hasField("keyId")) { + return request.failed("missing request keyId parameter"); + } + + OBSDataAutoRelease data = obs_data_get_obj(request.parameters(), "keyModifiers"); + + obs_key_combination_t combo = {0}; + uint32_t modifiers = 0; + if (obs_data_get_bool(data, "shift")) + modifiers |= INTERACT_SHIFT_KEY; + if (obs_data_get_bool(data, "control")) + modifiers |= INTERACT_CONTROL_KEY; + if (obs_data_get_bool(data, "alt")) + modifiers |= INTERACT_ALT_KEY; + if (obs_data_get_bool(data, "command")) + modifiers |= INTERACT_COMMAND_KEY; + + combo.modifiers = modifiers; + combo.key = obs_key_from_name(obs_data_get_string(request.parameters(), "keyId")); + + if (!modifiers + && (combo.key == OBS_KEY_NONE || combo.key >= OBS_KEY_LAST_VALUE)) { + return request.failed("invalid key-modifier combination"); + } + + // Inject hotkey press-release sequence + obs_hotkey_inject_event(combo, false); + obs_hotkey_inject_event(combo, true); + obs_hotkey_inject_event(combo, false); + + return request.success(); +} + +/** +* Executes a list of requests sequentially (one-by-one on the same thread). +* +* @param {Array} `requests` Array of requests to perform. Executed in order. +* @param {String} `requests.*.request-type` Request type. Eg. `GetVersion`. +* @param {String (Optional)} `requests.*.message-id` ID of the individual request. Can be any string and not required to be unique. Defaults to empty string if not specified. +* @param {boolean (Optional)} `abortOnFail` Stop processing batch requests if one returns a failure. +* +* @return {Array} `results` Batch requests results, ordered sequentially. +* @return {String} `results.*.message-id` ID of the individual request which was originally provided by the client. +* @return {String} `results.*.status` Status response as string. Either `ok` or `error`. +* @return {String (Optional)} `results.*.error` Error message accompanying an `error` status. +* +* @api requests +* @name ExecuteBatch +* @category general +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::ExecuteBatch(const RpcRequest& request) { + if (!request.hasField("requests")) { + return request.failed("missing request parameters"); + } + + bool abortOnFail = obs_data_get_bool(request.parameters(), "abortOnFail"); + + OBSDataArrayAutoRelease results = obs_data_array_create(); + + OBSDataArrayAutoRelease requests = obs_data_get_array(request.parameters(), "requests"); + size_t requestsCount = obs_data_array_count(requests); + for (size_t i = 0; i < requestsCount; i++) { + OBSDataAutoRelease requestData = obs_data_array_item(requests, i); + QString messageId = obs_data_get_string(requestData, "message-id"); + QString methodName = obs_data_get_string(requestData, "request-type"); + obs_data_unset_user_value(requestData, "request-type"); + obs_data_unset_user_value(requestData, "message-id"); + + // build RpcRequest from json data object + RpcRequest subRequest(messageId, methodName, requestData); + + // execute the request + RpcResponse subResponse = processRequest(subRequest); + + // transform response into json data + OBSDataAutoRelease subResponseData = OBSRemoteProtocol::rpcResponseToJsonData(subResponse); + + obs_data_array_push_back(results, subResponseData); + + // if told to abort on fail and a failure occurs, stop request processing and return the progress + if (abortOnFail && (subResponse.status() == RpcResponse::Status::Error)) + break; + } + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_array(response, "results", results); + return request.success(response); +} + +/** + * Waits for the specified duration. Designed to be used in `ExecuteBatch` operations. + * + * @param {int} `sleepMillis` Delay in milliseconds to wait before continuing. + * + * @api requests + * @name Sleep + * @category general + * @since unreleased + */ +RpcResponse WSRequestHandler::Sleep(const RpcRequest& request) { + if (!request.hasField("sleepMillis")) { + return request.failed("missing request parameters"); + } + + long long sleepMillis = obs_data_get_int(request.parameters(), "sleepMillis"); + std::this_thread::sleep_for(std::chrono::milliseconds(sleepMillis)); + + return request.success(); +} diff --git a/src/WSRequestHandler_MediaControl.cpp b/src/WSRequestHandler_MediaControl.cpp new file mode 100644 index 00000000..20df9681 --- /dev/null +++ b/src/WSRequestHandler_MediaControl.cpp @@ -0,0 +1,405 @@ +#include "Utils.h" + +#include "WSRequestHandler.h" + +bool isMediaSource(const QString& sourceKind) +{ + return (sourceKind == "vlc_source" || sourceKind == "ffmpeg_source"); +} + +QString getSourceMediaState(obs_source_t *source) +{ + QString mediaState; + enum obs_media_state mstate = obs_source_media_get_state(source); + switch (mstate) { + case OBS_MEDIA_STATE_NONE: + mediaState = "none"; + break; + case OBS_MEDIA_STATE_PLAYING: + mediaState = "playing"; + break; + case OBS_MEDIA_STATE_OPENING: + mediaState = "opening"; + break; + case OBS_MEDIA_STATE_BUFFERING: + mediaState = "buffering"; + break; + case OBS_MEDIA_STATE_PAUSED: + mediaState = "paused"; + break; + case OBS_MEDIA_STATE_STOPPED: + mediaState = "stopped"; + break; + case OBS_MEDIA_STATE_ENDED: + mediaState = "ended"; + break; + case OBS_MEDIA_STATE_ERROR: + mediaState = "error"; + break; + default: + mediaState = "unknown"; + } + return mediaState; +} + +/** +* Pause or play a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +* Note :Leaving out `playPause` toggles the current pause state +* +* @param {String} `sourceName` Source name. +* @param {boolean} `playPause` (optional) Whether to pause or play the source. `false` for play, `true` for pause. +* +* @api requests +* @name PlayPauseMedia +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::PlayPauseMedia(const RpcRequest& request) { + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + bool playPause = obs_data_get_bool(request.parameters(), "playPause"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + if (!request.hasField("playPause")) { + if (obs_source_media_get_state(source) == obs_media_state::OBS_MEDIA_STATE_PLAYING) { + obs_source_media_play_pause(source, true); + } else { + obs_source_media_play_pause(source, false); + } + } else { + bool playPause = obs_data_get_bool(request.parameters(), "playPause"); + obs_source_media_play_pause(source, playPause); + } + return request.success(); +} + +/** +* Restart a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +* +* @param {String} `sourceName` Source name. +* +* @api requests +* @name RestartMedia +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::RestartMedia(const RpcRequest& request) { + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + obs_source_media_restart(source); + return request.success(); +} + +/** +* Stop a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +* +* @param {String} `sourceName` Source name. +* +* @api requests +* @name StopMedia +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::StopMedia(const RpcRequest& request) { + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + obs_source_media_stop(source); + return request.success(); +} + +/** +* Skip to the next media item in the playlist. Supports only vlc media source (as of OBS v25.0.8) +* +* @param {String} `sourceName` Source name. +* +* @api requests +* @name NextMedia +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::NextMedia(const RpcRequest& request) { + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + obs_source_media_next(source); + return request.success(); +} + +/** +* Go to the previous media item in the playlist. Supports only vlc media source (as of OBS v25.0.8) +* +* @param {String} `sourceName` Source name. +* +* @api requests +* @name PreviousMedia +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::PreviousMedia(const RpcRequest& request) { + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + obs_source_media_previous(source); + return request.success(); +} + +/** +* Get the length of media in milliseconds. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +* Note: For some reason, for the first 5 or so seconds that the media is playing, the total duration can be off by upwards of 50ms. +* +* @param {String} `sourceName` Source name. +* +* @return {int} `mediaDuration` The total length of media in milliseconds.. +* +* @api requests +* @name GetMediaDuration +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::GetMediaDuration(const RpcRequest& request) { + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_int(response, "mediaDuration", obs_source_media_get_duration(source)); + return request.success(response); +} + +/** +* Get the current timestamp of media in milliseconds. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +* +* @param {String} `sourceName` Source name. +* +* @return {int} `timestamp` The time in milliseconds since the start of the media. +* +* @api requests +* @name GetMediaTime +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::GetMediaTime(const RpcRequest& request) { + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_int(response, "timestamp", obs_source_media_get_time(source)); + return request.success(response); +} + +/** +* Set the timestamp of a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +* +* @param {String} `sourceName` Source name. +* @param {int} `timestamp` Milliseconds to set the timestamp to. +* +* @api requests +* @name SetMediaTime +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::SetMediaTime(const RpcRequest& request) { + if (!request.hasField("sourceName") || !request.hasField("timestamp")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + int64_t timestamp = (int64_t)obs_data_get_int(request.parameters(), "timestamp"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + obs_source_media_set_time(source, timestamp); + return request.success(); +} + +/** +* Scrub media using a supplied offset. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +* Note: Due to processing/network delays, this request is not perfect. The processing rate of this request has also not been tested. +* +* @param {String} `sourceName` Source name. +* @param {int} `timeOffset` Millisecond offset (positive or negative) to offset the current media position. +* +* @api requests +* @name ScrubMedia +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::ScrubMedia(const RpcRequest& request) { + if (!request.hasField("sourceName") || !request.hasField("timeOffset")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + int64_t timeOffset = (int64_t)obs_data_get_int(request.parameters(), "timeOffset"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + int64_t newTime = obs_source_media_get_time(source) + timeOffset; + if (newTime < 0) { + newTime = 0; + } + + obs_source_media_set_time(source, newTime); + return request.success(); +} + +/** +* Get the current playing state of a media source. Supports ffmpeg and vlc media sources (as of OBS v25.0.8) +* +* @param {String} `sourceName` Source name. +* +* @return {String} `mediaState` The media state of the provided source. States: `none`, `playing`, `opening`, `buffering`, `paused`, `stopped`, `ended`, `error`, `unknown` +* +* @api requests +* @name GetMediaState +* @category media control +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::GetMediaState(const RpcRequest& request) { + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_string(response, "mediaState", getSourceMediaState(source).toUtf8()); + + return request.success(response); +} + +/** +* List the media state of all media sources (vlc and media source) +* +* @return {Array} `mediaSources` Array of sources +* @return {String} `mediaSources.*.sourceName` Unique source name +* @return {String} `mediaSources.*.sourceKind` Unique source internal type (a.k.a `ffmpeg_source` or `vlc_source`) +* @return {String} `mediaSources.*.mediaState` The current state of media for that source. States: `none`, `playing`, `opening`, `buffering`, `paused`, `stopped`, `ended`, `error`, `unknown` +* +* @api requests +* @name GetMediaSourcesList +* @category sources +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::GetMediaSourcesList(const RpcRequest& request) +{ + OBSDataArrayAutoRelease sourcesArray = obs_data_array_create(); + + auto sourceEnumProc = [](void* privateData, obs_source_t* source) -> bool { + obs_data_array_t* sourcesArray = (obs_data_array_t*)privateData; + + QString sourceKind = obs_source_get_id(source); + if (isMediaSource(sourceKind)) { + OBSDataAutoRelease sourceData = obs_data_create(); + obs_data_set_string(sourceData, "sourceName", obs_source_get_name(source)); + obs_data_set_string(sourceData, "sourceKind", sourceKind.toUtf8()); + + QString mediaState = getSourceMediaState(source); + obs_data_set_string(sourceData, "mediaState", mediaState.toUtf8()); + + obs_data_array_push_back(sourcesArray, sourceData); + } + return true; + }; + obs_enum_sources(sourceEnumProc, sourcesArray); + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_array(response, "mediaSources", sourcesArray); + return request.success(response); +} diff --git a/src/WSRequestHandler_Outputs.cpp b/src/WSRequestHandler_Outputs.cpp index dad0bf19..d87a8ca8 100644 --- a/src/WSRequestHandler_Outputs.cpp +++ b/src/WSRequestHandler_Outputs.cpp @@ -15,7 +15,7 @@ * @property {boolean} `flags.encoded` Output is encoded * @property {boolean} `flags.multiTrack` Output uses several audio tracks * @property {boolean} `flags.service` Output uses a service -* @property {Object} `settings` Output name +* @property {Object} `settings` Output settings * @property {boolean} `active` Output status (active or not) * @property {boolean} `reconnecting` Output reconnection status (reconnecting or not) * @property {double} `congestion` Output congestion @@ -59,7 +59,7 @@ obs_data_t* getOutputInfo(obs_output_t* output) return data; } -RpcResponse findOutputOrFail(const RpcRequest& request, std::function callback) +RpcResponse findOutputOrFail(const RpcRequest& request, std::function callback) { if (!request.hasField("outputName")) { return request.failed("missing request parameters"); @@ -71,7 +71,7 @@ RpcResponse findOutputOrFail(const RpcRequest& request, std::function(param)); + }, (void*)profileName, true); + return request.success(); } /** * Get the name of the current profile. - * + * * @return {String} `profile-name` Name of the currently active profile. * * @api requests @@ -49,6 +58,7 @@ RpcResponse WSRequestHandler::GetCurrentProfile(const RpcRequest& request) { * Get a list of available profiles. * * @return {Array} `profiles` List of available profiles. + * @return {String} `profiles.*.profile-name` Filter name * * @api requests * @name ListProfiles diff --git a/src/WSRequestHandler_Recording.cpp b/src/WSRequestHandler_Recording.cpp index df2ed4a0..e00e4555 100644 --- a/src/WSRequestHandler_Recording.cpp +++ b/src/WSRequestHandler_Recording.cpp @@ -1,8 +1,10 @@ +#include "obs-websocket.h" #include "WSRequestHandler.h" #include #include #include "Utils.h" +#include "WSEvents.h" RpcResponse ifCanPause(const RpcRequest& request, std::function callback) { @@ -13,8 +15,37 @@ RpcResponse ifCanPause(const RpcRequest& request, std::function c return callback(); } + /** + * Get current recording status. + * + * @return {boolean} `isRecording` Current recording status. + * @return {boolean} `isRecordingPaused` Whether the recording is paused or not. + * @return {String (optional)} `recordTimecode` Time elapsed since recording started (only present if currently recording). + * @return {String (optional)} `recordingFilename` Absolute path to the recording file (only present if currently recording). + * + * @api requests + * @name GetRecordingStatus + * @category recording + * @since 4.9.0 + */ +RpcResponse WSRequestHandler::GetRecordingStatus(const RpcRequest& request) { + auto events = GetEventsSystem(); + + OBSDataAutoRelease data = obs_data_create(); + obs_data_set_bool(data, "isRecording", obs_frontend_recording_active()); + obs_data_set_bool(data, "isRecordingPaused", obs_frontend_recording_paused()); + + if (obs_frontend_recording_active()) { + QString recordingTimecode = events->getRecordingTimecode(); + obs_data_set_string(data, "recordTimecode", recordingTimecode.toUtf8().constData()); + obs_data_set_string(data, "recordingFilename", Utils::GetCurrentRecordingFilename()); + } + + return request.success(data); +} + /** - * Toggle recording on or off. + * Toggle recording on or off (depending on the current recording state). * * @api requests * @name StartStopRecording @@ -106,7 +137,7 @@ RpcResponse WSRequestHandler::ResumeRecording(const RpcRequest& request) { * In the current profile, sets the recording folder of the Simple and Advanced * output modes to the specified value. * - * Please note: if `SetRecordingFolder` is called while a recording is + * Note: If `SetRecordingFolder` is called while a recording is * in progress, the change won't be applied immediately and will be * effective on the next recording. * diff --git a/src/WSRequestHandler_ReplayBuffer.cpp b/src/WSRequestHandler_ReplayBuffer.cpp index 510b8f05..035a41f1 100644 --- a/src/WSRequestHandler_ReplayBuffer.cpp +++ b/src/WSRequestHandler_ReplayBuffer.cpp @@ -1,9 +1,29 @@ +#include "obs-websocket.h" +#include "WSEvents.h" #include "Utils.h" #include "WSRequestHandler.h" + + /** + * Get the status of the OBS replay buffer. + * + * @return {boolean} `isReplayBufferActive` Current recording status. + * + * @api requests + * @name GetReplayBufferStatus + * @category replay buffer + * @since 4.9.0 + */ +RpcResponse WSRequestHandler::GetReplayBufferStatus(const RpcRequest& request) { + OBSDataAutoRelease data = obs_data_create(); + obs_data_set_bool(data, "isReplayBufferActive", obs_frontend_replay_buffer_active()); + + return request.success(data); +} + /** -* Toggle the Replay Buffer on/off. +* Toggle the Replay Buffer on/off (depending on the current state of the replay buffer). * * @api requests * @name StartStopReplayBuffer diff --git a/src/WSRequestHandler_SceneCollections.cpp b/src/WSRequestHandler_SceneCollections.cpp index 1f87996b..8fc0ef42 100644 --- a/src/WSRequestHandler_SceneCollections.cpp +++ b/src/WSRequestHandler_SceneCollections.cpp @@ -2,6 +2,11 @@ #include "WSRequestHandler.h" +/** +* @typedef {Object} `ScenesCollection` +* @property {String} `sc-name` Name of the scene collection +*/ + /** * Change the active scene collection. * @@ -17,13 +22,22 @@ RpcResponse WSRequestHandler::SetCurrentSceneCollection(const RpcRequest& reques return request.failed("missing request parameters"); } - QString sceneCollection = obs_data_get_string(request.parameters(), "sc-name"); - if (sceneCollection.isEmpty()) { + const char* sceneCollection = obs_data_get_string(request.parameters(), "sc-name"); + if (!sceneCollection) { return request.failed("invalid request parameters"); } - // TODO : Check if specified profile exists and if changing is allowed - obs_frontend_set_current_scene_collection(sceneCollection.toUtf8()); + char** collections = obs_frontend_get_scene_collections(); + bool collectionExists = Utils::StringInStringList(collections, sceneCollection); + bfree(collections); + if (!collectionExists) { + return request.failed("scene collection does not exist"); + } + + obs_queue_task(OBS_TASK_UI, [](void* param) { + obs_frontend_set_current_scene_collection(reinterpret_cast(param)); + }, (void*)sceneCollection, true); + return request.success(); } @@ -50,7 +64,7 @@ RpcResponse WSRequestHandler::GetCurrentSceneCollection(const RpcRequest& reques /** * List available scene collections * - * @return {Array} `scene-collections` Scene collections list + * @return {Array} `scene-collections` Scene collections list * * @api requests * @name ListSceneCollections diff --git a/src/WSRequestHandler_SceneItems.cpp b/src/WSRequestHandler_SceneItems.cpp index 5e736802..7c6f967c 100644 --- a/src/WSRequestHandler_SceneItems.cpp +++ b/src/WSRequestHandler_SceneItems.cpp @@ -2,20 +2,96 @@ #include "WSRequestHandler.h" +/** +* Get a list of all scene items in a scene. +* +* @param {String (optional)} `sceneName` Name of the scene to get the list of scene items from. Defaults to the current scene if not specified. +* +* @return {String} `sceneName` Name of the requested (or current) scene +* @return {Array} `sceneItems` Array of scene items +* @return {int} `sceneItems.*.itemId` Unique item id of the source item +* @return {String} `sceneItems.*.sourceKind` ID if the scene item's source. For example `vlc_source` or `image_source` +* @return {String} `sceneItems.*.sourceName` Name of the scene item's source +* @return {String} `sceneItems.*.sourceType` Type of the scene item's source. Either `input`, `group`, or `scene` +* +* @api requests +* @name GetSceneItemList +* @category scene items +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::GetSceneItemList(const RpcRequest& request) { + const char* sceneName = obs_data_get_string(request.parameters(), "sceneName"); + + OBSSourceAutoRelease sceneSource; + if (sceneName && strcmp(sceneName, "") != 0) { + sceneSource = obs_get_source_by_name(sceneName); + } else { + sceneSource = obs_frontend_get_current_scene(); + } + + OBSScene scene = obs_scene_from_source(sceneSource); + if (!scene) { + return request.failed("requested scene is invalid or doesnt exist"); + } + + OBSDataArrayAutoRelease sceneItemArray = obs_data_array_create(); + + auto sceneItemEnumProc = [](obs_scene_t *, obs_sceneitem_t* item, void* privateData) -> bool { + obs_data_array_t* sceneItemArray = (obs_data_array_t*)privateData; + + OBSDataAutoRelease sceneItemData = obs_data_create(); + obs_data_set_int(sceneItemData, "itemId", obs_sceneitem_get_id(item)); + OBSSource source = obs_sceneitem_get_source(item); + obs_data_set_string(sceneItemData, "sourceKind", obs_source_get_id(source)); + obs_data_set_string(sceneItemData, "sourceName", obs_source_get_name(source)); + + QString typeString = ""; + enum obs_source_type sourceType = obs_source_get_type(source); + switch (sourceType) { + case OBS_SOURCE_TYPE_INPUT: + typeString = "input"; + break; + + case OBS_SOURCE_TYPE_SCENE: + typeString = "scene"; + break; + + default: + typeString = "unknown"; + break; + } + obs_data_set_string(sceneItemData, "sourceType", typeString.toUtf8()); + + obs_data_array_push_back(sceneItemArray, sceneItemData); + return true; + }; + obs_scene_enum_items(scene, sceneItemEnumProc, sceneItemArray); + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_string(response, "sceneName", obs_source_get_name(sceneSource)); + obs_data_set_array(response, "sceneItems", sceneItemArray); + + return request.success(response); +} + /** * Gets the scene specific properties of the specified source item. * Coordinates are relative to the item's parent (the scene or group it belongs to). * -* @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene. -* @param {String} `item` The name of the source. +* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene. +* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object). +* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object) +* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object) * -* @return {String} `name` The name of the source. -* @return {int} `position.x` The x position of the source from the left. -* @return {int} `position.y` The y position of the source from the top. -* @return {int} `position.alignment` The point on the source that the item is manipulated from. +* @return {String} `name` Scene Item name. +* @return {int} `itemId` Scene Item ID. +* @return {double} `position.x` The x position of the source from the left. +* @return {double} `position.y` The y position of the source from the top. +* @return {int} `position.alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis. * @return {double} `rotation` The clockwise rotation of the item in degrees around the point of alignment. * @return {double} `scale.x` The x-scale factor of the source. * @return {double} `scale.y` The y-scale factor of the source. +* @return {String} `scale.filter` The scale filter of the source. Can be "OBS_SCALE_DISABLE", "OBS_SCALE_POINT", "OBS_SCALE_BICUBIC", "OBS_SCALE_BILINEAR", "OBS_SCALE_LANCZOS" or "OBS_SCALE_AREA". * @return {int} `crop.top` The number of pixels cropped off the top of the source before scaling. * @return {int} `crop.right` The number of pixels cropped off the right of the source before scaling. * @return {int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling. @@ -31,10 +107,9 @@ * @return {int} `sourceHeight` Base source (without scaling) of the source * @return {double} `width` Scene item width (base source width multiplied by the horizontal scaling factor) * @return {double} `height` Scene item height (base source height multiplied by the vertical scaling factor) -* @return {int} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis. * @return {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group) * @return {Array (optional)} `groupChildren` List of children (if this item is a group) -* +* * @api requests * @name GetSceneItemProperties * @category scene items @@ -45,24 +120,25 @@ RpcResponse WSRequestHandler::GetSceneItemProperties(const RpcRequest& request) return request.failed("missing request parameters"); } - QString itemName = obs_data_get_string(request.parameters(), "item"); - if (itemName.isEmpty()) { - return request.failed("invalid request parameters"); - } + OBSData params = request.parameters(); - QString sceneName = obs_data_get_string(request.parameters(), "scene-name"); + QString sceneName = obs_data_get_string(params, "scene-name"); OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName); if (!scene) { return request.failed("requested scene doesn't exist"); } - OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName); + OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item"); + OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField); if (!sceneItem) { return request.failed("specified scene item doesn't exist"); } OBSDataAutoRelease data = Utils::GetSceneItemPropertiesData(sceneItem); - obs_data_set_string(data, "name", itemName.toUtf8()); + + OBSSource sceneItemSource = obs_sceneitem_get_source(sceneItem); + obs_data_set_string(data, "name", obs_source_get_name(sceneItemSource)); + obs_data_set_int(data, "itemId", obs_sceneitem_get_id(sceneItem)); return request.success(data); } @@ -71,14 +147,17 @@ RpcResponse WSRequestHandler::GetSceneItemProperties(const RpcRequest& request) * Sets the scene specific properties of a source. Unspecified properties will remain unchanged. * Coordinates are relative to the item's parent (the scene or group it belongs to). * -* @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene. -* @param {String} `item` The name of the source. -* @param {int (optional)} `position.x` The new x position of the source. -* @param {int (optional)} `position.y` The new y position of the source. +* @param {String (optional)} `scene-name` Name of the scene the source item belongs to. Defaults to the current scene. +* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object). +* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object) +* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object) +* @param {double (optional)} `position.x` The new x position of the source. +* @param {double (optional)} `position.y` The new y position of the source. * @param {int (optional)} `position.alignment` The new alignment of the source. * @param {double (optional)} `rotation` The new clockwise rotation of the item in degrees. * @param {double (optional)} `scale.x` The new x scale of the item. * @param {double (optional)} `scale.y` The new y scale of the item. +* @param {String (optional)} `scale.filter` The new scale filter of the source. Can be "OBS_SCALE_DISABLE", "OBS_SCALE_POINT", "OBS_SCALE_BICUBIC", "OBS_SCALE_BILINEAR", "OBS_SCALE_LANCZOS" or "OBS_SCALE_AREA". * @param {int (optional)} `crop.top` The new amount of pixels cropped off the top of the source before scaling. * @param {int (optional)} `crop.bottom` The new amount of pixels cropped off the bottom of the source before scaling. * @param {int (optional)} `crop.left` The new amount of pixels cropped off the left of the source before scaling. @@ -100,19 +179,16 @@ RpcResponse WSRequestHandler::SetSceneItemProperties(const RpcRequest& request) return request.failed("missing request parameters"); } - QString itemName = obs_data_get_string(request.parameters(), "item"); - if (itemName.isEmpty()) { - return request.failed("invalid request parameters"); - } + OBSData params = request.parameters(); - QString sceneName = obs_data_get_string(request.parameters(), "scene-name"); + QString sceneName = obs_data_get_string(params, "scene-name"); OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName); if (!scene) { return request.failed("requested scene doesn't exist"); } - OBSSceneItemAutoRelease sceneItem = - Utils::GetSceneItemFromName(scene, itemName); + OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item"); + OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField); if (!sceneItem) { return request.failed("specified scene item doesn't exist"); } @@ -126,51 +202,81 @@ RpcResponse WSRequestHandler::SetSceneItemProperties(const RpcRequest& request) vec2 oldPosition; OBSDataAutoRelease positionError = obs_data_create(); obs_sceneitem_get_pos(sceneItem, &oldPosition); - OBSDataAutoRelease reqPosition = obs_data_get_obj(request.parameters(), "position"); + + OBSDataAutoRelease reqPosition = obs_data_get_obj(params, "position"); vec2 newPosition = oldPosition; + if (obs_data_has_user_value(reqPosition, "x")) { - newPosition.x = obs_data_get_int(reqPosition, "x"); + newPosition.x = obs_data_get_double(reqPosition, "x"); } if (obs_data_has_user_value(reqPosition, "y")) { - newPosition.y = obs_data_get_int(reqPosition, "y"); + newPosition.y = obs_data_get_double(reqPosition, "y"); } + if (obs_data_has_user_value(reqPosition, "alignment")) { const uint32_t alignment = obs_data_get_int(reqPosition, "alignment"); if (Utils::IsValidAlignment(alignment)) { obs_sceneitem_set_alignment(sceneItem, alignment); - } - else { + } else { badRequest = true; obs_data_set_string(positionError, "alignment", "invalid"); obs_data_set_obj(errorData, "position", positionError); } } + obs_sceneitem_set_pos(sceneItem, &newPosition); } if (request.hasField("rotation")) { - obs_sceneitem_set_rot(sceneItem, (float)obs_data_get_double(request.parameters(), "rotation")); + obs_sceneitem_set_rot(sceneItem, (float)obs_data_get_double(params, "rotation")); } if (request.hasField("scale")) { + OBSDataAutoRelease reqScale = obs_data_get_obj(params, "scale"); + + if (obs_data_has_user_value(reqScale, "filter")) { + QString newScaleFilter = obs_data_get_string(reqScale, "filter"); + if (newScaleFilter == "OBS_SCALE_DISABLE") { + obs_sceneitem_set_scale_filter(sceneItem, OBS_SCALE_DISABLE); + } + else if (newScaleFilter == "OBS_SCALE_POINT") { + obs_sceneitem_set_scale_filter(sceneItem, OBS_SCALE_POINT); + } + else if (newScaleFilter == "OBS_SCALE_BICUBIC") { + obs_sceneitem_set_scale_filter(sceneItem, OBS_SCALE_BICUBIC); + } + else if (newScaleFilter == "OBS_SCALE_BILINEAR") { + obs_sceneitem_set_scale_filter(sceneItem, OBS_SCALE_BICUBIC); + } + else if (newScaleFilter == "OBS_SCALE_LANCZOS") { + obs_sceneitem_set_scale_filter(sceneItem, OBS_SCALE_LANCZOS); + } + else if (newScaleFilter == "OBS_SCALE_AREA") { + obs_sceneitem_set_scale_filter(sceneItem, OBS_SCALE_AREA); + } + } + vec2 oldScale; obs_sceneitem_get_scale(sceneItem, &oldScale); - OBSDataAutoRelease reqScale = obs_data_get_obj(request.parameters(), "scale"); vec2 newScale = oldScale; + if (obs_data_has_user_value(reqScale, "x")) { newScale.x = obs_data_get_double(reqScale, "x"); } if (obs_data_has_user_value(reqScale, "y")) { newScale.y = obs_data_get_double(reqScale, "y"); } + obs_sceneitem_set_scale(sceneItem, &newScale); } if (request.hasField("crop")) { obs_sceneitem_crop oldCrop; obs_sceneitem_get_crop(sceneItem, &oldCrop); - OBSDataAutoRelease reqCrop = obs_data_get_obj(request.parameters(), "crop"); + + OBSDataAutoRelease reqCrop = obs_data_get_obj(params, "crop"); obs_sceneitem_crop newCrop = oldCrop; + if (obs_data_has_user_value(reqCrop, "top")) { newCrop.top = obs_data_get_int(reqCrop, "top"); } @@ -183,21 +289,23 @@ RpcResponse WSRequestHandler::SetSceneItemProperties(const RpcRequest& request) if (obs_data_has_user_value(reqCrop, "left")) { newCrop.left = obs_data_get_int(reqCrop, "left"); } + obs_sceneitem_set_crop(sceneItem, &newCrop); } if (request.hasField("visible")) { - obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(request.parameters(), "visible")); + obs_sceneitem_set_visible(sceneItem, obs_data_get_bool(params, "visible")); } if (request.hasField("locked")) { - obs_sceneitem_set_locked(sceneItem, obs_data_get_bool(request.parameters(), "locked")); + obs_sceneitem_set_locked(sceneItem, obs_data_get_bool(params, "locked")); } if (request.hasField("bounds")) { bool badBounds = false; OBSDataAutoRelease boundsError = obs_data_create(); - OBSDataAutoRelease reqBounds = obs_data_get_obj(request.parameters(), "bounds"); + OBSDataAutoRelease reqBounds = obs_data_get_obj(params, "bounds"); + if (obs_data_has_user_value(reqBounds, "type")) { QString newBoundsType = obs_data_get_string(reqBounds, "type"); if (newBoundsType == "OBS_BOUNDS_NONE") { @@ -226,16 +334,20 @@ RpcResponse WSRequestHandler::SetSceneItemProperties(const RpcRequest& request) obs_data_set_string(boundsError, "type", "invalid"); } } + vec2 oldBounds; obs_sceneitem_get_bounds(sceneItem, &oldBounds); vec2 newBounds = oldBounds; + if (obs_data_has_user_value(reqBounds, "x")) { newBounds.x = obs_data_get_double(reqBounds, "x"); } if (obs_data_has_user_value(reqBounds, "y")) { newBounds.y = obs_data_get_double(reqBounds, "y"); } + obs_sceneitem_set_bounds(sceneItem, &newBounds); + if (obs_data_has_user_value(reqBounds, "alignment")) { const uint32_t bounds_alignment = obs_data_get_int(reqBounds, "alignment"); if (Utils::IsValidAlignment(bounds_alignment)) { @@ -246,6 +358,7 @@ RpcResponse WSRequestHandler::SetSceneItemProperties(const RpcRequest& request) obs_data_set_string(boundsError, "alignment", "invalid"); } } + if (badBounds) { obs_data_set_obj(errorData, "bounds", boundsError); } @@ -263,8 +376,10 @@ RpcResponse WSRequestHandler::SetSceneItemProperties(const RpcRequest& request) /** * Reset a scene item. * -* @param {String (optional)} `scene-name` Name of the scene the source belongs to. Defaults to the current scene. -* @param {String} `item` Name of the source item. +* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene. +* @param {String | Object} `item` Scene Item name (if this field is a string) or specification (if it is an object). +* @param {String (optional)} `item.name` Scene Item name (if the `item` field is an object) +* @param {int (optional)} `item.id` Scene Item ID (if the `item` field is an object) * * @api requests * @name ResetSceneItem @@ -272,24 +387,20 @@ RpcResponse WSRequestHandler::SetSceneItemProperties(const RpcRequest& request) * @since 4.2.0 */ RpcResponse WSRequestHandler::ResetSceneItem(const RpcRequest& request) { - // TODO: remove this request, or refactor it to ResetSource - if (!request.hasField("item")) { return request.failed("missing request parameters"); } - const char* itemName = obs_data_get_string(request.parameters(), "item"); - if (!itemName) { - return request.failed("invalid request parameters"); - } + OBSData params = request.parameters(); - const char* sceneName = obs_data_get_string(request.parameters(), "scene-name"); + const char* sceneName = obs_data_get_string(params, "scene-name"); OBSScene scene = Utils::GetSceneFromNameOrCurrent(sceneName); if (!scene) { return request.failed("requested scene doesn't exist"); } - OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromName(scene, itemName); + OBSDataItemAutoRelease itemField = obs_data_item_byname(params, "item"); + OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField); if (!sceneItem) { return request.failed("specified scene item doesn't exist"); } @@ -305,27 +416,28 @@ RpcResponse WSRequestHandler::ResetSceneItem(const RpcRequest& request) { /** * Show or hide a specified source item in a specified scene. * -* @param {String} `source` Scene item name in the specified scene. +* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the currently active scene. +* @param {String (optional)} `source` Scene Item name. +* @param {int (optional)} `item` Scene Item id * @param {boolean} `render` true = shown ; false = hidden -* @param {String (optional)} `scene-name` Name of the scene where the source resides. Defaults to the currently active scene. * * @api requests * @name SetSceneItemRender * @category scene items * @since 0.3 -* @deprecated Since 4.3.0. Prefer the use of SetSceneItemProperties. */ RpcResponse WSRequestHandler::SetSceneItemRender(const RpcRequest& request) { - if (!request.hasField("source") || - !request.hasField("render")) + bool doesntHaveSourceOrItemParameter = !(request.hasField("source") || request.hasField("item")); + if (!request.hasField("render") || doesntHaveSourceOrItemParameter) { return request.failed("missing request parameters"); } const char* itemName = obs_data_get_string(request.parameters(), "source"); + int64_t itemId = obs_data_get_int(request.parameters(), "item"); bool isVisible = obs_data_get_bool(request.parameters(), "render"); - if (!itemName) { + if (!itemName && !itemId) { return request.failed("invalid request parameters"); } @@ -335,12 +447,19 @@ RpcResponse WSRequestHandler::SetSceneItemRender(const RpcRequest& request) { return request.failed("requested scene doesn't exist"); } - OBSSceneItemAutoRelease sceneItem = - Utils::GetSceneItemFromName(scene, itemName); - if (!sceneItem) { - return request.failed("specified scene item doesn't exist"); - } + OBSSceneItemAutoRelease sceneItem; + if (strlen(itemName)) { + sceneItem = Utils::GetSceneItemFromName(scene, itemName); + if (!sceneItem) { + return request.failed("specified scene item name doesn't exist"); + } + } else { + sceneItem = Utils::GetSceneItemFromId(scene, itemId); + if (!sceneItem) { + return request.failed("specified scene item ID doesn't exist"); + } + } obs_sceneitem_set_visible(sceneItem, isVisible); return request.success(); } @@ -348,8 +467,8 @@ RpcResponse WSRequestHandler::SetSceneItemRender(const RpcRequest& request) { /** * Sets the coordinates of a specified source item. * -* @param {String (optional)} `scene-name` The name of the scene that the source item belongs to. Defaults to the current scene. -* @param {String} `item` The name of the source item. +* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene. +* @param {String} `item` Scene Item name. * @param {double} `x` X coordinate. * @param {double} `y` Y coordinate. @@ -393,8 +512,8 @@ RpcResponse WSRequestHandler::SetSceneItemPosition(const RpcRequest& request) { /** * Set the transform of the specified source item. * -* @param {String (optional)} `scene-name` The name of the scene that the source item belongs to. Defaults to the current scene. -* @param {String} `item` The name of the source item. +* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene. +* @param {String} `item` Scene Item name. * @param {double} `x-scale` Width scale factor. * @param {double} `y-scale` Height scale factor. * @param {double} `rotation` Source item rotation (in degrees). @@ -439,7 +558,7 @@ RpcResponse WSRequestHandler::SetSceneItemTransform(const RpcRequest& request) { obs_sceneitem_set_scale(sceneItem, &scale); obs_sceneitem_set_rot(sceneItem, rotation); - + obs_sceneitem_defer_update_end(sceneItem); return request.success(); @@ -448,8 +567,8 @@ RpcResponse WSRequestHandler::SetSceneItemTransform(const RpcRequest& request) { /** * Sets the crop coordinates of the specified source item. * -* @param {String (optional)} `scene-name` the name of the scene that the source item belongs to. Defaults to the current scene. -* @param {String} `item` The name of the source. +* @param {String (optional)} `scene-name` Name of the scene the scene item belongs to. Defaults to the current scene. +* @param {String} `item` Scene Item name. * @param {int} `top` Pixel position of the top of the source item. * @param {int} `bottom` Pixel position of the bottom of the source item. * @param {int} `left` Pixel position of the left of the source item. @@ -496,10 +615,10 @@ RpcResponse WSRequestHandler::SetSceneItemCrop(const RpcRequest& request) { /** * Deletes a scene item. * - * @param {String (optional)} `scene` Name of the scene the source belongs to. Defaults to the current scene. - * @param {Object} `item` item to delete (required) - * @param {String} `item.name` name of the scene item (prefer `id`, including both is acceptable). - * @param {int} `item.id` id of the scene item. + * @param {String (optional)} `scene` Name of the scene the scene item belongs to. Defaults to the current scene. + * @param {Object} `item` Scene item to delete (required) + * @param {String} `item.name` Scene Item name (prefer `id`, including both is acceptable). + * @param {int} `item.id` Scene Item ID. * * @api requests * @name DeleteSceneItem @@ -517,8 +636,8 @@ RpcResponse WSRequestHandler::DeleteSceneItem(const RpcRequest& request) { return request.failed("requested scene doesn't exist"); } - OBSDataAutoRelease item = obs_data_get_obj(request.parameters(), "item"); - OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item); + OBSDataItemAutoRelease itemField = obs_data_item_byname(request.parameters(), "item"); + OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromRequestField(scene, itemField); if (!sceneItem) { return request.failed("item with id/name combination not found in specified scene"); } @@ -528,14 +647,67 @@ RpcResponse WSRequestHandler::DeleteSceneItem(const RpcRequest& request) { return request.success(); } +/** + * Creates a scene item in a scene. In other words, this is how you add a source into a scene. + * + * @param {String} `sceneName` Name of the scene to create the scene item in + * @param {String} `sourceName` Name of the source to be added + * @param {boolean (optional)} `setVisible` Whether to make the sceneitem visible on creation or not. Default `true` + * + * @return {int} `itemId` Numerical ID of the created scene item + * + * @api requests + * @name AddSceneItem + * @category scene items + * @since 4.9.0 + */ +RpcResponse WSRequestHandler::AddSceneItem(const RpcRequest& request) { + if (!request.hasField("sceneName") || !request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + const char* sceneName = obs_data_get_string(request.parameters(), "sceneName"); + OBSSourceAutoRelease sceneSource = obs_get_source_by_name(sceneName); + OBSScene scene = obs_scene_from_source(sceneSource); + if (!scene) { + return request.failed("requested scene is invalid or doesnt exist"); + } + + const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); + if (!source) { + return request.failed("requested source does not exist"); + } + + if (source == sceneSource) { + return request.failed("you cannot add a scene as a sceneitem to itself"); + } + + Utils::AddSourceData data; + data.source = source; + data.setVisible = true; + if (request.hasField("setVisible")) { + data.setVisible = obs_data_get_bool(request.parameters(), "setVisible"); + } + + obs_enter_graphics(); + obs_scene_atomic_update(scene, Utils::AddSourceHelper, &data); + obs_leave_graphics(); + + OBSDataAutoRelease responseData = obs_data_create(); + obs_data_set_int(responseData, "itemId", obs_sceneitem_get_id(data.sceneItem)); + + return request.success(responseData); +} + /** * Duplicates a scene item. * * @param {String (optional)} `fromScene` Name of the scene to copy the item from. Defaults to the current scene. * @param {String (optional)} `toScene` Name of the scene to create the item in. Defaults to the current scene. - * @param {Object} `item` item to duplicate (required) - * @param {String} `item.name` name of the scene item (prefer `id`, including both is acceptable). - * @param {int} `item.id` id of the scene item. + * @param {Object} `item` Scene Item to duplicate from the source scene (required) + * @param {String} `item.name` Scene Item name (prefer `id`, including both is acceptable). + * @param {int} `item.id` Scene Item ID. * * @return {String} `scene` Name of the scene where the new item was created * @return {Object} `item` New item info @@ -570,8 +742,8 @@ RpcResponse WSRequestHandler::DuplicateSceneItem(const RpcRequest& request) { return request.failed("requested toScene doesn't exist"); } - OBSDataAutoRelease item = obs_data_get_obj(request.parameters(), "item"); - OBSSceneItemAutoRelease referenceItem = Utils::GetSceneItemFromItem(fromScene, item); + OBSDataItemAutoRelease itemField = obs_data_item_byname(request.parameters(), "item"); + OBSSceneItemAutoRelease referenceItem = Utils::GetSceneItemFromRequestField(fromScene, itemField); if (!referenceItem) { return request.failed("item with id/name combination not found in specified scene"); } diff --git a/src/WSRequestHandler_Scenes.cpp b/src/WSRequestHandler_Scenes.cpp index 9399ac5d..9fd837a4 100644 --- a/src/WSRequestHandler_Scenes.cpp +++ b/src/WSRequestHandler_Scenes.cpp @@ -36,7 +36,7 @@ RpcResponse WSRequestHandler::SetCurrentScene(const RpcRequest& request) { /** * Get the current scene's name and source items. - * + * * @return {String} `name` Name of the currently active scene. * @return {Array} `sources` Ordered list of the current scene's source items. * @@ -58,9 +58,9 @@ RpcResponse WSRequestHandler::GetCurrentScene(const RpcRequest& request) { /** * Get a list of scenes in the currently active profile. - * + * * @return {String} `current-scene` Name of the currently active scene. - * @return {Array} `scenes` Ordered list of the current profile's scenes (See `[GetCurrentScene](#getcurrentscene)` for more information). + * @return {Array} `scenes` Ordered list of the current profile's scenes (See [GetCurrentScene](#getcurrentscene) for more information). * * @api requests * @name GetSceneList @@ -79,13 +79,38 @@ RpcResponse WSRequestHandler::GetSceneList(const RpcRequest& request) { return request.success(data); } +/** + * Create a new scene scene. + * + * @param {String} `sceneName` Name of the scene to create. + * + * @api requests + * @name CreateScene + * @category scenes + * @since 4.9.0 + */ +RpcResponse WSRequestHandler::CreateScene(const RpcRequest& request) { + if (!request.hasField("sceneName")) { + return request.failed("missing request parameters"); + } + + const char* sceneName = obs_data_get_string(request.parameters(), "sceneName"); + OBSSourceAutoRelease source = obs_get_source_by_name(sceneName); + + if (source) { + return request.failed("scene with this name already exists"); + } + obs_scene_create(sceneName); + return request.success(); +} + /** * Changes the order of scene items in the requested scene. * * @param {String (optional)} `scene` Name of the scene to reorder (defaults to current). * @param {Array} `items` Ordered list of objects with name and/or id specified. Id preferred due to uniqueness per scene -* @param {int (optional)} `items[].id` Id of a specific scene item. Unique on a scene by scene basis. -* @param {String (optional)} `items[].name` Name of a scene item. Sufficiently unique if no scene items share sources within the scene. +* @param {int (optional)} `items.*.id` Id of a specific scene item. Unique on a scene by scene basis. +* @param {String (optional)} `items.*.name` Name of a scene item. Sufficiently unique if no scene items share sources within the scene. * * @api requests * @name ReorderSceneItems @@ -121,7 +146,7 @@ RpcResponse WSRequestHandler::ReorderSceneItems(const RpcRequest& request) { struct obs_sceneitem_order_info info; size_t itemCount = obs_data_array_count(ctx->items); - for (int i = 0; i < itemCount; i++) { + for (uint i = 0; i < itemCount; i++) { OBSDataAutoRelease item = obs_data_array_item(ctx->items, i); OBSSceneItemAutoRelease sceneItem = Utils::GetSceneItemFromItem(scene, item); @@ -148,3 +173,128 @@ RpcResponse WSRequestHandler::ReorderSceneItems(const RpcRequest& request) { return request.success(); } + +/** + * Set a scene to use a specific transition override. + * + * @param {String} `sceneName` Name of the scene to switch to. + * @param {String} `transitionName` Name of the transition to use. + * @param {int (Optional)} `transitionDuration` Duration in milliseconds of the transition if transition is not fixed. Defaults to the current duration specified in the UI if there is no current override and this value is not given. + * + * @api requests + * @name SetSceneTransitionOverride + * @category scenes + * @since 4.8.0 + */ +RpcResponse WSRequestHandler::SetSceneTransitionOverride(const RpcRequest& request) { + if (!request.hasField("sceneName") || !request.hasField("transitionName")) { + return request.failed("missing request parameters"); + } + + QString sceneName = obs_data_get_string(request.parameters(), "sceneName"); + OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8()); + if (!source) { + return request.failed("requested scene does not exist"); + } + + enum obs_source_type sourceType = obs_source_get_type(source); + if (sourceType != OBS_SOURCE_TYPE_SCENE) { + return request.failed("requested scene is invalid"); + } + + QString transitionName = obs_data_get_string(request.parameters(), "transitionName"); + if (!Utils::GetTransitionFromName(transitionName)) { + return request.failed("requested transition does not exist"); + } + + OBSDataAutoRelease sourceData = obs_source_get_private_settings(source); + obs_data_set_string(sourceData, "transition", transitionName.toUtf8().constData()); + + if (request.hasField("transitionDuration")) { + int transitionOverrideDuration = obs_data_get_int(request.parameters(), "transitionDuration"); + obs_data_set_int(sourceData, "transition_duration", transitionOverrideDuration); + } else if(!obs_data_has_user_value(sourceData, "transition_duration")) { + obs_data_set_int(sourceData, "transition_duration", + obs_frontend_get_transition_duration() + ); + } + + return request.success(); +} + +/** + * Remove any transition override on a scene. + * + * @param {String} `sceneName` Name of the scene to switch to. + * + * @api requests + * @name RemoveSceneTransitionOverride + * @category scenes + * @since 4.8.0 + */ +RpcResponse WSRequestHandler::RemoveSceneTransitionOverride(const RpcRequest& request) { + if (!request.hasField("sceneName")) { + return request.failed("missing request parameters"); + } + + QString sceneName = obs_data_get_string(request.parameters(), "sceneName"); + OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8()); + if (!source) { + return request.failed("requested scene does not exist"); + } + + enum obs_source_type sourceType = obs_source_get_type(source); + if (sourceType != OBS_SOURCE_TYPE_SCENE) { + return request.failed("requested scene is invalid"); + } + + OBSDataAutoRelease sourceData = obs_source_get_private_settings(source); + obs_data_erase(sourceData, "transition"); + obs_data_erase(sourceData, "transition_duration"); + + return request.success(); +} + +/** + * Get the current scene transition override. + * + * @param {String} `sceneName` Name of the scene to switch to. + * + * @return {String} `transitionName` Name of the current overriding transition. Empty string if no override is set. + * @return {int} `transitionDuration` Transition duration. `-1` if no override is set. + * + * @api requests + * @name GetSceneTransitionOverride + * @category scenes + * @since 4.8.0 + */ +RpcResponse WSRequestHandler::GetSceneTransitionOverride(const RpcRequest& request) { + if (!request.hasField("sceneName")) { + return request.failed("missing request parameters"); + } + + QString sceneName = obs_data_get_string(request.parameters(), "sceneName"); + OBSSourceAutoRelease source = obs_get_source_by_name(sceneName.toUtf8()); + if (!source) { + return request.failed("requested scene does not exist"); + } + + enum obs_source_type sourceType = obs_source_get_type(source); + if (sourceType != OBS_SOURCE_TYPE_SCENE) { + return request.failed("requested scene is invalid"); + } + + OBSDataAutoRelease sourceData = obs_source_get_private_settings(source); + const char* transitionOverrideName = obs_data_get_string(sourceData, "transition"); + + bool hasDurationOverride = obs_data_has_user_value(sourceData, "transition_duration"); + int transitionOverrideDuration = obs_data_get_int(sourceData, "transition_duration"); + + OBSDataAutoRelease fields = obs_data_create(); + obs_data_set_string(fields, "transitionName", transitionOverrideName); + obs_data_set_int(fields, "transitionDuration", + (hasDurationOverride ? transitionOverrideDuration : -1) + ); + + return request.success(fields); +} diff --git a/src/WSRequestHandler_Sources.cpp b/src/WSRequestHandler_Sources.cpp index e043ee0e..c0085884 100644 --- a/src/WSRequestHandler_Sources.cpp +++ b/src/WSRequestHandler_Sources.cpp @@ -8,12 +8,91 @@ #include "WSRequestHandler.h" +bool isTextGDIPlusSource(const QString& sourceKind) +{ + return (sourceKind == "text_gdiplus" || sourceKind == "text_gdiplus_v2"); +} + +bool isTextFreeType2Source(const QString& sourceKind) +{ + return (sourceKind == "text_ft2_source" || sourceKind == "text_ft2_source_v2"); +} + +/** + * Create a source and add it as a sceneitem to a scene. + * + * @param {String} `sourceName` Source name. + * @param {String} `sourceKind` Source kind, Eg. `vlc_source`. + * @param {String} `sceneName` Scene to add the new source to. + * @param {Object (optional)} `sourceSettings` Source settings data. + * @param {boolean (optional)} `setVisible` Set the created SceneItem as visible or not. Defaults to true + * + * @return {int} `itemId` ID of the SceneItem in the scene. + * + * @api requests + * @name CreateSource + * @category sources + * @since 4.9.0 + */ +RpcResponse WSRequestHandler::CreateSource(const RpcRequest& request) +{ + if (!request.hasField("sourceName") || !request.hasField("sourceKind") || !request.hasField("sceneName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + QString sourceKind = obs_data_get_string(request.parameters(), "sourceKind"); + if (sourceName.isEmpty() || sourceKind.isEmpty()) { + return request.failed("empty sourceKind or sourceName parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (source) { + return request.failed("a source with that name already exists"); + } + + const char* sceneName = obs_data_get_string(request.parameters(), "sceneName"); + OBSSourceAutoRelease sceneSource = obs_get_source_by_name(sceneName); + OBSScene scene = obs_scene_from_source(sceneSource); + if (!scene) { + return request.failed("requested scene is invalid or doesnt exist"); + } + + OBSDataAutoRelease sourceSettings = nullptr; + if (request.hasField("sourceSettings")) { + sourceSettings = obs_data_get_obj(request.parameters(), "sourceSettings"); + } + + OBSSourceAutoRelease newSource = obs_source_create(sourceKind.toUtf8(), sourceName.toUtf8(), sourceSettings, nullptr); + + if (!newSource) { + return request.failed("failed to create the source"); + } + obs_source_set_enabled(newSource, true); + + Utils::AddSourceData data; + data.source = newSource; + data.setVisible = true; + if (request.hasField("setVisible")) { + data.setVisible = obs_data_get_bool(request.parameters(), "setVisible"); + } + + obs_enter_graphics(); + obs_scene_atomic_update(scene, Utils::AddSourceHelper, &data); + obs_leave_graphics(); + + OBSDataAutoRelease responseData = obs_data_create(); + obs_data_set_int(responseData, "itemId", obs_sceneitem_get_id(data.sceneItem)); + + return request.success(responseData); +} + /** * List all sources available in the running OBS instance * * @return {Array} `sources` Array of sources * @return {String} `sources.*.name` Unique source name -* @return {String} `sources.*.typeId` Non-unique source internal type (a.k.a type id) +* @return {String} `sources.*.typeId` Non-unique source internal type (a.k.a kind) * @return {String} `sources.*.type` Source type. Value is one of the following: "input", "filter", "transition", "scene" or "unknown" * * @api requests @@ -146,12 +225,13 @@ RpcResponse WSRequestHandler::GetSourceTypesList(const RpcRequest& request) } /** -* Get the volume of the specified source. +* Get the volume of the specified source. Default response uses mul format, NOT SLIDER PERCENTAGE. * * @param {String} `source` Source name. +* @param {boolean (optional)} `useDecibel` Output volume in decibels of attenuation instead of amplitude/mul. * * @return {String} `name` Source name. -* @return {double} `volume` Volume of the source. Between `0.0` and `1.0`. +* @return {double} `volume` Volume of the source. Between `0.0` and `20.0` if using mul, under `26.0` if using dB. * @return {boolean} `muted` Indicates whether the source is muted. * * @api requests @@ -175,35 +255,50 @@ RpcResponse WSRequestHandler::GetVolume(const RpcRequest& request) return request.failed("specified source doesn't exist"); } + float volume = obs_source_get_volume(source); + + bool useDecibel = obs_data_get_bool(request.parameters(), "useDecibel"); + if (useDecibel) { + volume = obs_mul_to_db(volume); + } + + if (volume == -INFINITY) { + volume = -100.0; + } + OBSDataAutoRelease response = obs_data_create(); obs_data_set_string(response, "name", obs_source_get_name(source)); - obs_data_set_double(response, "volume", obs_source_get_volume(source)); + obs_data_set_double(response, "volume", volume); obs_data_set_bool(response, "muted", obs_source_muted(source)); - return request.success(response); } /** - * Set the volume of the specified source. - * - * @param {String} `source` Source name. - * @param {double} `volume` Desired volume. Must be between `0.0` and `1.0`. - * - * @api requests - * @name SetVolume - * @category sources - * @since 4.0.0 - */ +* Set the volume of the specified source. Default request format uses mul, NOT SLIDER PERCENTAGE. +* +* @param {String} `source` Source name. +* @param {double} `volume` Desired volume. Must be between `0.0` and `20.0` for mul, and under 26.0 for dB. OBS will interpret dB values under -100.0 as Inf. Note: The OBS volume sliders only reach a maximum of 1.0mul/0.0dB, however OBS actually supports larger values. +* @param {boolean (optional)} `useDecibel` Interperet `volume` data as decibels instead of amplitude/mul. +* +* @api requests +* @name SetVolume +* @category sources +* @since 4.0.0 +*/ RpcResponse WSRequestHandler::SetVolume(const RpcRequest& request) { if (!request.hasField("source") || !request.hasField("volume")) { return request.failed("missing request parameters"); } + bool useDecibel = obs_data_get_bool(request.parameters(), "useDecibel"); + QString sourceName = obs_data_get_string(request.parameters(), "source"); float sourceVolume = obs_data_get_double(request.parameters(), "volume"); - if (sourceName.isEmpty() || sourceVolume < 0.0 || sourceVolume > 1.0) { + bool isNotValidDecibel = (useDecibel && sourceVolume > 26.0); + bool isNotValidMul = (!useDecibel && (sourceVolume < 0.0 || sourceVolume > 20.0)); + if (sourceName.isEmpty() || isNotValidDecibel || isNotValidMul) { return request.failed("invalid request parameters"); } @@ -212,10 +307,104 @@ RpcResponse WSRequestHandler::SetVolume(const RpcRequest& request) return request.failed("specified source doesn't exist"); } + if (useDecibel) { + sourceVolume = obs_db_to_mul(sourceVolume); + } obs_source_set_volume(source, sourceVolume); + return request.success(); } +/** +* Changes whether an audio track is active for a source. +* +* @param {String} `sourceName` Source name. +* @param {int} `track` Audio tracks 1-6. +* @param {boolean} `active` Whether audio track is active or not. +* +* @api requests +* @name SetTracks +* @category sources +* @since unreleased +*/ +RpcResponse WSRequestHandler::SetAudioTracks(const RpcRequest& request) + { + if (!request.hasField("sourceName") || !request.hasField("track") || !request.hasField("active")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + bool active = obs_data_get_bool(request.parameters(), "active"); + int track = obs_data_get_int(request.parameters(), "track")-1; + + if (sourceName.isEmpty() || track > 5 || track < 0) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + uint32_t mixers = obs_source_get_audio_mixers(source); + + if (active && !(mixers & (1 << track))) + mixers |= (1 << track); + else if (mixers & (1 << track)) + mixers &= ~(1 << track); + + obs_source_set_audio_mixers(source, mixers); + + return request.success(); +} + + +/** +* Gets whether an audio track is active for a source. +* +* @param {String} `sourceName` Source name. +* +* @return {boolean} `track1` +* @return {boolean} `track2` +* @return {boolean} `track3` +* @return {boolean} `track4` +* @return {boolean} `track5` +* @return {boolean} `track6` +* +* @api requests +* @name GetTracks +* @category sources +* @since unreleased +*/ +RpcResponse WSRequestHandler::GetAudioTracks(const RpcRequest& request) +{ + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + uint32_t mixers = obs_source_get_audio_mixers(source); + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_string(response, "name", obs_source_get_name(source)); + obs_data_set_bool(response, "track1", mixers & (1 << 0)); + obs_data_set_bool(response, "track2", mixers & (1 << 1)); + obs_data_set_bool(response, "track3", mixers & (1 << 2)); + obs_data_set_bool(response, "track4", mixers & (1 << 3)); + obs_data_set_bool(response, "track5", mixers & (1 << 4)); + obs_data_set_bool(response, "track6", mixers & (1 << 5)); + return request.success(response); +} + /** * Get the mute status of a specified source. * @@ -315,6 +504,114 @@ RpcResponse WSRequestHandler::ToggleMute(const RpcRequest& request) return request.success(); } +/** +* Get the source's active status of a specified source (if it is showing in the final mix). +* +* @param {String} `sourceName` Source name. +* +* @return {boolean} `sourceActive` Source active status of the source. +* +* @api requests +* @name GetSourceActive +* @category sources +* @since unreleased +*/ +RpcResponse WSRequestHandler::GetSourceActive(const RpcRequest& request) +{ + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_bool(response, "sourceActive", obs_source_active(source)); + + return request.success(response); +} + +/** +* Get the audio's active status of a specified source. +* +* @param {String} `sourceName` Source name. +* +* @return {boolean} `audioActive` Audio active status of the source. +* +* @api requests +* @name GetAudioActive +* @category sources +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::GetAudioActive(const RpcRequest& request) +{ + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_bool(response, "audioActive", obs_source_audio_active(source)); + + return request.success(response); +} + +/** +* Sets (aka rename) the name of a source. Also works with scenes since scenes are technically sources in OBS. +* +* Note: If the new name already exists as a source, obs-websocket will return an error. +* +* @param {String} `sourceName` Source name. +* @param {String} `newName` New source name. +* +* @api requests +* @name SetSourceName +* @category sources +* @since 4.8.0 +*/ +RpcResponse WSRequestHandler::SetSourceName(const RpcRequest& request) +{ + if (!request.hasField("sourceName") || !request.hasField("newName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + QString newName = obs_data_get_string(request.parameters(), "newName"); + if (sourceName.isEmpty() || newName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + OBSSourceAutoRelease existingSource = obs_get_source_by_name(newName.toUtf8()); + if (!existingSource) { // OBS is supposed to automatically rename colliding source names, but it doesn't. So this gets to be the solution for now. + obs_source_set_name(source, newName.toUtf8()); + + return request.success(); + } else { + return request.failed("a source with that name already exists"); + } +} + /** * Set the audio sync offset of a specified source. * @@ -407,6 +704,7 @@ RpcResponse WSRequestHandler::GetSourceSettings(const RpcRequest& request) const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); + if (!source) { return request.failed("specified source doesn't exist"); } @@ -467,21 +765,17 @@ RpcResponse WSRequestHandler::SetSourceSettings(const RpcRequest& request) } } - OBSDataAutoRelease currentSettings = obs_source_get_settings(source); OBSDataAutoRelease newSettings = obs_data_get_obj(request.parameters(), "sourceSettings"); - OBSDataAutoRelease sourceSettings = obs_data_create(); - obs_data_apply(sourceSettings, currentSettings); - obs_data_apply(sourceSettings, newSettings); - - obs_source_update(source, sourceSettings); + obs_source_update(source, newSettings); obs_source_update_properties(source); + OBSDataAutoRelease updatedSettings = obs_source_get_settings(source); + OBSDataAutoRelease response = obs_data_create(); obs_data_set_string(response, "sourceName", obs_source_get_name(source)); obs_data_set_string(response, "sourceType", obs_source_get_id(source)); - obs_data_set_obj(response, "sourceSettings", sourceSettings); - + obs_data_set_obj(response, "sourceSettings", updatedSettings); return request.success(response); } @@ -492,8 +786,8 @@ RpcResponse WSRequestHandler::SetSourceSettings(const RpcRequest& request) * * @return {String} `source` Source name. * @return {String} `align` Text Alignment ("left", "center", "right"). - * @return {int} `bk-color` Background color. - * @return {int} `bk-opacity` Background opacity (0-100). + * @return {int} `bk_color` Background color. + * @return {int} `bk_opacity` Background opacity (0-100). * @return {boolean} `chatlog` Chat log. * @return {int} `chatlog_lines` Chat log lines. * @return {int} `color` Text color. @@ -536,8 +830,8 @@ RpcResponse WSRequestHandler::GetTextGDIPlusProperties(const RpcRequest& request return request.failed("specified source doesn't exist"); } - QString sourceId = obs_source_get_id(source); - if (sourceId != "text_gdiplus") { + QString sourceKind = obs_source_get_id(source); + if (!isTextGDIPlusSource(sourceKind)) { return request.failed("not a text gdi plus source"); } @@ -552,8 +846,8 @@ RpcResponse WSRequestHandler::GetTextGDIPlusProperties(const RpcRequest& request * * @param {String} `source` Name of the source. * @param {String (optional)} `align` Text Alignment ("left", "center", "right"). - * @param {int (optional)} `bk-color` Background color. - * @param {int (optional)} `bk-opacity` Background opacity (0-100). + * @param {int (optional)} `bk_color` Background color. + * @param {int (optional)} `bk_opacity` Background opacity (0-100). * @param {boolean (optional)} `chatlog` Chat log. * @param {int (optional)} `chatlog_lines` Chat log lines. * @param {int (optional)} `color` Text color. @@ -601,8 +895,8 @@ RpcResponse WSRequestHandler::SetTextGDIPlusProperties(const RpcRequest& request return request.failed("specified source doesn't exist"); } - QString sourceId = obs_source_get_id(source); - if (sourceId != "text_gdiplus") { + QString sourceKind = obs_source_get_id(source); + if (!isTextGDIPlusSource(sourceKind)) { return request.failed("not a text gdi plus source"); } @@ -767,8 +1061,8 @@ RpcResponse WSRequestHandler::GetTextFreetype2Properties(const RpcRequest& reque return request.failed("specified source doesn't exist"); } - QString sourceId = obs_source_get_id(source); - if (sourceId != "text_ft2_source") { + QString sourceKind = obs_source_get_id(source); + if (!isTextFreeType2Source(sourceKind)) { return request.failed("not a freetype 2 source"); } @@ -805,18 +1099,18 @@ RpcResponse WSRequestHandler::GetTextFreetype2Properties(const RpcRequest& reque */ RpcResponse WSRequestHandler::SetTextFreetype2Properties(const RpcRequest& request) { - const char* sourceName = obs_data_get_string(request.parameters(), "source"); - if (!sourceName) { + const char* sourceName = obs_data_get_string(request.parameters(), "source"); + if (!sourceName) { return request.failed("invalid request parameters"); - } + } OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); if (!source) { return request.failed("specified source doesn't exist"); } - QString sourceId = obs_source_get_id(source); - if (sourceId != "text_ft2_source") { + QString sourceKind = obs_source_get_id(source); + if (!isTextFreeType2Source(sourceKind)) { return request.failed("not text freetype 2 source"); } @@ -909,6 +1203,7 @@ RpcResponse WSRequestHandler::SetTextFreetype2Properties(const RpcRequest& reque * @name GetBrowserSourceProperties * @category sources * @since 4.1.0 + * @deprecated Since 4.8.0. Prefer the use of GetSourceSettings. Will be removed in v5.0.0 */ RpcResponse WSRequestHandler::GetBrowserSourceProperties(const RpcRequest& request) { @@ -950,6 +1245,7 @@ RpcResponse WSRequestHandler::GetBrowserSourceProperties(const RpcRequest& reque * @api requests * @name SetBrowserSourceProperties * @category sources + * @deprecated Since 4.8.0. Prefer the use of SetSourceSettings. Will be removed in v5.0.0 * @since 4.1.0 */ RpcResponse WSRequestHandler::SetBrowserSourceProperties(const RpcRequest& request) @@ -1414,19 +1710,151 @@ RpcResponse WSRequestHandler::SetSourceFilterVisibility(const RpcRequest& reques return request.success(); } +/** +* Get the audio monitoring type of the specified source. +* +* @param {String} `sourceName` Source name. +* +* @return {String} `monitorType` The monitor type in use. Options: `none`, `monitorOnly`, `monitorAndOutput`. +* +* @api requests +* @name GetAudioMonitorType +* @category sources +* @since 4.8.0 +*/ +RpcResponse WSRequestHandler::GetAudioMonitorType(const RpcRequest& request) + { + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + + if (sourceName.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + OBSDataAutoRelease response = obs_data_create(); + + QString monitorType; + enum obs_monitoring_type mtype = obs_source_get_monitoring_type(source); + switch (mtype) { + case OBS_MONITORING_TYPE_NONE: + monitorType = "none"; + break; + case OBS_MONITORING_TYPE_MONITOR_ONLY: + monitorType = "monitorOnly"; + break; + case OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT: + monitorType = "monitorAndOutput"; + break; + default: + monitorType = "unknown"; + break; + } + obs_data_set_string(response, "monitorType", monitorType.toUtf8()); + + return request.success(response); +} + +/** +* Set the audio monitoring type of the specified source. +* +* @param {String} `sourceName` Source name. +* @param {String} `monitorType` The monitor type to use. Options: `none`, `monitorOnly`, `monitorAndOutput`. +* +* @api requests +* @name SetAudioMonitorType +* @category sources +* @since 4.8.0 +*/ +RpcResponse WSRequestHandler::SetAudioMonitorType(const RpcRequest& request) + { + if (!request.hasField("sourceName") || !request.hasField("monitorType")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + QString monitorType = obs_data_get_string(request.parameters(), "monitorType"); + + if (sourceName.isEmpty() || monitorType.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + if (monitorType == "none") { + obs_source_set_monitoring_type(source, OBS_MONITORING_TYPE_NONE); + } else if (monitorType == "monitorOnly") { + obs_source_set_monitoring_type(source, OBS_MONITORING_TYPE_MONITOR_ONLY); + } else if (monitorType == "monitorAndOutput") { + obs_source_set_monitoring_type(source, OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT); + } else { + return request.failed("invalid monitorType"); + } + return request.success(); +} + +/** +* Get the default settings for a given source type. +* +* @param {String} `sourceKind` Source kind. Also called "source id" in libobs terminology. +* +* @return {String} `sourceKind` Source kind. Same value as the `sourceKind` parameter. +* @return {Object} `defaultSettings` Settings object for source. +* +* @api requests +* @name GetSourceDefaultSettings +* @category sources +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::GetSourceDefaultSettings(const RpcRequest& request) +{ + if (!request.hasField("sourceKind")) { + return request.failed("missing request parameters"); + } + + QString sourceKind = obs_data_get_string(request.parameters(), "sourceKind"); + if (sourceKind.isEmpty()) { + return request.failed("invalid request parameters"); + } + + OBSDataAutoRelease defaultData = obs_get_source_defaults(sourceKind.toUtf8()); + if (!defaultData) { + return request.failed("invalid sourceKind"); + } + + OBSDataAutoRelease defaultSettings = Utils::OBSDataGetDefaults(defaultData); + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_string(response, "sourceKind", sourceKind.toUtf8().constData()); + obs_data_set_obj(response, "defaultSettings", defaultSettings); + return request.success(response); +} + /** * Takes a picture snapshot of a source and then can either or both: -* - Send it over as a Data URI (base64-encoded data) in the response (by specifying `embedPictureFormat` in the request) -* - Save it to disk (by specifying `saveToFilePath` in the request) +* - Send it over as a Data URI (base64-encoded data) in the response (by specifying `embedPictureFormat` in the request) +* - Save it to disk (by specifying `saveToFilePath` in the request) * * At least `embedPictureFormat` or `saveToFilePath` must be specified. * * Clients can specify `width` and `height` parameters to receive scaled pictures. Aspect ratio is * preserved if only one of these two parameters is specified. * -* @param {String} `sourceName` Source name. Note that, since scenes are also sources, you can also provide a scene name. +* @param {String (optional)} `sourceName` Source name. Note: Since scenes are also sources, you can also provide a scene name. If not provided, the currently active scene is used. * @param {String (optional)} `embedPictureFormat` Format of the Data URI encoded picture. Can be "png", "jpg", "jpeg" or "bmp" (or any other value supported by Qt's Image module) * @param {String (optional)} `saveToFilePath` Full file path (file extension included) where the captured image is to be saved. Can be in a format different from `pictureFormat`. Can be a relative path. +* @param {String (optional)} `fileFormat` Format to save the image file as (one of the values provided in the `supported-image-export-formats` response field of `GetVersion`). If not specified, tries to guess based on file extension. +* @param {int (optional)} `compressionQuality` Compression ratio between -1 and 100 to write the image with. -1 is automatic, 1 is smallest file/most compression, 100 is largest file/least compression. Varies with image type. * @param {int (optional)} `width` Screenshot width. Defaults to the source's base width. * @param {int (optional)} `height` Screenshot height. Defaults to the source's base height. * @@ -1440,18 +1868,19 @@ RpcResponse WSRequestHandler::SetSourceFilterVisibility(const RpcRequest& reques * @since 4.6.0 */ RpcResponse WSRequestHandler::TakeSourceScreenshot(const RpcRequest& request) { - if (!request.hasField("sourceName")) { - return request.failed("missing request parameters"); - } - if (!request.hasField("embedPictureFormat") && !request.hasField("saveToFilePath")) { return request.failed("At least 'embedPictureFormat' or 'saveToFilePath' must be specified"); } - const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); - OBSSourceAutoRelease source = obs_get_source_by_name(sourceName); - if (!source) { - return request.failed("specified source doesn't exist");; + OBSSourceAutoRelease source; + if (!request.hasField("sourceName")) { + source = obs_frontend_get_current_scene(); + } else { + const char* sourceName = obs_data_get_string(request.parameters(), "sourceName"); + source = obs_get_source_by_name(sourceName); + if (!source) { + return request.failed("specified source doesn't exist");; + } } const uint32_t sourceWidth = obs_source_get_base_width(source); @@ -1510,7 +1939,7 @@ RpcResponse WSRequestHandler::TakeSourceScreenshot(const RpcRequest& request) { gs_stage_texture(stagesurface, gs_texrender_get_texture(texrender)); if (gs_stagesurface_map(stagesurface, &videoData, &videoLinesize)) { int linesize = sourceImage.bytesPerLine(); - for (int y = 0; y < imgHeight; y++) { + for (uint y = 0; y < imgHeight; y++) { memcpy(sourceImage.scanLine(y), videoData + (y * videoLinesize), linesize); } gs_stagesurface_unmap(stagesurface); @@ -1529,20 +1958,30 @@ RpcResponse WSRequestHandler::TakeSourceScreenshot(const RpcRequest& request) { OBSDataAutoRelease response = obs_data_create(); + int compressionQuality {-1}; + if (request.hasField("compressionQuality")) { + compressionQuality = obs_data_get_int(request.parameters(), "compressionQuality"); + + if (compressionQuality < -1 || compressionQuality > 100) { + QString errorMessage = QString("compression quality out of range: %1").arg(compressionQuality); + return request.failed(errorMessage.toUtf8()); + } + } + if (request.hasField("embedPictureFormat")) { const char* pictureFormat = obs_data_get_string(request.parameters(), "embedPictureFormat"); QByteArrayList supportedFormats = QImageWriter::supportedImageFormats(); if (!supportedFormats.contains(pictureFormat)) { - QString errorMessage = QString("Unsupported picture format: %1").arg(pictureFormat); + QString errorMessage = QString("unsupported picture format: %1").arg(pictureFormat); return request.failed(errorMessage.toUtf8()); } QByteArray encodedImgBytes; QBuffer buffer(&encodedImgBytes); buffer.open(QBuffer::WriteOnly); - if (!sourceImage.save(&buffer, pictureFormat)) { - return request.failed("Embed image encoding failed"); + if (!sourceImage.save(&buffer, pictureFormat, compressionQuality)) { + return request.failed("embed image encoding failed"); } buffer.close(); @@ -1559,7 +1998,18 @@ RpcResponse WSRequestHandler::TakeSourceScreenshot(const RpcRequest& request) { QFileInfo filePathInfo(filePathStr); QString absoluteFilePath = filePathInfo.absoluteFilePath(); - if (!sourceImage.save(absoluteFilePath)) { + const char* fileFormat = nullptr; + if (request.hasField("fileFormat")) { + fileFormat = obs_data_get_string(request.parameters(), "fileFormat"); + QByteArrayList supportedFormats = QImageWriter::supportedImageFormats(); + + if (!supportedFormats.contains(fileFormat)) { + QString errorMessage = QString("unsupported file format: %1").arg(fileFormat); + return request.failed(errorMessage.toUtf8()); + } + } + + if (!sourceImage.save(absoluteFilePath, fileFormat, compressionQuality)) { return request.failed("Image save failed"); } obs_data_set_string(response, "imageFile", absoluteFilePath.toUtf8()); @@ -1568,3 +2018,38 @@ RpcResponse WSRequestHandler::TakeSourceScreenshot(const RpcRequest& request) { obs_data_set_string(response, "sourceName", obs_source_get_name(source)); return request.success(response); } + +/** +* Refreshes the specified browser source. +* +* @param {String} `sourceName` Source name. +* +* @api requests +* @name RefreshBrowserSource +* @category sources +* @since 4.9.0 +*/ +RpcResponse WSRequestHandler::RefreshBrowserSource(const RpcRequest& request) +{ + if (!request.hasField("sourceName")) { + return request.failed("missing request parameters"); + } + + QString sourceName = obs_data_get_string(request.parameters(), "sourceName"); + + OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.toUtf8()); + if (!source) { + return request.failed("specified source doesn't exist"); + } + + if (strcmp(obs_source_get_id(source), "browser_source") != 0) { + return request.failed("specified source is not a browser"); + } + + obs_properties_t *sourceProperties = obs_source_properties(source); + obs_property_t *property = obs_properties_get(sourceProperties, "refreshnocache"); + obs_property_button_clicked(property, source); // This returns a boolean but we ignore it because the browser plugin always returns `false`. + obs_properties_destroy(sourceProperties); + + return request.success(); +} diff --git a/src/WSRequestHandler_Streaming.cpp b/src/WSRequestHandler_Streaming.cpp index 744a691f..56e9a001 100644 --- a/src/WSRequestHandler_Streaming.cpp +++ b/src/WSRequestHandler_Streaming.cpp @@ -11,9 +11,10 @@ * * @return {boolean} `streaming` Current streaming status. * @return {boolean} `recording` Current recording status. + * @return {boolean} `recording-paused` If recording is paused. + * @return {boolean} `preview-only` Always false. Retrocompatibility with OBSRemote. * @return {String (optional)} `stream-timecode` Time elapsed since streaming started (only present if currently streaming). * @return {String (optional)} `rec-timecode` Time elapsed since recording started (only present if currently recording). - * @return {boolean} `preview-only` Always false. Retrocompatibility with OBSRemote. * * @api requests * @name GetStreamingStatus @@ -43,7 +44,7 @@ RpcResponse WSRequestHandler::GetStreamingStatus(const RpcRequest& request) { } /** - * Toggle streaming on or off. + * Toggle streaming on or off (depending on the current stream state). * * @api requests * @name StartStopStreaming @@ -61,7 +62,7 @@ RpcResponse WSRequestHandler::StartStopStreaming(const RpcRequest& request) { * Start streaming. * Will return an `error` if streaming is already active. * - * @param {Object (optional)} `stream` Special stream configuration. Please note: these won't be saved to OBS' configuration. + * @param {Object (optional)} `stream` Special stream configuration. Note: these won't be saved to OBS' configuration. * @param {String (optional)} `stream.type` If specified ensures the type of stream matches the given type (usually 'rtmp_custom' or 'rtmp_common'). If the currently configured stream type does not match the given stream type, all settings must be specified in the `settings` object or an error will occur when starting the stream. * @param {Object (optional)} `stream.metadata` Adds the given object parameters as encoded query string parameters to the 'key' of the RTMP stream. Used to pass data to the RTMP service about the streaming. May be any String, Numeric, or Boolean field. * @param {Object (optional)} `stream.settings` Settings for the stream. @@ -103,10 +104,10 @@ RpcResponse WSRequestHandler::StartStreaming(const RpcRequest& request) { && obs_data_has_user_value(newSettings, "key")) { const char* key = obs_data_get_string(newSettings, "key"); - int keylen = strlen(key); + size_t keylen = strlen(key); bool hasQuestionMark = false; - for (int i = 0; i < keylen; i++) { + for (size_t i = 0; i < keylen; i++) { if (key[i] == '?') { hasQuestionMark = true; break; @@ -292,7 +293,6 @@ RpcResponse WSRequestHandler::SaveStreamSettings(const RpcRequest& request) { /** * Send the provided text as embedded CEA-608 caption data. - * As of OBS Studio 23.1, captions are not yet available on Linux. * * @param {String} `text` Captions text * @@ -301,7 +301,6 @@ RpcResponse WSRequestHandler::SaveStreamSettings(const RpcRequest& request) { * @category streaming * @since 4.6.0 */ -#if BUILD_CAPTIONS RpcResponse WSRequestHandler::SendCaptions(const RpcRequest& request) { if (!request.hasField("text")) { return request.failed("missing request parameters"); @@ -316,5 +315,4 @@ RpcResponse WSRequestHandler::SendCaptions(const RpcRequest& request) { return request.success(); } -#endif diff --git a/src/WSRequestHandler_StudioMode.cpp b/src/WSRequestHandler_StudioMode.cpp index fb958f3b..c5e33a70 100644 --- a/src/WSRequestHandler_StudioMode.cpp +++ b/src/WSRequestHandler_StudioMode.cpp @@ -133,6 +133,9 @@ RpcResponse WSRequestHandler::TransitionToProgram(const RpcRequest& request) { * @since 4.1.0 */ RpcResponse WSRequestHandler::EnableStudioMode(const RpcRequest& request) { + if (obs_frontend_preview_program_mode_active()) { + return request.failed("studio mode already active"); + } obs_queue_task(OBS_TASK_UI, [](void* param) { obs_frontend_set_preview_program_mode(true); @@ -150,6 +153,9 @@ RpcResponse WSRequestHandler::EnableStudioMode(const RpcRequest& request) { * @since 4.1.0 */ RpcResponse WSRequestHandler::DisableStudioMode(const RpcRequest& request) { + if (!obs_frontend_preview_program_mode_active()) { + return request.failed("studio mode not active"); + } obs_queue_task(OBS_TASK_UI, [](void* param) { obs_frontend_set_preview_program_mode(false); @@ -160,7 +166,7 @@ RpcResponse WSRequestHandler::DisableStudioMode(const RpcRequest& request) { } /** - * Toggles Studio Mode. + * Toggles Studio Mode (depending on the current state of studio mode). * * @api requests * @name ToggleStudioMode @@ -176,4 +182,4 @@ RpcResponse WSRequestHandler::ToggleStudioMode(const RpcRequest& request) { }, nullptr, true); return request.success(); -} +} \ No newline at end of file diff --git a/src/WSRequestHandler_Transitions.cpp b/src/WSRequestHandler_Transitions.cpp index 7c22687c..c9daa0eb 100644 --- a/src/WSRequestHandler_Transitions.cpp +++ b/src/WSRequestHandler_Transitions.cpp @@ -81,7 +81,7 @@ RpcResponse WSRequestHandler::SetCurrentTransition(const RpcRequest& request) { if (!success) { return request.failed("requested transition does not exist"); } - + return request.success(); } @@ -120,3 +120,155 @@ RpcResponse WSRequestHandler::GetTransitionDuration(const RpcRequest& request) { obs_data_set_int(response, "transition-duration", obs_frontend_get_transition_duration()); return request.success(response); } + +/** + * Get the position of the current transition. + * + * @return {double} `position` current transition position. This value will be between 0.0 and 1.0. Note: Transition returns 1.0 when not active. + * + * @api requests + * @name GetTransitionPosition + * @category transitions + * @since 4.9.0 + */ +RpcResponse WSRequestHandler::GetTransitionPosition(const RpcRequest& request) { + OBSSourceAutoRelease currentTransition = obs_frontend_get_current_transition(); + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_double(response, "position", obs_transition_get_time(currentTransition)); + + return request.success(response); +} + +/** + * Get the current settings of a transition + * + * @param {String} `transitionName` Transition name + * + * @return {Object} `transitionSettings` Current transition settings + * + * @api requests + * @name GetTransitionSettings + * @category transitions + * @since 4.9.0 + */ +RpcResponse WSRequestHandler::GetTransitionSettings(const RpcRequest& request) { + if (!request.hasField("transitionName")) { + return request.failed("missing request parameters"); + } + + const char* transitionName = obs_data_get_string(request.parameters(), "transitionName"); + OBSSourceAutoRelease transition = Utils::GetTransitionFromName(transitionName); + if (!transition) { + return request.failed("specified transition doesn't exist"); + } + + OBSDataAutoRelease transitionSettings = obs_source_get_settings(transition); + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_obj(response, "transitionSettings", transitionSettings); + return request.success(response); +} + +/** + * Change the current settings of a transition + * + * @param {String} `transitionName` Transition name + * @param {Object} `transitionSettings` Transition settings (they can be partial) + * + * @return {Object} `transitionSettings` Updated transition settings + * + * @api requests + * @name SetTransitionSettings + * @category transitions + * @since 4.9.0 + */ +RpcResponse WSRequestHandler::SetTransitionSettings(const RpcRequest& request) { + if (!request.hasField("transitionName") || !request.hasField("transitionSettings")) { + return request.failed("missing request parameters"); + } + + const char* transitionName = obs_data_get_string(request.parameters(), "transitionName"); + OBSSourceAutoRelease transition = Utils::GetTransitionFromName(transitionName); + if (!transition) { + return request.failed("specified transition doesn't exist"); + } + + OBSDataAutoRelease newSettings = obs_data_get_obj(request.parameters(), "transitionSettings"); + obs_source_update(transition, newSettings); + obs_source_update_properties(transition); + + OBSDataAutoRelease updatedSettings = obs_source_get_settings(transition); + + OBSDataAutoRelease response = obs_data_create(); + obs_data_set_obj(response, "transitionSettings", updatedSettings); + return request.success(response); +} + +/** + * Release the T-Bar (like a user releasing their mouse button after moving it). + * *YOU MUST CALL THIS if you called `SetTBarPosition` with the `release` parameter set to `false`.* + * + * @api requests + * @name ReleaseTBar + * @category transitions + * @since 4.9.0 + */ +RpcResponse WSRequestHandler::ReleaseTBar(const RpcRequest& request) { + if (!obs_frontend_preview_program_mode_active()) { + return request.failed("studio mode not enabled"); + } + + if (obs_transition_fixed(obs_frontend_get_current_transition())) { + return request.failed("current transition doesn't support t-bar control"); + } + + obs_frontend_release_tbar(); + + return request.success(); +} + +/** + * Set the manual position of the T-Bar (in Studio Mode) to the specified value. Will return an error if OBS is not in studio mode + * or if the current transition doesn't support T-Bar control. + * + * If your code needs to perform multiple successive T-Bar moves (e.g. : in an animation, or in response to a user moving a T-Bar control in your User Interface), set `release` to false and call `ReleaseTBar` later once the animation/interaction is over. + * + * @param {double} `position` T-Bar position. This value must be between 0.0 and 1.0. + * @param {boolean (optional)} `release` Whether or not the T-Bar gets released automatically after setting its new position (like a user releasing their mouse button after moving the T-Bar). Call `ReleaseTBar` manually if you set `release` to false. Defaults to true. + * + * @api requests + * @name SetTBarPosition + * @category transitions + * @since 4.9.0 + */ +RpcResponse WSRequestHandler::SetTBarPosition(const RpcRequest& request) { + if (!obs_frontend_preview_program_mode_active()) { + return request.failed("studio mode not enabled"); + } + + if (obs_transition_fixed(obs_frontend_get_current_transition())) { + return request.failed("current transition doesn't support t-bar control"); + } + + if (!request.hasField("position")) { + return request.failed("missing request parameters"); + } + + double position = obs_data_get_double(request.parameters(), "position"); + if (position < 0.0 || position > 1.0) { + return request.failed("position is out of range"); + } + + bool release = true; + if (request.hasField("release")) { + release = obs_data_get_bool(request.parameters(), "release"); + } + + obs_frontend_set_tbar_position((int)((float)position * 1024.0)); + if (release) { + obs_frontend_release_tbar(); + } + + return request.success(); +} diff --git a/src/WSServer.cpp b/src/WSServer.cpp index c9f09362..3e6f9934 100644 --- a/src/WSServer.cpp +++ b/src/WSServer.cpp @@ -44,6 +44,7 @@ WSServer::WSServer() _connections(), _clMutex(QMutex::Recursive) { + _server.get_alog().clear_channels(websocketpp::log::alevel::frame_header | websocketpp::log::alevel::frame_payload | websocketpp::log::alevel::control); _server.init_asio(); #ifndef _WIN32 _server.set_reuse_addr(true); @@ -59,10 +60,10 @@ WSServer::~WSServer() stop(); } -void WSServer::start(quint16 port) +void WSServer::start(quint16 port, bool lockToIPv4) { - if (_server.is_listening() && port == _serverPort) { - blog(LOG_INFO, "WSServer::start: server already on this port. no restart needed"); + if (_server.is_listening() && (port == _serverPort && _lockToIPv4 == lockToIPv4)) { + blog(LOG_INFO, "WSServer::start: server already on this port and protocol mode. no restart needed"); return; } @@ -73,9 +74,16 @@ void WSServer::start(quint16 port) _server.reset(); _serverPort = port; + _lockToIPv4 = lockToIPv4; websocketpp::lib::error_code errorCode; - _server.listen(_serverPort, errorCode); + if (lockToIPv4) { + blog(LOG_INFO, "WSServer::start: Locked to IPv4 bindings"); + _server.listen(websocketpp::lib::asio::ip::tcp::v4(), _serverPort, errorCode); + } else { + blog(LOG_INFO, "WSServer::start: Not locked to IPv4 bindings"); + _server.listen(_serverPort, errorCode); + } if (errorCode) { std::string errorCodeMessage = errorCode.message(); @@ -83,7 +91,7 @@ void WSServer::start(quint16 port) obs_frontend_push_ui_translation(obs_module_get_string); QString errorTitle = tr("OBSWebsocket.Server.StartFailed.Title"); - QString errorMessage = tr("OBSWebsocket.Server.StartFailed.Message").arg(_serverPort); + QString errorMessage = tr("OBSWebsocket.Server.StartFailed.Message").arg(_serverPort).arg(errorCodeMessage.c_str()); obs_frontend_pop_ui_translation(); QMainWindow* mainWindow = reinterpret_cast(obs_frontend_get_main_window()); @@ -113,13 +121,11 @@ void WSServer::stop() for (connection_hdl hdl : _connections) { _server.close(hdl, websocketpp::close::status::going_away, "Server stopping"); } - _connections.clear(); - _connectionProperties.clear(); _threadPool.waitForDone(); - while (!_server.stopped()) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + while (_connections.size() > 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); } blog(LOG_INFO, "server stopped successfully"); @@ -127,16 +133,16 @@ void WSServer::stop() void WSServer::broadcast(const RpcEvent& event) { - OBSRemoteProtocol protocol; - std::string message = protocol.encodeEvent(event); + std::string message = OBSRemoteProtocol::encodeEvent(event); - if (GetConfig()->DebugEnabled) { + auto config = GetConfig(); + if (config && config->DebugEnabled) { blog(LOG_INFO, "Update << '%s'", message.c_str()); } QMutexLocker locker(&_clMutex); for (connection_hdl hdl : _connections) { - if (GetConfig()->AuthRequired) { + if (config && config->AuthRequired) { bool authenticated = _connectionProperties[hdl].isAuthenticated(); if (!authenticated) { continue; @@ -179,15 +185,15 @@ void WSServer::onMessage(connection_hdl hdl, server::message_ptr message) ConnectionProperties& connProperties = _connectionProperties[hdl]; locker.unlock(); - if (GetConfig()->DebugEnabled) { + auto config = GetConfig(); + if (config && config->DebugEnabled) { blog(LOG_INFO, "Request >> '%s'", payload.c_str()); } WSRequestHandler requestHandler(connProperties); - OBSRemoteProtocol protocol; - std::string response = protocol.processMessage(requestHandler, payload); + std::string response = OBSRemoteProtocol::processMessage(requestHandler, payload); - if (GetConfig()->DebugEnabled) { + if (config && config->DebugEnabled) { blog(LOG_INFO, "Response << '%s'", response.c_str()); } diff --git a/src/WSServer.h b/src/WSServer.h index 723f51af..1ca0734b 100644 --- a/src/WSServer.h +++ b/src/WSServer.h @@ -44,7 +44,7 @@ Q_OBJECT public: explicit WSServer(); virtual ~WSServer(); - void start(quint16 port); + void start(quint16 port, bool lockToIPv4); void stop(); void broadcast(const RpcEvent& event); QThreadPool* threadPool() { @@ -62,6 +62,7 @@ private: server _server; quint16 _serverPort; + bool _lockToIPv4; std::set> _connections; std::map> _connectionProperties; QMutex _clMutex; diff --git a/src/forms/settings-dialog.cpp b/src/forms/settings-dialog.cpp index 6aa2d4f1..4835a8c4 100644 --- a/src/forms/settings-dialog.cpp +++ b/src/forms/settings-dialog.cpp @@ -16,12 +16,16 @@ You should have received a copy of the GNU General Public License along with this program. If not, see */ +#include "settings-dialog.h" + #include +#include +#include #include "../obs-websocket.h" #include "../Config.h" #include "../WSServer.h" -#include "settings-dialog.h" + #define CHANGE_ME "changeme" @@ -35,22 +39,25 @@ SettingsDialog::SettingsDialog(QWidget* parent) : this, &SettingsDialog::AuthCheckboxChanged); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::FormAccepted); - - - AuthCheckboxChanged(); } void SettingsDialog::showEvent(QShowEvent* event) { auto conf = GetConfig(); + if (conf) { + ui->serverEnabled->setChecked(conf->ServerEnabled); + ui->serverPort->setValue(conf->ServerPort); + ui->lockToIPv4->setChecked(conf->LockToIPv4); - ui->serverEnabled->setChecked(conf->ServerEnabled); - ui->serverPort->setValue(conf->ServerPort); + ui->debugEnabled->setChecked(conf->DebugEnabled); + ui->alertsEnabled->setChecked(conf->AlertsEnabled); - ui->debugEnabled->setChecked(conf->DebugEnabled); - ui->alertsEnabled->setChecked(conf->AlertsEnabled); + ui->authRequired->blockSignals(true); + ui->authRequired->setChecked(conf->AuthRequired); + ui->authRequired->blockSignals(false); + } - ui->authRequired->setChecked(conf->AuthRequired); ui->password->setText(CHANGE_ME); + ui->password->setEnabled(ui->authRequired->isChecked()); } void SettingsDialog::ToggleShowHide() { @@ -60,18 +67,41 @@ void SettingsDialog::ToggleShowHide() { setVisible(false); } +void SettingsDialog::PreparePasswordEntry() { + ui->authRequired->blockSignals(true); + ui->authRequired->setChecked(true); + ui->authRequired->blockSignals(false); + ui->password->setEnabled(true); + ui->password->setFocus(); +} + void SettingsDialog::AuthCheckboxChanged() { - if (ui->authRequired->isChecked()) + if (ui->authRequired->isChecked()) { ui->password->setEnabled(true); - else - ui->password->setEnabled(false); + } + else { + obs_frontend_push_ui_translation(obs_module_get_string); + QString authDisabledWarning = QObject::tr("OBSWebsocket.Settings.AuthDisabledWarning"); + obs_frontend_pop_ui_translation(); + + QMessageBox::StandardButton response = QMessageBox::question(this, "obs-websocket", authDisabledWarning); + if (response == QMessageBox::Yes) { + ui->password->setEnabled(false); + } else { + ui->authRequired->setChecked(true); + } + } } void SettingsDialog::FormAccepted() { auto conf = GetConfig(); + if (!conf) { + return; + } conf->ServerEnabled = ui->serverEnabled->isChecked(); conf->ServerPort = ui->serverPort->value(); + conf->LockToIPv4 = ui->lockToIPv4->isChecked(); conf->DebugEnabled = ui->debugEnabled->isChecked(); conf->AlertsEnabled = ui->alertsEnabled->isChecked(); @@ -81,7 +111,7 @@ void SettingsDialog::FormAccepted() { conf->SetPassword(ui->password->text()); } - if (!GetConfig()->Secret.isEmpty()) + if (!conf->Secret.isEmpty()) conf->AuthRequired = true; else conf->AuthRequired = false; @@ -95,7 +125,7 @@ void SettingsDialog::FormAccepted() { auto server = GetServer(); if (conf->ServerEnabled) { - server->start(conf->ServerPort); + server->start(conf->ServerPort, conf->LockToIPv4); } else { server->stop(); } diff --git a/src/forms/settings-dialog.h b/src/forms/settings-dialog.h index 138ae395..f67f1f98 100644 --- a/src/forms/settings-dialog.h +++ b/src/forms/settings-dialog.h @@ -31,6 +31,7 @@ public: ~SettingsDialog(); void showEvent(QShowEvent* event); void ToggleShowHide(); + void PreparePasswordEntry(); private Q_SLOTS: void AuthCheckboxChanged(); diff --git a/src/forms/settings-dialog.ui b/src/forms/settings-dialog.ui index 6964a153..237fb932 100644 --- a/src/forms/settings-dialog.ui +++ b/src/forms/settings-dialog.ui @@ -2,150 +2,157 @@ SettingsDialog - - - 0 - 0 - 407 - 195 - - - - - 0 - 0 - - - - OBSWebsocket.Settings.DialogTitle - - - false - - - - QLayout::SetDefaultConstraint - - - - - - - OBSWebsocket.Settings.AuthRequired - - - - - - - OBSWebsocket.Settings.Password - - - - - - - QLineEdit::Password - - - - - - - OBSWebsocket.Settings.ServerEnable - - - true - - - - - - - OBSWebsocket.Settings.ServerPort - - - - - - - 1024 - - - 65535 - - - 4444 - - - - - - - OBSWebsocket.Settings.AlertsEnable - - - true - - - - - - - OBSWebsocket.Settings.DebugEnable - - - false - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - + + + 0 + 0 + 407 + 216 + + + + + 0 + 0 + + + + OBSWebsocket.Settings.DialogTitle + + + false + + + + QLayout::SetDefaultConstraint + + + + + + + OBSWebsocket.Settings.AuthRequired + + + + + + + OBSWebsocket.Settings.Password + + + + + + + QLineEdit::Password + + + + + + + OBSWebsocket.Settings.ServerEnable + + + true + + + + + + + OBSWebsocket.Settings.ServerPort + + + + + + + 1024 + + + 65535 + + + 4444 + + + + + + + OBSWebsocket.Settings.AlertsEnable + + + true + + + + + + + OBSWebsocket.Settings.DebugEnable + + + false + + + + + + + OBSWebsocket.Settings.LockToIPv4 + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + - - buttonBox - accepted() - SettingsDialog - accept() - - - 248 - 294 - - - 157 - 314 - - - - - buttonBox - rejected() - SettingsDialog - reject() - - - 316 - 300 - - - 286 - 314 - - - + + buttonBox + accepted() + SettingsDialog + accept() + + + 248 + 294 + + + 157 + 314 + + + + + buttonBox + rejected() + SettingsDialog + reject() + + + 316 + 300 + + + 286 + 314 + + + diff --git a/src/obs-websocket.cpp b/src/obs-websocket.cpp index 9f19ab9e..4fb3e729 100644 --- a/src/obs-websocket.cpp +++ b/src/obs-websocket.cpp @@ -47,6 +47,7 @@ OBS_MODULE_USE_DEFAULT_LOCALE("obs-websocket", "en-US") ConfigPtr _config; WSServerPtr _server; WSEventsPtr _eventsSystem; +SettingsDialog* settingsDialog = nullptr; bool obs_module_load(void) { blog(LOG_INFO, "you can haz websockets (version %s)", OBS_WEBSOCKET_VERSION); @@ -64,14 +65,14 @@ bool obs_module_load(void) { // UI setup obs_frontend_push_ui_translation(obs_module_get_string); QMainWindow* mainWindow = (QMainWindow*)obs_frontend_get_main_window(); - SettingsDialog* settingsDialog = new SettingsDialog(mainWindow); + settingsDialog = new SettingsDialog(mainWindow); obs_frontend_pop_ui_translation(); 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] { + QObject::connect(menuAction, &QAction::triggered, [] { // The settings dialog belongs to the main window. Should be ok // to pass the pointer to this QAction belonging to the main window settingsDialog->ToggleShowHide(); @@ -81,7 +82,7 @@ bool obs_module_load(void) { auto eventCallback = [](enum obs_frontend_event event, void *param) { if (event == OBS_FRONTEND_EVENT_FINISHED_LOADING) { if (_config->ServerEnabled) { - _server->start(_config->ServerPort); + _server->start(_config->ServerPort, _config->LockToIPv4); } obs_frontend_remove_event_callback((obs_frontend_event_cb)param, nullptr); } @@ -115,3 +116,10 @@ WSServerPtr GetServer() { WSEventsPtr GetEventsSystem() { return _eventsSystem; } + +void ShowPasswordSetting() { + if (settingsDialog) { + settingsDialog->PreparePasswordEntry(); + settingsDialog->setVisible(true); + } +} diff --git a/src/obs-websocket.h b/src/obs-websocket.h index 5f118d28..35eac461 100644 --- a/src/obs-websocket.h +++ b/src/obs-websocket.h @@ -55,7 +55,8 @@ typedef std::shared_ptr WSEventsPtr; ConfigPtr GetConfig(); WSServerPtr GetServer(); WSEventsPtr GetEventsSystem(); +void ShowPasswordSetting(); -#define OBS_WEBSOCKET_VERSION "4.8.0" +#define OBS_WEBSOCKET_VERSION "4.9.0" #define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__) diff --git a/src/protocol/OBSRemoteProtocol.cpp b/src/protocol/OBSRemoteProtocol.cpp index e5b1da1a..13ca1591 100644 --- a/src/protocol/OBSRemoteProtocol.cpp +++ b/src/protocol/OBSRemoteProtocol.cpp @@ -31,11 +31,15 @@ std::string OBSRemoteProtocol::processMessage(WSRequestHandler& requestHandler, OBSDataAutoRelease data = obs_data_create_from_json(msg); if (!data) { blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg); - return errorResponse(QString::Null(), "invalid JSON payload"); + return jsonDataToString( + errorResponse(nullptr, "invalid JSON payload") + ); } if (!obs_data_has_user_value(data, "request-type") || !obs_data_has_user_value(data, "message-id")) { - return errorResponse(QString::Null(), "missing request parameters"); + return jsonDataToString( + errorResponse(nullptr, "missing request parameters") + ); } QString methodName = obs_data_get_string(data, "request-type"); @@ -49,15 +53,8 @@ std::string OBSRemoteProtocol::processMessage(WSRequestHandler& requestHandler, RpcRequest request(messageId, methodName, params); RpcResponse response = requestHandler.processRequest(request); - OBSData additionalFields = response.additionalFields(); - switch (response.status()) { - case RpcResponse::Status::Ok: - return successResponse(messageId, additionalFields); - case RpcResponse::Status::Error: - return errorResponse(messageId, response.errorMessage(), additionalFields); - } - - return std::string(); + OBSDataAutoRelease responseData = rpcResponseToJsonData(response); + return jsonDataToString(responseData); } std::string OBSRemoteProtocol::encodeEvent(const RpcEvent& event) @@ -67,13 +64,15 @@ std::string OBSRemoteProtocol::encodeEvent(const RpcEvent& event) QString updateType = event.updateType(); obs_data_set_string(eventData, "update-type", updateType.toUtf8().constData()); - if (obs_frontend_streaming_active()) { - QString streamingTimecode = Utils::nsToTimestamp(event.streamTime()); + std::optional streamTime = event.streamTime(); + if (streamTime.has_value()) { + QString streamingTimecode = Utils::nsToTimestamp(streamTime.value()); obs_data_set_string(eventData, "stream-timecode", streamingTimecode.toUtf8().constData()); } - if (obs_frontend_recording_active()) { - QString recordingTimecode = Utils::nsToTimestamp(event.recordingTime()); + std::optional recordingTime = event.recordingTime(); + if (recordingTime.has_value()) { + QString recordingTimecode = Utils::nsToTimestamp(recordingTime.value()); obs_data_set_string(eventData, "rec-timecode", recordingTimecode.toUtf8().constData()); } @@ -85,33 +84,56 @@ std::string OBSRemoteProtocol::encodeEvent(const RpcEvent& event) return std::string(obs_data_get_json(eventData)); } -std::string OBSRemoteProtocol::buildResponse(QString messageId, QString status, obs_data_t* fields) +obs_data_t* OBSRemoteProtocol::rpcResponseToJsonData(const RpcResponse& response) { - OBSDataAutoRelease response = obs_data_create(); - if (!messageId.isNull()) { - obs_data_set_string(response, "message-id", messageId.toUtf8().constData()); - } - obs_data_set_string(response, "status", status.toUtf8().constData()); + QByteArray messageIdBytes = response.messageId().toUtf8(); + const char* messageId = messageIdBytes.constData(); - if (fields) { - obs_data_apply(response, fields); + OBSData additionalFields = response.additionalFields(); + switch (response.status()) { + case RpcResponse::Status::Ok: + return successResponse(messageId, additionalFields); + case RpcResponse::Status::Error: + return errorResponse(messageId, response.errorMessage().toUtf8().constData(), additionalFields); + default: + assert(false); } - std::string responseString = obs_data_get_json(response); - return responseString; + return nullptr; } -std::string OBSRemoteProtocol::successResponse(QString messageId, obs_data_t* fields) +obs_data_t* OBSRemoteProtocol::successResponse(const char* messageId, obs_data_t* fields) { return buildResponse(messageId, "ok", fields); } -std::string OBSRemoteProtocol::errorResponse(QString messageId, QString errorMessage, obs_data_t* additionalFields) +obs_data_t* OBSRemoteProtocol::errorResponse(const char* messageId, const char* errorMessage, obs_data_t* additionalFields) { OBSDataAutoRelease fields = obs_data_create(); if (additionalFields) { obs_data_apply(fields, additionalFields); } - obs_data_set_string(fields, "error", errorMessage.toUtf8().constData()); + obs_data_set_string(fields, "error", errorMessage); return buildResponse(messageId, "error", fields); } + +obs_data_t* OBSRemoteProtocol::buildResponse(const char* messageId, const char* status, obs_data_t* fields) +{ + obs_data_t* response = obs_data_create(); + if (messageId) { + obs_data_set_string(response, "message-id", messageId); + } + obs_data_set_string(response, "status", status); + + if (fields) { + obs_data_apply(response, fields); + } + + return response; +} + +std::string OBSRemoteProtocol::jsonDataToString(OBSDataAutoRelease data) +{ + std::string responseString = obs_data_get_json(data); + return responseString; +} diff --git a/src/protocol/OBSRemoteProtocol.h b/src/protocol/OBSRemoteProtocol.h index 03d8aa7d..354ca514 100644 --- a/src/protocol/OBSRemoteProtocol.h +++ b/src/protocol/OBSRemoteProtocol.h @@ -20,7 +20,8 @@ with this program. If not, see #include #include -#include + +#include "../rpc/RpcResponse.h" class WSRequestHandler; class RpcEvent; @@ -28,11 +29,13 @@ class RpcEvent; class OBSRemoteProtocol { public: - std::string processMessage(WSRequestHandler& requestHandler, std::string message); - std::string encodeEvent(const RpcEvent& event); + static std::string processMessage(WSRequestHandler& requestHandler, std::string message); + static std::string encodeEvent(const RpcEvent& event); + static obs_data_t* rpcResponseToJsonData(const RpcResponse& response); private: - std::string buildResponse(QString messageId, QString status, obs_data_t* fields = nullptr); - std::string successResponse(QString messageId, obs_data_t* fields = nullptr); - std::string errorResponse(QString messageId, QString errorMessage, obs_data_t* additionalFields = nullptr); + static obs_data_t* successResponse(const char* messageId, obs_data_t* fields = nullptr); + static obs_data_t* errorResponse(const char* messageId, const char* errorMessage, obs_data_t* additionalFields = nullptr); + static obs_data_t* buildResponse(const char* messageId, const char*, obs_data_t* fields = nullptr); + static std::string jsonDataToString(OBSDataAutoRelease data); }; diff --git a/src/rpc/RpcEvent.cpp b/src/rpc/RpcEvent.cpp index a8d3b06b..a971b6a2 100644 --- a/src/rpc/RpcEvent.cpp +++ b/src/rpc/RpcEvent.cpp @@ -20,7 +20,7 @@ with this program. If not, see RpcEvent::RpcEvent( const QString& updateType, - uint64_t streamTime, uint64_t recordingTime, + std::optional streamTime, std::optional recordingTime, obs_data_t* additionalFields ) : _updateType(updateType), diff --git a/src/rpc/RpcEvent.h b/src/rpc/RpcEvent.h index 6dd0df99..af69a10e 100644 --- a/src/rpc/RpcEvent.h +++ b/src/rpc/RpcEvent.h @@ -18,6 +18,7 @@ with this program. If not, see #pragma once +#include #include #include @@ -28,7 +29,7 @@ class RpcEvent public: explicit RpcEvent( const QString& updateType, - uint64_t streamTime, uint64_t recordingTime, + std::optional streamTime, std::optional recordingTime, obs_data_t* additionalFields = nullptr ); @@ -37,12 +38,12 @@ public: return _updateType; } - const uint64_t streamTime() const + const std::optional streamTime() const { return _streamTime; } - const uint64_t recordingTime() const + const std::optional recordingTime() const { return _recordingTime; } @@ -54,7 +55,7 @@ public: private: QString _updateType; - uint64_t _streamTime; - uint64_t _recordingTime; + std::optional _streamTime; + std::optional _recordingTime; OBSDataAutoRelease _additionalFields; }; diff --git a/src/rpc/RpcResponse.h b/src/rpc/RpcResponse.h index a6381bfd..d0c96d6e 100644 --- a/src/rpc/RpcResponse.h +++ b/src/rpc/RpcResponse.h @@ -36,7 +36,7 @@ public: obs_data_t* additionalFields = nullptr ); - Status status() { + const Status status() const { return _status; }