diff --git a/.gitignore b/.gitignore index c60fdf06..b7e57102 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /build32/ /build64/ /release/ +/package/ /installer/Output/ .idea .vscode diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8e712fbb..00000000 --- a/.travis.yml +++ /dev/null @@ -1,49 +0,0 @@ -language: cpp - -env: - global: - # AWS key ID - - secure: pAiNUGVbjP12BfnWPk0FFTkbnk4Tocvv88XiT3rzRqkQaD7/iyEogLBfHM4nOEgFiIMHbC41aE83w5JgRNPwn6mTgoQBOglzqq1tGuXfqPyV2VStk8beji1evubGoVjjPaoPTFyIdQc5GGxdHyogI/ed9Hb3ccyykYvjyolj9XoCiW42QHx60AHGwl+So+dEa8xydj9SLRPlZ/AitmI/cPVN3YotA7s37BLFiab54enxk7T4rwpR1nU0HVfoCpn5F4wZYxRq+LlSVFzC8vVE9cpDSLS5kjrZIZaT18tYG1/untCj+wqMIZbghaJXLtPSRW2YPHcJTz8q1YSXnJ19+0uiAIMAqaVv0kD5BAM97byYDBW+b9H6SYFkb/Pw/qcK9amMzMBjDPFpYFkl9Q2kzhsNs3HsZf/flSZjtrkQJiP3SOi/KvKzVK9X4Wym6hYZWHgmMTTYFrvr6BYnf2GkpfKNjm1d2kc0NNrq4d5H4NOEQB8MP+QH+o+BPeM6d9dthrUc1Pw+BXzOAr85CN4qtpPGoAl/Dbfgd6eu/88E2LpUufW2VFAOPWjykSOqzSN3orh7AaWuE34VFEnQ+2y3uIE8AKoyXzJv6zYkyNnNewKZeGe2kKYNwLn5UxQA9JEj7a+tvVevk4xBSkkjFAvjSG2z8/F1FXNbEfoLX1Hz/bU= - # AWS key secret - - secure: bGwljoP3E1OVBXLXox0O6p8kwQXLcNQ8YDKVa4H8u9Y+Ic7uqE4iV3rYS3ynNWSBMVRWY3ZbyClnhrCNwRhBAlcd8qWSJdpjVzs6HdQyzhuKa1P3V4FJPb7upGP/5R/DECGwex8Mun9dmXpYDak75LxfKIJUidPis5VDCYqul7k/xVVCou6Ctjpj7vQhWXDj2G/py+mdB8DERhymnQCtyK1Ziu8c4QlFKByZmnD72GFm/h3JPI1Pq1V2mz3x6x6GaYjb9Rdbd0UNwqjGQX4q2M/c3GEJa6B2JBCoTncawNZBNnPUF9qtv+zh0TNaNHMRWX13AJ/qYB+nVDub0C9b/6Mc48mt0Tv4ze15MproVrylZdV6qHYEG8yGPBqpTVbRP6gv6Y2TXIHWoTzqA+F/Gv2IDChyHXsld/MQQS2MSo5iaYktIrZKtX8Z0qAmTzPwIVBromaSI3vrE7UH0fRSQ6fAM8+Tn+MRthOBdqu23kS1dnG+X2CPbUhBfsJp0OSwVQD5jQtA51/sREVeGFiJvzQIkvwQDjb5MYilsRnwmoBXemkLmqaviXVY4rz1o5AIvz2pgZS2YggK1xHZCuI5tSjcNEkb77VwZTfsqrdDo9EJh6VgfdnGlHQhR2/A5hUJ4ANpJ/LgZlgfVp71Xg2GWQW6M4Znc5uj6A6xLBkO6FA= - -cache: - directories: - - node_modules - -matrix: - include: - - os: linux - env: _generate_docs - script: "./CI/generate-docs.sh" - - - os: linux - env: _linux_build - dist: trusty - sudo: required - services: - - docker - before_install: - - docker run -d --name xenial -v $(dirname $(pwd)):/root -v /home/travis/package:/package - -e TRAVIS_BRANCH="$TRAVIS_BRANCH" -e TRAVIS_TAG="$TRAVIS_TAG" -w /root nimmis/ubuntu:16.04 - - docker exec -it xenial /root/obs-websocket/CI/install-dependencies-xenial.sh - script: - - docker exec -it xenial /root/obs-websocket/CI/build-xenial.sh - after_success: - - docker exec -it xenial /root/obs-websocket/CI/package-xenial.sh - -deploy: -- provider: s3 - region: eu-central-1 - bucket: obs-websocket-linux-builds - access_key_id: "$AWS_ID" - secret_access_key: "$AWS_SECRET" - local_dir: /home/travis/package - skip_cleanup: true - acl: public_read - on: - repo: Palakis/obs-websocket - condition: - - "$TRAVIS_OS_NAME = linux" - - "-d /home/travis/package" - all_branches: true diff --git a/CI/build-xenial.sh b/CI/build-ubuntu.sh similarity index 78% rename from CI/build-xenial.sh rename to CI/build-ubuntu.sh index cc7e53ef..b19158ae 100755 --- a/CI/build-xenial.sh +++ b/CI/build-ubuntu.sh @@ -1,8 +1,6 @@ #!/bin/sh set -ex -cd /root/obs-websocket - mkdir build && cd build cmake -DCMAKE_INSTALL_PREFIX=/usr .. make -j4 diff --git a/CI/install-build-obs.cmd b/CI/checkout-cmake-obs-windows.cmd similarity index 50% rename from CI/install-build-obs.cmd rename to CI/checkout-cmake-obs-windows.cmd index 9f73b3e0..0fc6c12c 100644 --- a/CI/install-build-obs.cmd +++ b/CI/checkout-cmake-obs-windows.cmd @@ -18,25 +18,25 @@ REM Set up the build flag as undefined. set "BuildOBS=" REM Check the last tag successfully built by CI. -if exist C:\projects\obs-studio-last-tag-built.txt ( - set /p OBSLastTagBuilt= C:\projects\latest-obs-studio-tag-pre-pull.txt - set /p OBSLatestTagPrePull= "%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*" > C:\projects\latest-obs-studio-tag-post-pull.txt - set /p OBSLatestTagPostPull= C:\projects\latest-obs-studio-tag.txt + git describe --tags --abbrev=0 --exclude="*-rc*" > "%OBSPath%\latest-obs-studio-tag-post-pull.txt" + set /p OBSLatestTagPostPull=<"%OBSPath%\latest-obs-studio-tag-post-pull.txt" + set /p OBSLatestTag=<"%OBSPath%\latest-obs-studio-tag-post-pull.txt" + echo %OBSLatestTagPostPull%> "%OBSPath%\latest-obs-studio-tag.txt" ) REM Check the obs-studio tags for mismatches. @@ -58,22 +58,22 @@ if not %OBSLatestTagPostPull%==%OBSLastTagBuilt% ( REM If obs-studio directory does not exist, clone the git repo, get the latest REM tag number, and set the build flag. -if not exist C:\projects\obs-studio ( +if not exist %OBSPath% ( echo obs-studio directory does not exist - git clone https://github.com/obsproject/obs-studio - cd C:\projects\obs-studio\ - git describe --tags --abbrev=0 --exclude="*-rc*" > C:\projects\obs-studio-latest-tag.txt - set /p OBSLatestTag= "%OBSPath%\obs-studio-latest-tag.txt" + set /p OBSLatestTag=<"%OBSPath%\obs-studio-latest-tag.txt" set BuildOBS=true ) REM If the needed obs-studio libs for this build_config do not exist, REM set the build flag. -if not exist C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib ( +if not exist %OBSPath%\build32\libobs\%build_config%\obs.lib ( echo obs-studio\build32\libobs\%build_config%\obs.lib does not exist set BuildOBS=true ) -if not exist C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib ( +if not exist %OBSPath%\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib ( echo obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib does not exist set BuildOBS=true ) @@ -95,35 +95,43 @@ echo: REM If the build flag is set, build obs-studio. if defined BuildOBS ( echo Building obs-studio... + cd /D %OBSPath% echo git checkout %OBSLatestTag% git checkout %OBSLatestTag% echo: - echo Removing previous build dirs... - if exist build rmdir /s /q C:\projects\obs-studio\build - if exist build32 rmdir /s /q C:\projects\obs-studio\build32 - if exist build64 rmdir /s /q C:\projects\obs-studio\build64 - echo Making new build dirs... - mkdir build + + echo Removing previous build dirs... + if exist build32 rmdir /s /q "%OBSPath%\build32" + if exist build64 rmdir /s /q "%OBSPath%\build64" + + echo Making new build dirs... mkdir build32 mkdir build64 - echo Running cmake for obs-studio %OBSLatestTag% 32-bit... - cd ./build32 - cmake -G "Visual Studio 14 2015" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true .. + + echo Running cmake for obs-studio %OBSLatestTag% 32-bit... + cd build32 + cmake -G "Visual Studio 16 2019" -A Win32 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR32%" -DDepsPath="%DepsPath32%" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true .. echo: echo: - echo Running cmake for obs-studio %OBSLatestTag% 64-bit... - cd ../build64 - cmake -G "Visual Studio 14 2015 Win64" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true .. + + echo Running cmake for obs-studio %OBSLatestTag% 64-bit... + cd ..\build64 + cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0 -DQTDIR="%QTDIR64%" -DDepsPath="%DepsPath64%" -DBUILD_CAPTIONS=true -DDISABLE_PLUGINS=true -DCOPIED_DEPENDENCIES=false -DCOPY_DEPENDENCIES=true .. echo: echo: - echo Building obs-studio %OBSLatestTag% 32-bit ^(Build Config: %build_config%^)... - call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build32\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" - echo Building obs-studio %OBSLatestTag% 64-bit ^(Build Config: %build_config%^)... - call msbuild /m /p:Configuration=%build_config% C:\projects\obs-studio\build64\obs-studio.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" - cd .. - git describe --tags --abbrev=0 > C:\projects\obs-studio-last-tag-built.txt - set /p OBSLastTagBuilt= "%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 new file mode 100644 index 00000000..ff4ffd57 --- /dev/null +++ b/CI/download-obs-deps.cmd @@ -0,0 +1,6 @@ +if not exist %DepsBasePath% ( + curl -o %DepsBasePath%.zip -kLO https://obsproject.com/downloads/dependencies2017.zip -f --retry 5 -C - + 7z x %DepsBasePath%.zip -o%DepsBasePath% +) else ( + echo "OBS dependencies are already there. Download skipped." +) diff --git a/CI/generate-docs.sh b/CI/generate-docs.sh index 5e7d6d01..bb1d3dfd 100755 --- a/CI/generate-docs.sh +++ b/CI/generate-docs.sh @@ -4,6 +4,9 @@ echo "-- Generating documentation." echo "-- Node version: $(node -v)" echo "-- NPM version: $(npm -v)" +git fetch origin +git checkout ${CHECKOUT_REF/refs\/heads\//} + cd docs npm install npm run build @@ -15,19 +18,14 @@ if git diff --quiet; then exit 0 fi -if [ "$TRAVIS_PULL_REQUEST" != "false" -o "$TRAVIS_BRANCH" != "4.x-current" ]; then - echo "-- Skipping documentation deployment because this is either a pull request or a non-master branch." - exit 0 -fi - REMOTE_URL="$(git config remote.origin.url)" TARGET_REPO=${REMOTE_URL/https:\/\/github.com\//github.com/} GITHUB_REPO=https://${GH_TOKEN:-git}@${TARGET_REPO} -git config user.name "Travis CI" +git config user.name "Azure CI" git config user.email "$COMMIT_AUTHOR_EMAIL" git add ./generated git pull -git commit -m "docs(travis): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]" -git push -q $GITHUB_REPO HEAD:$TRAVIS_BRANCH +git commit -m "docs(ci): Update protocol.md - $(git rev-parse --short HEAD) [skip ci]" +git push -q $GITHUB_REPO diff --git a/CI/install-dependencies-ubuntu.sh b/CI/install-dependencies-ubuntu.sh new file mode 100755 index 00000000..d0e16f53 --- /dev/null +++ b/CI/install-dependencies-ubuntu.sh @@ -0,0 +1,19 @@ +#!/bin/sh +set -ex + +sudo add-apt-repository -y ppa:obsproject/obs-studio +sudo apt-get -qq update + +sudo apt-get install -y \ + libc-dev-bin \ + libc6-dev git \ + build-essential \ + checkinstall \ + cmake \ + obs-studio \ + qtbase5-dev + +# Dirty hack +sudo wget -O /usr/include/obs/obs-frontend-api.h https://raw.githubusercontent.com/obsproject/obs-studio/25.0.0/UI/obs-frontend-api/obs-frontend-api.h + +sudo ldconfig diff --git a/CI/install-dependencies-xenial.sh b/CI/install-dependencies-xenial.sh deleted file mode 100755 index bd9dd03d..00000000 --- a/CI/install-dependencies-xenial.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -set -ex - -add-apt-repository -y ppa:obsproject/obs-studio -apt-get -qq update - -apt-get install -y \ - libc-dev-bin \ - libc6-dev git \ - build-essential \ - checkinstall \ - cmake \ - obs-studio \ - qtbase5-dev - -# Dirty hack -wget -O /usr/include/obs/obs-frontend-api.h https://raw.githubusercontent.com/obsproject/obs-studio/master/UI/obs-frontend-api/obs-frontend-api.h - -ldconfig diff --git a/CI/install-qt-win.cmd b/CI/install-qt-win.cmd new file mode 100644 index 00000000..e0537fe8 --- /dev/null +++ b/CI/install-qt-win.cmd @@ -0,0 +1,8 @@ +if not exist %QtBaseDir% ( + curl -kLO https://cdn-fastly.obsproject.com/downloads/Qt_5.10.1.7z -f --retry 5 -z Qt_5.10.1.7z + 7z x Qt_5.10.1.7z -o%QtBaseDir% +) else ( + echo "Qt is already installed. Download skipped." +) + +dir %QtBaseDir% diff --git a/CI/install-setup-qt.cmd b/CI/install-setup-qt.cmd deleted file mode 100644 index e7dc3784..00000000 --- a/CI/install-setup-qt.cmd +++ /dev/null @@ -1,6 +0,0 @@ -@echo off - -REM Set default values to use AppVeyor's built-in Qt. -set QTDIR32=C:\Qt\5.10.1\msvc2015 -set QTDIR64=C:\Qt\5.10.1\msvc2015_64 -set QTCompileVersion=5.10.1 diff --git a/CI/package-macos.sh b/CI/package-macos.sh index b68c40a8..5210ba5b 100755 --- a/CI/package-macos.sh +++ b/CI/package-macos.sh @@ -19,7 +19,6 @@ export VERSION="$GIT_HASH-$GIT_BRANCH_OR_TAG" export LATEST_VERSION="$GIT_BRANCH_OR_TAG" export FILENAME="obs-websocket-$VERSION.pkg" -export LATEST_FILENAME="obs-websocket-latest-$LATEST_VERSION.pkg" echo "[obs-websocket] Modifying obs-websocket.so" install_name_tool \ @@ -40,4 +39,3 @@ packagesbuild ./CI/macos/obs-websocket.pkgproj echo "[obs-websocket] Renaming obs-websocket.pkg to $FILENAME" mv ./release/obs-websocket.pkg ./release/$FILENAME -cp ./release/$FILENAME ./release/$LATEST_FILENAME diff --git a/CI/package-ubuntu.sh b/CI/package-ubuntu.sh new file mode 100755 index 00000000..367e002c --- /dev/null +++ b/CI/package-ubuntu.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +set -e + +export GIT_HASH=$(git rev-parse --short HEAD) +export PKG_VERSION="1-$GIT_HASH-$BRANCH_SHORT_NAME-git" + +if [[ "$BRANCH_FULL_NAME" =~ "^refs/tags/" ]]; then + export PKG_VERSION="$BRANCH_SHORT_NAME" +fi + +cd ./build + +PAGER="cat" sudo checkinstall -y --type=debian --fstrans=no --nodoc \ + --backup=no --deldoc=yes --install=no \ + --pkgname=obs-websocket --pkgversion="$PKG_VERSION" \ + --pkglicense="GPLv2.0" --maintainer="stephane.lepin@gmail.com" \ + --pkggroup="video" \ + --pkgsource="https://github.com/Palakis/obs-websocket" \ + --requires="obs-studio,libqt5core5a,libqt5widgets5,qt5-image-formats-plugins" \ + --pakdir="../package" + +sudo chmod ao+r ../package/* diff --git a/CI/package-windows.cmd b/CI/package-windows.cmd new file mode 100644 index 00000000..fe752995 --- /dev/null +++ b/CI/package-windows.cmd @@ -0,0 +1,12 @@ +mkdir package +cd package + +git rev-parse --short HEAD > package-version.txt +set /p PackageVersion=" "${RELEASE_DIR}/obs-plugins/${ARCH_NAME}") + # In Release mode, copy Qt image format plugins + COMMAND if $==1 ( + "${CMAKE_COMMAND}" -E copy + "${QTDIR}/plugins/imageformats/qjpeg.dll" + "${RELEASE_DIR}/bin/${ARCH_NAME}/imageformats/qjpeg.dll") + 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 diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index c12a2501..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,40 +0,0 @@ -environment: - CURL_VERSION: 7.39.0 - -install: - - git submodule update --init --recursive - - cd C:\projects\ - - if not exist dependencies2015.zip curl -kLO https://obsproject.com/downloads/dependencies2015.zip -f --retry 5 -C - - - 7z x dependencies2015.zip -odependencies2015 - - set DepsPath32=%CD%\dependencies2015\win32 - - set DepsPath64=%CD%\dependencies2015\win64 - - call C:\projects\obs-websocket\CI\install-setup-qt.cmd - - set build_config=RelWithDebInfo - - call C:\projects\obs-websocket\CI\install-build-obs.cmd - - cd C:\projects\obs-websocket\ - - mkdir build32 - - mkdir build64 - - cd ./build32 - - cmake -G "Visual Studio 14 2015" -DQTDIR="%QTDIR32%" -DLibObs_DIR="C:\projects\obs-studio\build32\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build32\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build32\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" .. - - cd ../build64 - - cmake -G "Visual Studio 14 2015 Win64" -DQTDIR="%QTDIR64%" -DLibObs_DIR="C:\projects\obs-studio\build64\libobs" -DLIBOBS_INCLUDE_DIR="C:\projects\obs-studio\libobs" -DLIBOBS_LIB="C:\projects\obs-studio\build64\libobs\%build_config%\obs.lib" -DOBS_FRONTEND_LIB="C:\projects\obs-studio\build64\UI\obs-frontend-api\%build_config%\obs-frontend-api.lib" .. - -build_script: - - call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build32\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" - - call msbuild /m /p:Configuration=%build_config% C:\projects\obs-websocket\build64\obs-websocket.sln /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" - -before_deploy: - - 7z a "C:\projects\obs-websocket\build.zip" C:\projects\obs-websocket\release\* - - set PATH=%PATH%;"C:\\Program Files (x86)\\Inno Setup 5" - - iscc "C:\projects\obs-websocket\installer\installer.iss" - -deploy_script: - - ps: Push-AppveyorArtifact "C:\projects\obs-websocket\build.zip" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1)-Windows.zip" - - ps: Push-AppveyorArtifact "C:\projects\obs-websocket\installer\Output\obs-websocket-Windows-Installer.exe" -FileName "obs-websocket-$(git log --pretty=format:'%h' -n 1)-Windows-Installer.exe" - -test: off - -cache: - - C:\projects\dependencies2015.zip - - C:\projects\obs-studio-last-tag-built.txt - - C:\projects\obs-studio\ diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ec85baf6..11c09166 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,23 +1,158 @@ -pool: - vmImage: 'macOS-10.13' +jobs: +- job: 'GenerateDocs' + condition: | + or( + eq(variables['Build.SourceBranch'], 'refs/heads/4.x-current'), + eq(variables['Build.SourceBranch'], 'refs/heads/master') + ) + pool: + vmImage: 'ubuntu-18.04' + steps: + - checkout: self + submodules: false -steps: -- checkout: self - submodules: true + - script: ./CI/generate-docs.sh + displayName: 'Generate docs' + env: + CHECKOUT_REF: $(Build.SourceBranch) + GH_TOKEN: $(GithubToken) -- script: ./CI/install-dependencies-macos.sh - displayName: 'Install Dependencies' +- job: 'Build_Windows' + pool: + vmImage: 'windows-2019' + variables: + build_config: RelWithDebInfo + DepsBasePath: 'D:\obsdependencies' + DepsPath32: '$(DepsBasePath)\win32' + DepsPath64: '$(DepsBasePath)\win64' + QtBaseDir: 'D:\QtDep' + QTDIR32: '$(QtBaseDir)\5.10.1\msvc2017' + QTDIR64: '$(QtBaseDir)\5.10.1\msvc2017_64' + OBSPath: 'D:\obs-studio' + steps: + - checkout: self + submodules: true -- script: ./CI/install-build-obs-macos.sh - displayName: 'Build OBS' + - script: ./CI/install-qt-win.cmd + displayName: 'Install Qt' + env: + QtBaseDir: $(QtBaseDir) -- script: ./CI/build-macos.sh - displayName: 'Build obs-websocket' + - task: Cache@2 + displayName: Restore cached OBS Studio dependencies + inputs: + key: 'obsdeps | "$(Agent.OS)"' + restoreKeys: | + obsdeps | "$(Agent.OS)" + path: $(DepsBasePath) -- script: ./CI/package-macos.sh - displayName: 'Package' + - script: ./CI/download-obs-deps.cmd + displayName: 'Download OBS Studio dependencies' -- task: PublishBuildArtifacts@1 - inputs: - pathtoPublish: './release' - artifactName: 'build' + - task: Cache@2 + displayName: Restore cached OBS Studio builds + inputs: + key: 'obs | "$(Agent.OS)"' + restoreKeys: | + obs | "$(Agent.OS)" + path: $(OBSPath) + + - script: ./CI/checkout-cmake-obs-windows.cmd + displayName: 'Checkout & CMake OBS Studio' + env: + build_config: $(build_config) + DepsPath32: $(DepsPath32) + DepsPath64: $(DepsPath64) + QTDIR32: $(QTDIR32) + QTDIR64: $(QTDIR64) + OBSPath: $(OBSPath) + + - task: MSBuild@1 + displayName: 'Build OBS Studio 32-bit' + inputs: + msbuildArguments: '/m /p:Configuration=$(build_config)' + solution: '$(OBSPath)\build32\obs-studio.sln' + + - task: MSBuild@1 + displayName: 'Build OBS Studio 64-bit' + inputs: + msbuildArguments: '/m /p:Configuration=$(build_config)' + solution: '$(OBSPath)\build64\obs-studio.sln' + + - script: ./CI/prepare-windows.cmd + displayName: 'CMake obs-websocket' + env: + build_config: $(build_config) + QTDIR32: $(QTDIR32) + QTDIR64: $(QTDIR64) + OBSPath: $(OBSPath) + + - task: MSBuild@1 + displayName: 'Build obs-websocket 32-bit' + inputs: + msbuildArguments: '/m /p:Configuration=$(build_config)' + solution: '.\build32\obs-websocket.sln' + + - task: MSBuild@1 + displayName: 'Build obs-websocket 64-bit' + inputs: + msbuildArguments: '/m /p:Configuration=$(build_config)' + solution: '.\build64\obs-websocket.sln' + + - script: ./CI/package-windows.cmd + displayName: 'Package obs-websocket' + + - task: PublishBuildArtifacts@1 + displayName: 'Upload package artifacts' + inputs: + pathtoPublish: './package' + artifactName: 'windows_build' + +- job: 'Build_Linux' + pool: + vmImage: 'ubuntu-18.04' + variables: + BUILD_REASON: $(Build.Reason) + BRANCH_SHORT_NAME: $(Build.SourceBranchName) + BRANCH_FULL_NAME: $(Build.SourceBranch) + steps: + - checkout: self + submodules: true + + - script: ./CI/install-dependencies-ubuntu.sh + displayName: 'Install dependencies' + + - script: ./CI/build-ubuntu.sh + displayName: 'Build obs-websocket' + + - script: ./CI/package-ubuntu.sh + displayName: 'Package obs-websocket' + + - task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: './package' + artifactName: 'deb_build' + +- job: 'Build_macOS' + pool: + vmImage: 'macos-10.14' + steps: + - checkout: self + submodules: true + + - script: ./CI/install-dependencies-macos.sh + displayName: 'Install dependencies' + + - script: ./CI/install-build-obs-macos.sh + displayName: 'Build OBS' + + - script: ./CI/build-macos.sh + displayName: 'Build obs-websocket' + + - script: ./CI/package-macos.sh + displayName: 'Package obs-websocket' + + - task: PublishBuildArtifacts@1 + inputs: + pathtoPublish: './release' + artifactName: 'macos_build' diff --git a/docs/generated/comments.json b/docs/generated/comments.json index e21f0201..2c54dc9a 100644 --- a/docs/generated/comments.json +++ b/docs/generated/comments.json @@ -6,9 +6,11 @@ "property": [ "{Number} `cy`", "{Number} `cx`", + "{Number} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis.", "{String} `name` The name of this Scene Item.", "{int} `id` Scene item ID", "{Boolean} `render` Whether or not this Scene Item is set to \"visible\".", + "{Boolean} `muted` Whether or not this Scene Item is muted.", "{Boolean} `locked` Whether or not this Scene Item is locked and can't be moved around", "{Number} `source_cx`", "{Number} `source_cy`", @@ -30,6 +32,11 @@ "name": "cx", "description": "" }, + { + "type": "Number", + "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", "name": "name", @@ -45,6 +52,11 @@ "name": "render", "description": "Whether or not this Scene Item is set to \"visible\"." }, + { + "type": "Boolean", + "name": "muted", + "description": "Whether or not this Scene Item is muted." + }, { "type": "Boolean", "name": "locked", @@ -769,7 +781,7 @@ "return": [ "{String} `name` Transition name.", "{String} `type` Transition type.", - "{int} `duration` Transition duration (in milliseconds).", + "{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} `to-scene` Destination scene of the transition" ], @@ -791,7 +803,7 @@ { "type": "int", "name": "duration", - "description": "Transition duration (in milliseconds)." + "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", @@ -829,6 +841,134 @@ "lead": "", "type": "class", "examples": [] + }, + { + "subheads": [], + "description": "A transition (other than \"cut\") has ended.\nPlease note that the `from-scene` field is not available in TransitionEnd.", + "return": [ + "{String} `name` Transition name.", + "{String} `type` Transition type.", + "{int} `duration` Transition duration (in milliseconds).", + "{String} `to-scene` Destination scene of the transition" + ], + "api": "events", + "name": "TransitionEnd", + "category": "transitions", + "since": "4.8.0", + "returns": [ + { + "type": "String", + "name": "name", + "description": "Transition name." + }, + { + "type": "String", + "name": "type", + "description": "Transition type." + }, + { + "type": "int", + "name": "duration", + "description": "Transition duration (in milliseconds)." + }, + { + "type": "String", + "name": "to-scene", + "description": "Destination scene of the transition" + } + ], + "names": [ + { + "name": "", + "description": "TransitionEnd" + } + ], + "categories": [ + { + "name": "", + "description": "transitions" + } + ], + "sinces": [ + { + "name": "", + "description": "4.8.0" + } + ], + "heading": { + "level": 2, + "text": "TransitionEnd" + }, + "lead": "", + "type": "class", + "examples": [] + }, + { + "subheads": [], + "description": "A stinger transition has finished playing its video.", + "return": [ + "{String} `name` Transition name.", + "{String} `type` Transition type.", + "{int} `duration` Transition duration (in milliseconds).", + "{String} `from-scene` Source scene of the transition", + "{String} `to-scene` Destination scene of the transition" + ], + "api": "events", + "name": "TransitionVideoEnd", + "category": "transitions", + "since": "4.8.0", + "returns": [ + { + "type": "String", + "name": "name", + "description": "Transition name." + }, + { + "type": "String", + "name": "type", + "description": "Transition type." + }, + { + "type": "int", + "name": "duration", + "description": "Transition duration (in milliseconds)." + }, + { + "type": "String", + "name": "from-scene", + "description": "Source scene of the transition" + }, + { + "type": "String", + "name": "to-scene", + "description": "Destination scene of the transition" + } + ], + "names": [ + { + "name": "", + "description": "TransitionVideoEnd" + } + ], + "categories": [ + { + "name": "", + "description": "transitions" + } + ], + "sinces": [ + { + "name": "", + "description": "4.8.0" + } + ], + "heading": { + "level": 2, + "text": "TransitionVideoEnd" + }, + "lead": "", + "type": "class", + "examples": [] } ], "profiles": [ @@ -2566,6 +2706,67 @@ "type": "class", "examples": [] }, + { + "subheads": [], + "description": "An 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.", + "{int} `item-id` Scene item ID", + "{boolean} `item-locked` New locked state of the item." + ], + "api": "events", + "name": "SceneItemLockChanged", + "category": "sources", + "since": "unreleased", + "returns": [ + { + "type": "String", + "name": "scene-name", + "description": "Name of the scene." + }, + { + "type": "String", + "name": "item-name", + "description": "Name of the item in the scene." + }, + { + "type": "int", + "name": "item-id", + "description": "Scene item ID" + }, + { + "type": "boolean", + "name": "item-locked", + "description": "New locked state of the item." + } + ], + "names": [ + { + "name": "", + "description": "SceneItemLockChanged" + } + ], + "categories": [ + { + "name": "", + "description": "sources" + } + ], + "sinces": [ + { + "name": "", + "description": "unreleased" + } + ], + "heading": { + "level": 2, + "text": "SceneItemLockChanged" + }, + "lead": "", + "type": "class", + "examples": [] + }, { "subheads": [], "description": "An item's transform has been changed.", @@ -2840,7 +3041,8 @@ "{double} `version` OBSRemote compatible API version. Fixed to 1.1 for retrocompatibility.", "{String} `obs-websocket-version` obs-websocket plugin version.", "{String} `obs-studio-version` OBS Studio program version.", - "{String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : \"Method1,Method2,Method3\")." + "{String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : \"Method1,Method2,Method3\").", + "{String} `supported-image-export-formats` List of supported formats for features that use image export (like the TakeSourceScreenshot request type) formatted as a comma-separated list string" ], "api": "requests", "name": "GetVersion", @@ -2866,6 +3068,11 @@ "type": "String", "name": "available-requests", "description": "List of available request types, formatted as a comma-separated list string (e.g. : \"Method1,Method2,Method3\")." + }, + { + "type": "String", + "name": "supported-image-export-formats", + "description": "List of supported formats for features that use image export (like the TakeSourceScreenshot request type) formatted as a comma-separated list string" } ], "names": [ @@ -3293,6 +3500,67 @@ "lead": "", "type": "class", "examples": [] + }, + { + "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).", + "{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)} `name` Name of the source or scene to be displayed (ignored for other projector types)." + ], + "api": "requests", + "name": "OpenProjector", + "category": "general", + "since": "unreleased", + "params": [ + { + "type": "String (Optional)", + "name": "type", + "description": "Type of projector: Preview (default), Source, Scene, StudioProgram, or Multiview (case insensitive)." + }, + { + "type": "int (Optional)", + "name": "monitor", + "description": "Monitor to open the projector on. If -1 or omitted, opens a window." + }, + { + "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." + }, + { + "type": "String (Optional)", + "name": "name", + "description": "Name of the source or scene to be displayed (ignored for other projector types)." + } + ], + "names": [ + { + "name": "", + "description": "OpenProjector" + } + ], + "categories": [ + { + "name": "", + "description": "general" + } + ], + "sinces": [ + { + "name": "", + "description": "unreleased" + } + ], + "heading": { + "level": 2, + "text": "OpenProjector" + }, + "lead": "", + "type": "class", + "examples": [] } ], "outputs": [ @@ -4131,6 +4399,7 @@ "{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.", @@ -4139,9 +4408,8 @@ "{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)" - ], - "property": [ + "{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)" ], @@ -4210,6 +4478,11 @@ "name": "visible", "description": "If the source is visible." }, + { + "type": "bool", + "name": "muted", + "description": "If the source is muted." + }, { "type": "bool", "name": "locked", @@ -4254,6 +4527,21 @@ "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": [ @@ -4268,18 +4556,6 @@ "description": "The name of the source." } ], - "properties": [ - { - "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)" - } - ], "names": [ { "name": "", @@ -7477,9 +7753,9 @@ "{Object (optional)} `stream.settings` Settings for the stream.", "{String (optional)} `stream.settings.server` The publish URL.", "{String (optional)} `stream.settings.key` The publish key of the stream.", - "{boolean (optional)} `stream.settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.", - "{String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use-auth` is not set to `true`.", - "{String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use-auth` is not set to `true`." + "{boolean (optional)} `stream.settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.", + "{String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use_auth` is not set to `true`.", + "{String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`." ], "api": "requests", "name": "StartStreaming", @@ -7518,18 +7794,18 @@ }, { "type": "boolean (optional)", - "name": "stream.settings.use-auth", + "name": "stream.settings.use_auth", "description": "Indicates whether authentication should be used when connecting to the streaming server." }, { "type": "String (optional)", "name": "stream.settings.username", - "description": "If authentication is enabled, the username for the streaming server. Ignored if `use-auth` is not set to `true`." + "description": "If authentication is enabled, the username for the streaming server. Ignored if `use_auth` is not set to `true`." }, { "type": "String (optional)", "name": "stream.settings.password", - "description": "If authentication is enabled, the password for the streaming server. Ignored if `use-auth` is not set to `true`." + "description": "If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`." } ], "names": [ @@ -7599,7 +7875,7 @@ "{Object} `settings` The actual settings of the stream.", "{String (optional)} `settings.server` The publish URL.", "{String (optional)} `settings.key` The publish key.", - "{boolean (optional)} `settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.", + "{boolean (optional)} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.", "{String (optional)} `settings.username` The username for the streaming service.", "{String (optional)} `settings.password` The password for the streaming service.", "{boolean} `save` Persist the settings to disk." @@ -7631,7 +7907,7 @@ }, { "type": "boolean (optional)", - "name": "settings.use-auth", + "name": "settings.use_auth", "description": "Indicates whether authentication should be used when connecting to the streaming server." }, { @@ -7684,9 +7960,9 @@ "{Object} `settings` Stream settings object.", "{String} `settings.server` The publish URL.", "{String} `settings.key` The publish key of the stream.", - "{boolean} `settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server.", - "{String} `settings.username` The username to use when accessing the streaming server. Only present if `use-auth` is `true`.", - "{String} `settings.password` The password to use when accessing the streaming server. Only present if `use-auth` is `true`." + "{boolean} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server.", + "{String} `settings.username` The username to use when accessing the streaming server. Only present if `use_auth` is `true`.", + "{String} `settings.password` The password to use when accessing the streaming server. Only present if `use_auth` is `true`." ], "api": "requests", "name": "GetStreamSettings", @@ -7715,18 +7991,18 @@ }, { "type": "boolean", - "name": "settings.use-auth", + "name": "settings.use_auth", "description": "Indicates whether authentication should be used when connecting to the streaming server." }, { "type": "String", "name": "settings.username", - "description": "The username to use when accessing the streaming server. Only present if `use-auth` is `true`." + "description": "The username to use when accessing the streaming server. Only present if `use_auth` is `true`." }, { "type": "String", "name": "settings.password", - "description": "The password to use when accessing the streaming server. Only present if `use-auth` is `true`." + "description": "The password to use when accessing the streaming server. Only present if `use_auth` is `true`." } ], "names": [ diff --git a/docs/generated/protocol.md b/docs/generated/protocol.md index bf1cb9e0..77f780d5 100644 --- a/docs/generated/protocol.md +++ b/docs/generated/protocol.md @@ -59,6 +59,8 @@ auth_response = base64_encode(auth_response_hash) + [TransitionListChanged](#transitionlistchanged) + [TransitionDurationChanged](#transitiondurationchanged) + [TransitionBegin](#transitionbegin) + + [TransitionEnd](#transitionend) + + [TransitionVideoEnd](#transitionvideoend) * [Profiles](#profiles) + [ProfileChanged](#profilechanged) + [ProfileListChanged](#profilelistchanged) @@ -101,6 +103,7 @@ auth_response = base64_encode(auth_response_hash) + [SceneItemAdded](#sceneitemadded) + [SceneItemRemoved](#sceneitemremoved) + [SceneItemVisibilityChanged](#sceneitemvisibilitychanged) + + [SceneItemLockChanged](#sceneitemlockchanged) + [SceneItemTransformChanged](#sceneitemtransformchanged) + [SceneItemSelected](#sceneitemselected) + [SceneItemDeselected](#sceneitemdeselected) @@ -118,6 +121,7 @@ auth_response = base64_encode(auth_response_hash) + [GetStats](#getstats) + [BroadcastCustomMessage](#broadcastcustommessage-1) + [GetVideoInfo](#getvideoinfo) + + [OpenProjector](#openprojector) * [Outputs](#outputs) + [ListOutputs](#listoutputs) + [GetOutputInfo](#getoutputinfo) @@ -222,9 +226,11 @@ These are complex types, such as `Source` and `Scene`, which are used as argumen | ---- | :---: | ------------| | `cy` | _Number_ | | | `cx` | _Number_ | | +| `alignment` | _Number_ | The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis. | | `name` | _String_ | The name of this Scene Item. | | `id` | _int_ | Scene item ID | | `render` | _Boolean_ | Whether or not this Scene Item is set to "visible". | +| `muted` | _Boolean_ | Whether or not this Scene Item is muted. | | `locked` | _Boolean_ | Whether or not this Scene Item is locked and can't be moved around | | `source_cx` | _Number_ | | | `source_cy` | _Number_ | | @@ -429,6 +435,46 @@ A transition (other than "cut") has begun. **Response Items:** +| Name | Type | Description | +| ---- | :---: | ------------| +| `name` | _String_ | Transition name. | +| `type` | _String_ | Transition type. | +| `duration` | _int_ | Transition duration (in milliseconds). Will be -1 for any transition with a fixed duration, such as a Stinger, due to limitations of the OBS API. | +| `from-scene` | _String_ | Source scene of the transition | +| `to-scene` | _String_ | Destination scene of the transition | + + +--- + +### TransitionEnd + + +- Added in v4.8.0 + +A transition (other than "cut") has ended. +Please note that the `from-scene` field is not available in TransitionEnd. + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `name` | _String_ | Transition name. | +| `type` | _String_ | Transition type. | +| `duration` | _int_ | Transition duration (in milliseconds). | +| `to-scene` | _String_ | Destination scene of the transition | + + +--- + +### TransitionVideoEnd + + +- Added in v4.8.0 + +A stinger transition has finished playing its video. + +**Response Items:** + | Name | Type | Description | | ---- | :---: | ------------| | `name` | _String_ | Transition name. | @@ -1030,6 +1076,25 @@ An item's visibility has been toggled. | `item-visible` | _boolean_ | New visibility state of the item. | +--- + +### SceneItemLockChanged + + +- Unreleased + +An item's locked status has been toggled. + +**Response Items:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `scene-name` | _String_ | Name of the scene. | +| `item-name` | _String_ | Name of the item in the scene. | +| `item-id` | _int_ | Scene item ID | +| `item-locked` | _boolean_ | New locked state of the item. | + + --- ### SceneItemTransformChanged @@ -1159,6 +1224,7 @@ _No specified parameters._ | `obs-websocket-version` | _String_ | obs-websocket plugin version. | | `obs-studio-version` | _String_ | OBS Studio program version. | | `available-requests` | _String_ | List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3"). | +| `supported-image-export-formats` | _String_ | List of supported formats for features that use image export (like the TakeSourceScreenshot request type) formatted as a comma-separated list string | --- @@ -1333,6 +1399,29 @@ _No specified parameters._ | `colorRange` | _String_ | Color range (full or partial) | +--- + +### OpenProjector + + +- Unreleased + +Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 or newer. + +**Request Fields:** + +| Name | Type | Description | +| ---- | :---: | ------------| +| `type` | _String (Optional)_ | Type of projector: Preview (default), Source, Scene, StudioProgram, or Multiview (case insensitive). | +| `monitor` | _int (Optional)_ | Monitor to open the projector on. If -1 or omitted, opens a window. | +| `geometry` | _String (Optional)_ | Size and position of the projector window (only if monitor is -1). Encoded in Base64 using Qt's geometry encoding (https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors. | +| `name` | _String (Optional)_ | Name of the source or scene to be displayed (ignored for other projector types). | + + +**Response Items:** + +_No additional response items._ + --- ## Outputs @@ -1791,6 +1880,7 @@ Coordinates are relative to the item's parent (the scene or group it belongs to) | `crop.bottom` | _int_ | The number of pixels cropped off the bottom of the source before scaling. | | `crop.left` | _int_ | The number of pixels cropped off the left of the source before scaling. | | `visible` | _bool_ | If the source is visible. | +| `muted` | _bool_ | If the source is muted. | | `locked` | _bool_ | If the source's transform is locked. | | `bounds.type` | _String_ | Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE". | | `bounds.alignment` | _int_ | Alignment of the bounding box. | @@ -1800,6 +1890,9 @@ Coordinates are relative to the item's parent (the scene or group it belongs to) | `sourceHeight` | _int_ | Base source (without scaling) of the source | | `width` | _double_ | Scene item width (base source width multiplied by the horizontal scaling factor) | | `height` | _double_ | Scene item height (base source height multiplied by the vertical scaling factor) | +| `alignment` | _int_ | The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis. | +| `parentGroupName` | _String (optional)_ | Name of the item's parent (if this item belongs to a group) | +| `groupChildren` | _Array<SceneItemTransform> (optional)_ | List of children (if this item is a group) | --- @@ -2903,9 +2996,9 @@ Will return an `error` if streaming is already active. | `stream.settings` | _Object (optional)_ | Settings for the stream. | | `stream.settings.server` | _String (optional)_ | The publish URL. | | `stream.settings.key` | _String (optional)_ | The publish key of the stream. | -| `stream.settings.use-auth` | _boolean (optional)_ | Indicates whether authentication should be used when connecting to the streaming server. | -| `stream.settings.username` | _String (optional)_ | If authentication is enabled, the username for the streaming server. Ignored if `use-auth` is not set to `true`. | -| `stream.settings.password` | _String (optional)_ | If authentication is enabled, the password for the streaming server. Ignored if `use-auth` is not set to `true`. | +| `stream.settings.use_auth` | _boolean (optional)_ | Indicates whether authentication should be used when connecting to the streaming server. | +| `stream.settings.username` | _String (optional)_ | If authentication is enabled, the username for the streaming server. Ignored if `use_auth` is not set to `true`. | +| `stream.settings.password` | _String (optional)_ | If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`. | **Response Items:** @@ -2947,7 +3040,7 @@ Sets one or more attributes of the current streaming server settings. Any option | `settings` | _Object_ | The actual settings of the stream. | | `settings.server` | _String (optional)_ | The publish URL. | | `settings.key` | _String (optional)_ | The publish key. | -| `settings.use-auth` | _boolean (optional)_ | Indicates whether authentication should be used when connecting to the streaming server. | +| `settings.use_auth` | _boolean (optional)_ | Indicates whether authentication should be used when connecting to the streaming server. | | `settings.username` | _String (optional)_ | The username for the streaming service. | | `settings.password` | _String (optional)_ | The password for the streaming service. | | `save` | _boolean_ | Persist the settings to disk. | @@ -2978,9 +3071,9 @@ _No specified parameters._ | `settings` | _Object_ | Stream settings object. | | `settings.server` | _String_ | The publish URL. | | `settings.key` | _String_ | The publish key of the stream. | -| `settings.use-auth` | _boolean_ | Indicates whether authentication should be used when connecting to the streaming server. | -| `settings.username` | _String_ | The username to use when accessing the streaming server. Only present if `use-auth` is `true`. | -| `settings.password` | _String_ | The password to use when accessing the streaming server. Only present if `use-auth` is `true`. | +| `settings.use_auth` | _boolean_ | Indicates whether authentication should be used when connecting to the streaming server. | +| `settings.username` | _String_ | The username to use when accessing the streaming server. Only present if `use_auth` is `true`. | +| `settings.password` | _String_ | The password to use when accessing the streaming server. Only present if `use_auth` is `true`. | --- diff --git a/src/Utils.cpp b/src/Utils.cpp index cb381d0f..978162be 100644 --- a/src/Utils.cpp +++ b/src/Utils.cpp @@ -104,9 +104,11 @@ obs_data_array_t* Utils::GetSceneItems(obs_source_t* source) { * @typedef {Object} `SceneItem` An OBS Scene Item. * @property {Number} `cy` * @property {Number} `cx` + * @property {Number} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis. * @property {String} `name` The name of this Scene Item. * @property {int} `id` Scene item ID * @property {Boolean} `render` Whether or not this Scene Item is set to "visible". + * @property {Boolean} `muted` Whether or not this Scene Item is muted. * @property {Boolean} `locked` Whether or not this Scene Item is locked and can't be moved around * @property {Number} `source_cx` * @property {Number} `source_cy` @@ -146,6 +148,8 @@ obs_data_t* Utils::GetSceneItemData(obs_sceneitem_t* item) { obs_data_set_double(data, "y", pos.y); obs_data_set_int(data, "source_cx", (int)item_width); obs_data_set_int(data, "source_cy", (int)item_height); + obs_data_set_bool(data, "muted", obs_source_muted(itemSource)); + obs_data_set_int(data, "alignment", (int)obs_sceneitem_get_alignment(item)); obs_data_set_double(data, "cx", item_width * scale.x); obs_data_set_double(data, "cy", item_height * scale.y); obs_data_set_bool(data, "render", obs_sceneitem_visible(item)); @@ -387,6 +391,13 @@ int Utils::GetTransitionDuration(obs_source_t* transition) { return 0; } + if (obs_transition_fixed(transition)) { + // If this transition has a fixed duration (such as a Stinger), + // we don't currently have a way of retrieving that number. + // For now, return -1 to indicate that we don't know the actual duration. + return -1; + } + OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition); OBSDataAutoRelease destinationSettings = obs_source_get_private_settings(destinationScene); @@ -413,6 +424,37 @@ bool Utils::SetTransitionByName(QString transitionName) { } } +obs_data_t* Utils::GetTransitionData(obs_source_t* transition) { + int duration = Utils::GetTransitionDuration(transition); + if (duration < 0) { + blog(LOG_WARNING, "GetTransitionData: duration is negative !"); + } + + OBSSourceAutoRelease sourceScene = obs_transition_get_source(transition, OBS_TRANSITION_SOURCE_A); + OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition); + + obs_data_t* transitionData = obs_data_create(); + obs_data_set_string(transitionData, "name", obs_source_get_name(transition)); + obs_data_set_string(transitionData, "type", obs_source_get_id(transition)); + obs_data_set_int(transitionData, "duration", duration); + + // When a transition starts and while it is running, SOURCE_A is the source scene + // and SOURCE_B is the destination scene. + // Before the transition_end event is triggered on a transition, the destination scene + // goes into SOURCE_A and SOURCE_B becomes null. This means that, in transition_stop + // we don't know what was the source scene + // TODO fix this in libobs + + bool isTransitionEndEvent = (sourceScene == destinationScene); + if (!isTransitionEndEvent) { + obs_data_set_string(transitionData, "from-scene", obs_source_get_name(sourceScene)); + } + + obs_data_set_string(transitionData, "to-scene", obs_source_get_name(destinationScene)); + + return transitionData; +} + QString Utils::OBSVersionString() { uint32_t version = obs_get_version(); @@ -787,41 +829,6 @@ void getPauseRecordingFunctions(RecordingPausedFunction* recPausedFuncPtr, Pause if (pauseRecFuncPtr) { *pauseRecFuncPtr = (PauseRecordingFunction)os_dlsym(frontendApi, "obs_frontend_recording_pause"); } - - os_dlclose(frontendApi); -} - -bool Utils::RecordingPauseSupported() -{ - RecordingPausedFunction recordingPaused = nullptr; - PauseRecordingFunction pauseRecording = nullptr; - getPauseRecordingFunctions(&recordingPaused, &pauseRecording); - - return (recordingPaused && pauseRecording); -} - -bool Utils::RecordingPaused() -{ - RecordingPausedFunction recordingPaused = nullptr; - getPauseRecordingFunctions(&recordingPaused, nullptr); - - if (recordingPaused == nullptr) { - return false; - } - - return recordingPaused(); -} - -void Utils::PauseRecording(bool pause) -{ - PauseRecordingFunction pauseRecording = nullptr; - getPauseRecordingFunctions(nullptr, &pauseRecording); - - if (pauseRecording == nullptr) { - return; - } - - pauseRecording(pause); } QString Utils::nsToTimestamp(uint64_t ns) diff --git a/src/Utils.h b/src/Utils.h index 4259c2e6..5934ad4e 100644 --- a/src/Utils.h +++ b/src/Utils.h @@ -60,6 +60,7 @@ namespace Utils { int GetTransitionDuration(obs_source_t* transition); obs_source_t* GetTransitionFromName(QString transitionName); bool SetTransitionByName(QString transitionName); + obs_data_t* GetTransitionData(obs_source_t* transition); QString OBSVersionString(); @@ -82,9 +83,5 @@ namespace Utils { const char* GetFilenameFormatting(); bool SetFilenameFormatting(const char* filenameFormatting); - bool RecordingPauseSupported(); - bool RecordingPaused(); - void PauseRecording(bool pause); - QString nsToTimestamp(uint64_t ns); }; diff --git a/src/WSEvents.cpp b/src/WSEvents.cpp index ab9cd1d2..99d823a6 100644 --- a/src/WSEvents.cpp +++ b/src/WSEvents.cpp @@ -122,7 +122,7 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private switch (event) { case OBS_FRONTEND_EVENT_FINISHED_LOADING: - owner->hookTransitionBeginEvent(); + owner->hookTransitionPlaybackEvents(); break; case OBS_FRONTEND_EVENT_SCENE_CHANGED: @@ -134,7 +134,7 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private break; case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED: - owner->hookTransitionBeginEvent(); + owner->hookTransitionPlaybackEvents(); owner->OnSceneCollectionChange(); break; @@ -147,7 +147,7 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private break; case OBS_FRONTEND_EVENT_TRANSITION_LIST_CHANGED: - owner->hookTransitionBeginEvent(); + owner->hookTransitionPlaybackEvents(); owner->OnTransitionListChange(); break; @@ -231,7 +231,7 @@ void WSEvents::FrontendEventHandler(enum obs_frontend_event event, void* private break; case OBS_FRONTEND_EVENT_EXIT: - owner->unhookTransitionBeginEvent(); + owner->unhookTransitionPlaybackEvents(); owner->OnExit(); break; } @@ -282,6 +282,8 @@ void WSEvents::connectSourceSignals(obs_source_t* source) { signal_handler_connect(sh, "item_remove", OnSceneItemDelete, this); signal_handler_connect(sh, "item_visible", OnSceneItemVisibilityChanged, this); + signal_handler_connect(sh, + "item_locked", OnSceneItemLockChanged, this); signal_handler_connect(sh, "item_transform", OnSceneItemTransform, this); signal_handler_connect(sh, "item_select", OnSceneItemSelected, this); signal_handler_connect(sh, "item_deselect", OnSceneItemDeselected, this); @@ -311,11 +313,15 @@ void WSEvents::disconnectSourceSignals(obs_source_t* source) { signal_handler_disconnect(sh, "item_remove", OnSceneItemDelete, this); signal_handler_disconnect(sh, "item_visible", OnSceneItemVisibilityChanged, this); + signal_handler_disconnect(sh, + "item_locked", OnSceneItemLockChanged, this); signal_handler_disconnect(sh, "item_transform", OnSceneItemTransform, this); signal_handler_disconnect(sh, "item_select", OnSceneItemSelected, this); signal_handler_disconnect(sh, "item_deselect", OnSceneItemDeselected, this); signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this); + signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this); + signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this); } void WSEvents::connectFilterSignals(obs_source_t* filter) { @@ -338,7 +344,7 @@ void WSEvents::disconnectFilterSignals(obs_source_t* filter) { signal_handler_disconnect(sh, "enable", OnSourceFilterVisibilityChanged, this); } -void WSEvents::hookTransitionBeginEvent() { +void WSEvents::hookTransitionPlaybackEvents() { obs_frontend_source_list transitions = {}; obs_frontend_get_transitions(&transitions); @@ -347,12 +353,16 @@ void WSEvents::hookTransitionBeginEvent() { signal_handler_t* sh = obs_source_get_signal_handler(transition); signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this); signal_handler_connect(sh, "transition_start", OnTransitionBegin, this); + signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this); + signal_handler_connect(sh, "transition_stop", OnTransitionEnd, this); + signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this); + signal_handler_connect(sh, "transition_video_stop", OnTransitionVideoEnd, this); } obs_frontend_source_list_free(&transitions); } -void WSEvents::unhookTransitionBeginEvent() { +void WSEvents::unhookTransitionPlaybackEvents() { obs_frontend_source_list transitions = {}; obs_frontend_get_transitions(&transitions); @@ -360,6 +370,8 @@ void WSEvents::unhookTransitionBeginEvent() { obs_source_t* transition = transitions.sources.array[i]; signal_handler_t* sh = obs_source_get_signal_handler(transition); signal_handler_disconnect(sh, "transition_start", OnTransitionBegin, this); + signal_handler_disconnect(sh, "transition_stop", OnTransitionEnd, this); + signal_handler_disconnect(sh, "transition_video_stop", OnTransitionVideoEnd, this); } obs_frontend_source_list_free(&transitions); @@ -742,7 +754,7 @@ void WSEvents::OnExit() { void WSEvents::StreamStatus() { bool streamingActive = obs_frontend_streaming_active(); bool recordingActive = obs_frontend_recording_active(); - bool recordingPaused = Utils::RecordingPaused(); + bool recordingPaused = obs_frontend_recording_paused(); bool replayBufferActive = obs_frontend_replay_buffer_active(); OBSOutputAutoRelease streamOutput = obs_frontend_get_streaming_output(); @@ -826,7 +838,7 @@ void WSEvents::Heartbeat() { bool streamingActive = obs_frontend_streaming_active(); bool recordingActive = obs_frontend_recording_active(); - bool recordingPaused = Utils::RecordingPaused(); + bool recordingPaused = obs_frontend_recording_paused(); OBSDataAutoRelease data = obs_data_create(); OBSOutputAutoRelease recordOutput = obs_frontend_get_recording_output(); @@ -885,7 +897,9 @@ void WSEvents::TransitionDurationChanged(int ms) { * * @return {String} `name` Transition name. * @return {String} `type` Transition type. - * @return {int} `duration` Transition duration (in milliseconds). + * @return {int} `duration` Transition duration (in milliseconds). + * Will be -1 for any transition with a fixed duration, + * such as a Stinger, due to limitations of the OBS API. * @return {String} `from-scene` Source scene of the transition * @return {String} `to-scene` Destination scene of the transition * @@ -902,29 +916,62 @@ void WSEvents::OnTransitionBegin(void* param, calldata_t* data) { return; } - int duration = Utils::GetTransitionDuration(transition); - if (duration < 0) { - blog(LOG_WARNING, "OnTransitionBegin: duration is negative !"); - } - - OBSDataAutoRelease fields = obs_data_create(); - obs_data_set_string(fields, "name", obs_source_get_name(transition)); - obs_data_set_string(fields, "type", obs_source_get_id(transition)); - obs_data_set_int(fields, "duration", duration); - - OBSSourceAutoRelease sourceScene = obs_transition_get_source(transition, OBS_TRANSITION_SOURCE_A); - if (sourceScene) { - obs_data_set_string(fields, "from-scene", obs_source_get_name(sourceScene)); - } - - OBSSourceAutoRelease destinationScene = obs_transition_get_active_source(transition); - if (destinationScene) { - obs_data_set_string(fields, "to-scene", obs_source_get_name(destinationScene)); - } - + OBSDataAutoRelease fields = Utils::GetTransitionData(transition); instance->broadcastUpdate("TransitionBegin", fields); } +/** +* A transition (other than "cut") has ended. +* Please note that the `from-scene` field is not available in TransitionEnd. +* +* @return {String} `name` Transition name. +* @return {String} `type` Transition type. +* @return {int} `duration` Transition duration (in milliseconds). +* @return {String} `to-scene` Destination scene of the transition +* +* @api events +* @name TransitionEnd +* @category transitions +* @since 4.8.0 +*/ +void WSEvents::OnTransitionEnd(void* param, calldata_t* data) { + auto instance = reinterpret_cast(param); + + OBSSource transition = calldata_get_pointer(data, "source"); + if (!transition) { + return; + } + + OBSDataAutoRelease fields = Utils::GetTransitionData(transition); + instance->broadcastUpdate("TransitionEnd", fields); +} + +/** +* A stinger transition has finished playing its video. +* +* @return {String} `name` Transition name. +* @return {String} `type` Transition type. +* @return {int} `duration` Transition duration (in milliseconds). +* @return {String} `from-scene` Source scene of the transition +* @return {String} `to-scene` Destination scene of the transition +* +* @api events +* @name TransitionVideoEnd +* @category transitions +* @since 4.8.0 +*/ +void WSEvents::OnTransitionVideoEnd(void* param, calldata_t* data) { + auto instance = reinterpret_cast(param); + + OBSSource transition = calldata_get_pointer(data, "source"); + if (!transition) { + return; + } + + OBSDataAutoRelease fields = Utils::GetTransitionData(transition); + instance->broadcastUpdate("TransitionVideoEnd", fields); +} + /** * A source has been created. A source can be an input, a scene or a transition. * @@ -1435,6 +1482,44 @@ void WSEvents::OnSceneItemVisibilityChanged(void* param, calldata_t* data) { instance->broadcastUpdate("SceneItemVisibilityChanged", fields); } +/** + * An item's locked status has been toggled. + * + * @return {String} `scene-name` Name of the scene. + * @return {String} `item-name` Name of the item in the scene. + * @return {int} `item-id` Scene item ID + * @return {boolean} `item-locked` New locked state of the item. + * + * @api events + * @name SceneItemLockChanged + * @category sources + * @since unreleased + */ +void WSEvents::OnSceneItemLockChanged(void* param, calldata_t* data) { + auto instance = reinterpret_cast(param); + + obs_scene_t* scene = nullptr; + calldata_get_ptr(data, "scene", &scene); + + obs_sceneitem_t* sceneItem = nullptr; + calldata_get_ptr(data, "item", &sceneItem); + + bool locked = false; + calldata_get_bool(data, "locked", &locked); + + const char* sceneName = + obs_source_get_name(obs_scene_get_source(scene)); + const char* sceneItemName = + obs_source_get_name(obs_sceneitem_get_source(sceneItem)); + + OBSDataAutoRelease fields = obs_data_create(); + obs_data_set_string(fields, "scene-name", sceneName); + obs_data_set_string(fields, "item-name", sceneItemName); + obs_data_set_int(fields, "item-id", obs_sceneitem_get_id(sceneItem)); + obs_data_set_bool(fields, "item-locked", locked); + instance->broadcastUpdate("SceneItemLockChanged", fields); +} + /** * An item's transform has been changed. * diff --git a/src/WSEvents.h b/src/WSEvents.h index 684fe654..06ce7f2c 100644 --- a/src/WSEvents.h +++ b/src/WSEvents.h @@ -43,8 +43,8 @@ public: void connectFilterSignals(obs_source_t* filter); void disconnectFilterSignals(obs_source_t* filter); - void hookTransitionBeginEvent(); - void unhookTransitionBeginEvent(); + void hookTransitionPlaybackEvents(); + void unhookTransitionPlaybackEvents(); uint64_t getStreamingTime(); uint64_t getRecordingTime(); @@ -116,6 +116,8 @@ private: enum obs_frontend_event event, void* privateData); static void OnTransitionBegin(void* param, calldata_t* data); + static void OnTransitionEnd(void* param, calldata_t* data); + static void OnTransitionVideoEnd(void* param, calldata_t* data); static void OnSourceCreate(void* param, calldata_t* data); static void OnSourceDestroy(void* param, calldata_t* data); @@ -136,6 +138,7 @@ private: static void OnSceneItemAdd(void* param, calldata_t* data); static void OnSceneItemDelete(void* param, calldata_t* data); static void OnSceneItemVisibilityChanged(void* param, calldata_t* data); + static void OnSceneItemLockChanged(void* param, calldata_t* data); static void OnSceneItemTransform(void* param, calldata_t* data); static void OnSceneItemSelected(void* param, calldata_t* data); static void OnSceneItemDeselected(void* param, calldata_t* data); diff --git a/src/WSRequestHandler.cpp b/src/WSRequestHandler.cpp index 0f10fad6..f2ae6c47 100644 --- a/src/WSRequestHandler.cpp +++ b/src/WSRequestHandler.cpp @@ -36,6 +36,7 @@ const QHash WSRequestHandler::messageMap { { "GetStats", &WSRequestHandler::GetStats }, { "SetHeartbeat", &WSRequestHandler::SetHeartbeat }, { "GetVideoInfo", &WSRequestHandler::GetVideoInfo }, + { "OpenProjector", &WSRequestHandler::OpenProjector }, { "SetFilenameFormatting", &WSRequestHandler::SetFilenameFormatting }, { "GetFilenameFormatting", &WSRequestHandler::GetFilenameFormatting }, diff --git a/src/WSRequestHandler.h b/src/WSRequestHandler.h index da93330b..d17384ee 100644 --- a/src/WSRequestHandler.h +++ b/src/WSRequestHandler.h @@ -54,6 +54,7 @@ class WSRequestHandler { RpcResponse GetStats(const RpcRequest&); RpcResponse SetHeartbeat(const RpcRequest&); RpcResponse GetVideoInfo(const RpcRequest&); + RpcResponse OpenProjector(const RpcRequest&); RpcResponse SetFilenameFormatting(const RpcRequest&); RpcResponse GetFilenameFormatting(const RpcRequest&); diff --git a/src/WSRequestHandler_General.cpp b/src/WSRequestHandler_General.cpp index bab1a6e0..56eeec8b 100644 --- a/src/WSRequestHandler_General.cpp +++ b/src/WSRequestHandler_General.cpp @@ -1,10 +1,13 @@ +#include "WSRequestHandler.h" + +#include +#include + #include "obs-websocket.h" #include "Config.h" #include "Utils.h" #include "WSEvents.h" -#include "WSRequestHandler.h" - #define CASE(x) case x: return #x; const char *describe_output_format(int format) { switch (format) { @@ -60,6 +63,7 @@ const char *describe_scale_type(int scale) { * @return {String} `obs-websocket-version` obs-websocket plugin version. * @return {String} `obs-studio-version` OBS Studio program version. * @return {String} `available-requests` List of available request types, formatted as a comma-separated list string (e.g. : "Method1,Method2,Method3"). + * @return {String} `supported-image-export-formats` List of supported formats for features that use image export (like the TakeSourceScreenshot request type) formatted as a comma-separated list string * * @api requests * @name GetVersion @@ -70,20 +74,28 @@ RpcResponse WSRequestHandler::GetVersion(const RpcRequest& request) { QString obsVersion = Utils::OBSVersionString(); QList names = messageMap.keys(); - names.sort(Qt::CaseInsensitive); + QList imageWriterFormats = QImageWriter::supportedImageFormats(); // (Palakis) OBS' data arrays only support object arrays, so I improvised. QString requests; + names.sort(Qt::CaseInsensitive); requests += names.takeFirst(); - for (QString reqName : names) { + for (const QString& reqName : names) { requests += ("," + reqName); } + QString supportedImageExportFormats; + supportedImageExportFormats += QString::fromUtf8(imageWriterFormats.takeFirst()); + for (const QByteArray& format : imageWriterFormats) { + supportedImageExportFormats += ("," + QString::fromUtf8(format)); + } + OBSDataAutoRelease data = obs_data_create(); obs_data_set_double(data, "version", 1.1); obs_data_set_string(data, "obs-websocket-version", OBS_WEBSOCKET_VERSION); obs_data_set_string(data, "obs-studio-version", obsVersion.toUtf8()); obs_data_set_string(data, "available-requests", requests.toUtf8()); + obs_data_set_string(data, "supported-image-export-formats", supportedImageExportFormats.toUtf8()); return request.success(data); } @@ -304,3 +316,31 @@ RpcResponse WSRequestHandler::GetVideoInfo(const RpcRequest& request) { return request.success(response); } + +/** + * Open a projector window or create a projector on a monitor. Requires OBS v24.0.4 or newer. + * + * @param {String (Optional)} `type` Type of projector: Preview (default), Source, Scene, StudioProgram, or Multiview (case insensitive). + * @param {int (Optional)} `monitor` Monitor to open the projector on. If -1 or omitted, opens a window. + * @param {String (Optional)} `geometry` Size and position of the projector window (only if monitor is -1). Encoded in Base64 using Qt's geometry encoding (https://doc.qt.io/qt-5/qwidget.html#saveGeometry). Corresponds to OBS's saved projectors. + * @param {String (Optional)} `name` Name of the source or scene to be displayed (ignored for other projector types). + * + * @api requests + * @name OpenProjector + * @category general + * @since unreleased + */ +RpcResponse WSRequestHandler::OpenProjector(const RpcRequest& request) { + const char* type = obs_data_get_string(request.parameters(), "type"); + + int monitor = -1; + if (request.hasField("monitor")) { + monitor = obs_data_get_int(request.parameters(), "monitor"); + } + + const char* geometry = obs_data_get_string(request.parameters(), "geometry"); + const char* name = obs_data_get_string(request.parameters(), "name"); + + obs_frontend_open_projector(type, monitor, geometry, name); + return request.success(); +} diff --git a/src/WSRequestHandler_Recording.cpp b/src/WSRequestHandler_Recording.cpp index 330843d3..df2ed4a0 100644 --- a/src/WSRequestHandler_Recording.cpp +++ b/src/WSRequestHandler_Recording.cpp @@ -10,10 +10,6 @@ RpcResponse ifCanPause(const RpcRequest& request, std::function c return request.failed("recording is not active"); } - if (!Utils::RecordingPauseSupported()) { - return request.failed("recording pauses are not available in this version of OBS Studio"); - } - return callback(); } @@ -77,11 +73,11 @@ RpcResponse WSRequestHandler::StartRecording(const RpcRequest& request) { */ RpcResponse WSRequestHandler::PauseRecording(const RpcRequest& request) { return ifCanPause(request, [request]() { - if (Utils::RecordingPaused()) { + if (obs_frontend_recording_paused()) { return request.failed("recording already paused"); } - Utils::PauseRecording(true); + obs_frontend_recording_pause(true); return request.success(); }); } @@ -97,11 +93,11 @@ RpcResponse WSRequestHandler::PauseRecording(const RpcRequest& request) { */ RpcResponse WSRequestHandler::ResumeRecording(const RpcRequest& request) { return ifCanPause(request, [request]() { - if (!Utils::RecordingPaused()) { + if (!obs_frontend_recording_paused()) { return request.failed("recording is not paused"); } - Utils::PauseRecording(false); + obs_frontend_recording_pause(false); return request.success(); }); } diff --git a/src/WSRequestHandler_SceneItems.cpp b/src/WSRequestHandler_SceneItems.cpp index cd54de16..5e736802 100644 --- a/src/WSRequestHandler_SceneItems.cpp +++ b/src/WSRequestHandler_SceneItems.cpp @@ -21,6 +21,7 @@ * @return {int} `crop.bottom` The number of pixels cropped off the bottom of the source before scaling. * @return {int} `crop.left` The number of pixels cropped off the left of the source before scaling. * @return {bool} `visible` If the source is visible. +* @return {bool} `muted` If the source is muted. * @return {bool} `locked` If the source's transform is locked. * @return {String} `bounds.type` Type of bounding box. Can be "OBS_BOUNDS_STRETCH", "OBS_BOUNDS_SCALE_INNER", "OBS_BOUNDS_SCALE_OUTER", "OBS_BOUNDS_SCALE_TO_WIDTH", "OBS_BOUNDS_SCALE_TO_HEIGHT", "OBS_BOUNDS_MAX_ONLY" or "OBS_BOUNDS_NONE". * @return {int} `bounds.alignment` Alignment of the bounding box. @@ -30,8 +31,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) -* @property {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group) -* @property {Array (optional)} `groupChildren` List of children (if this item is a group) +* @return {int} `alignment` The point on the source that the item is manipulated from. The sum of 1=Left or 2=Right, and 4=Top or 8=Bottom, or omit to center on that axis. +* @return {String (optional)} `parentGroupName` Name of the item's parent (if this item belongs to a group) +* @return {Array (optional)} `groupChildren` List of children (if this item is a group) * * @api requests * @name GetSceneItemProperties diff --git a/src/WSRequestHandler_Sources.cpp b/src/WSRequestHandler_Sources.cpp index 2feca185..e043ee0e 100644 --- a/src/WSRequestHandler_Sources.cpp +++ b/src/WSRequestHandler_Sources.cpp @@ -335,7 +335,7 @@ RpcResponse WSRequestHandler::SetSyncOffset(const RpcRequest& request) QString sourceName = obs_data_get_string(request.parameters(), "source"); int64_t sourceSyncOffset = (int64_t)obs_data_get_int(request.parameters(), "offset"); - if (sourceName.isEmpty() || sourceSyncOffset < 0) { + if (sourceName.isEmpty()) { return request.failed("invalid request parameters"); } diff --git a/src/WSRequestHandler_Streaming.cpp b/src/WSRequestHandler_Streaming.cpp index 4ee77d8b..744a691f 100644 --- a/src/WSRequestHandler_Streaming.cpp +++ b/src/WSRequestHandler_Streaming.cpp @@ -26,7 +26,7 @@ RpcResponse WSRequestHandler::GetStreamingStatus(const RpcRequest& request) { OBSDataAutoRelease data = obs_data_create(); obs_data_set_bool(data, "streaming", obs_frontend_streaming_active()); obs_data_set_bool(data, "recording", obs_frontend_recording_active()); - obs_data_set_bool(data, "recording-paused", Utils::RecordingPaused()); + obs_data_set_bool(data, "recording-paused", obs_frontend_recording_paused()); obs_data_set_bool(data, "preview-only", false); if (obs_frontend_streaming_active()) { @@ -67,9 +67,9 @@ RpcResponse WSRequestHandler::StartStopStreaming(const RpcRequest& request) { * @param {Object (optional)} `stream.settings` Settings for the stream. * @param {String (optional)} `stream.settings.server` The publish URL. * @param {String (optional)} `stream.settings.key` The publish key of the stream. - * @param {boolean (optional)} `stream.settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server. - * @param {String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use-auth` is not set to `true`. - * @param {String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use-auth` is not set to `true`. + * @param {boolean (optional)} `stream.settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server. + * @param {String (optional)} `stream.settings.username` If authentication is enabled, the username for the streaming server. Ignored if `use_auth` is not set to `true`. + * @param {String (optional)} `stream.settings.password` If authentication is enabled, the password for the streaming server. Ignored if `use_auth` is not set to `true`. * * @api requests * @name StartStreaming @@ -188,7 +188,7 @@ RpcResponse WSRequestHandler::StopStreaming(const RpcRequest& request) { * @param {Object} `settings` The actual settings of the stream. * @param {String (optional)} `settings.server` The publish URL. * @param {String (optional)} `settings.key` The publish key. - * @param {boolean (optional)} `settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server. + * @param {boolean (optional)} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server. * @param {String (optional)} `settings.username` The username for the streaming service. * @param {String (optional)} `settings.password` The password for the streaming service. * @param {boolean} `save` Persist the settings to disk. @@ -213,6 +213,7 @@ RpcResponse WSRequestHandler::SetStreamSettings(const RpcRequest& request) { OBSDataAutoRelease hotkeys = obs_hotkeys_save_service(service); service = obs_service_create( requestedType.toUtf8(), STREAM_SERVICE_ID, requestSettings, hotkeys); + obs_frontend_set_streaming_service(service); } else { // If type isn't changing, we should overlay the settings we got // to the existing settings. By doing so, you can send a request that @@ -235,10 +236,12 @@ RpcResponse WSRequestHandler::SetStreamSettings(const RpcRequest& request) { obs_frontend_save_streaming_service(); } - OBSDataAutoRelease serviceSettings = obs_service_get_settings(service); + OBSService responseService = obs_frontend_get_streaming_service(); + OBSDataAutoRelease serviceSettings = obs_service_get_settings(responseService); + const char* responseType = obs_service_get_type(responseService); OBSDataAutoRelease response = obs_data_create(); - obs_data_set_string(response, "type", requestedType.toUtf8()); + obs_data_set_string(response, "type", responseType); obs_data_set_obj(response, "settings", serviceSettings); return request.success(response); @@ -251,9 +254,9 @@ RpcResponse WSRequestHandler::SetStreamSettings(const RpcRequest& request) { * @return {Object} `settings` Stream settings object. * @return {String} `settings.server` The publish URL. * @return {String} `settings.key` The publish key of the stream. - * @return {boolean} `settings.use-auth` Indicates whether authentication should be used when connecting to the streaming server. - * @return {String} `settings.username` The username to use when accessing the streaming server. Only present if `use-auth` is `true`. - * @return {String} `settings.password` The password to use when accessing the streaming server. Only present if `use-auth` is `true`. + * @return {boolean} `settings.use_auth` Indicates whether authentication should be used when connecting to the streaming server. + * @return {String} `settings.username` The username to use when accessing the streaming server. Only present if `use_auth` is `true`. + * @return {String} `settings.password` The password to use when accessing the streaming server. Only present if `use_auth` is `true`. * * @api requests * @name GetStreamSettings diff --git a/src/WSRequestHandler_StudioMode.cpp b/src/WSRequestHandler_StudioMode.cpp index 67928ee4..fb958f3b 100644 --- a/src/WSRequestHandler_StudioMode.cpp +++ b/src/WSRequestHandler_StudioMode.cpp @@ -133,7 +133,11 @@ RpcResponse WSRequestHandler::TransitionToProgram(const RpcRequest& request) { * @since 4.1.0 */ RpcResponse WSRequestHandler::EnableStudioMode(const RpcRequest& request) { - obs_frontend_set_preview_program_mode(true); + obs_queue_task(OBS_TASK_UI, [](void* param) { + obs_frontend_set_preview_program_mode(true); + + UNUSED_PARAMETER(param); + }, nullptr, true); return request.success(); } @@ -146,7 +150,12 @@ RpcResponse WSRequestHandler::EnableStudioMode(const RpcRequest& request) { * @since 4.1.0 */ RpcResponse WSRequestHandler::DisableStudioMode(const RpcRequest& request) { - obs_frontend_set_preview_program_mode(false); + obs_queue_task(OBS_TASK_UI, [](void* param) { + obs_frontend_set_preview_program_mode(false); + + UNUSED_PARAMETER(param); + }, nullptr, true); + return request.success(); } @@ -159,7 +168,12 @@ RpcResponse WSRequestHandler::DisableStudioMode(const RpcRequest& request) { * @since 4.1.0 */ RpcResponse WSRequestHandler::ToggleStudioMode(const RpcRequest& request) { - bool previewProgramMode = obs_frontend_preview_program_mode_active(); - obs_frontend_set_preview_program_mode(!previewProgramMode); + obs_queue_task(OBS_TASK_UI, [](void* param) { + bool previewProgramMode = obs_frontend_preview_program_mode_active(); + obs_frontend_set_preview_program_mode(!previewProgramMode); + + UNUSED_PARAMETER(param); + }, nullptr, true); + return request.success(); } diff --git a/src/obs-websocket.h b/src/obs-websocket.h index 1a4dec48..5f118d28 100644 --- a/src/obs-websocket.h +++ b/src/obs-websocket.h @@ -56,6 +56,6 @@ ConfigPtr GetConfig(); WSServerPtr GetServer(); WSEventsPtr GetEventsSystem(); -#define OBS_WEBSOCKET_VERSION "4.7.0" +#define OBS_WEBSOCKET_VERSION "4.8.0" #define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)