Compare commits

..

36 Commits

Author SHA1 Message Date
333737f400 Requests: Additions and code cleanup 2021-09-02 19:29:13 -07:00
82d8a3d7ce Utils: Add stuff to Obs 2021-09-02 19:28:55 -07:00
d7f96b6dea Revert "Request: Minor code cleanup"
This reverts commit c16669c7b0.
2021-09-02 13:33:20 -07:00
c16669c7b0 Request: Minor code cleanup 2021-09-02 13:25:17 -07:00
0269209d59 Requests: Use OBS naming for monitorType enum 2021-09-02 11:24:48 -07:00
d2d2bdd730 docs(ci): Update generated docs - 9a8587d [skip ci] 2021-09-01 17:44:19 +00:00
9a8587d6df Requests: Check for last scene in RemoveScene 2021-09-01 10:43:36 -07:00
8a45560297 Utils: Add GetSceneCount() 2021-09-01 10:43:24 -07:00
fb0656c31e RequestHandler: Simplify request statuses 2021-09-01 10:30:40 -07:00
26bef074ac EventHandler: Add (disabled) InputAudioMonitorTypeChanged event 2021-08-31 06:39:09 -07:00
e18aaff661 RequestStatus: Add new statuses to replace old soon 2021-08-31 06:38:55 -07:00
4271730dc2 Requests: Add more requests 2021-08-30 13:55:22 -07:00
b86107a699 Base: Add OBSPropertiesAutoDestroy 2021-08-30 13:55:05 -07:00
6035f258d2 docs(ci): Update generated docs - a40160e [skip ci] 2021-08-30 17:01:00 +00:00
a40160e305 WebSocketServer: Use Sec-WebSocket-Protocol for json/msgpack 2021-08-30 09:59:59 -07:00
b58f6e8366 WebSocketServer: Remove unnecessary string usage 2021-08-30 04:46:59 -07:00
670fa7c249 CI: More fixes 2021-08-30 04:15:55 -07:00
d858118e28 CI: Fix oopsies 2021-08-30 03:42:32 -07:00
bb71a4c77b CI: Prefix checkinstall version to avoid errors 2021-08-30 03:29:30 -07:00
3dfd091e71 CI: Provide package version suffix to cmake 2021-08-30 03:17:09 -07:00
a3d0ff5eea CMakeLists: More changes for version suffix 2021-08-30 03:08:09 -07:00
5988f0f97a Installer: Use OBS_WEBSOCKET_VERSION instead of CMAKE_PROJECT_VERSION 2021-08-30 02:52:21 -07:00
eb6015df05 Base: Remove generated installer file 2021-08-30 02:51:35 -07:00
15188e3ebe GitIgnore: Ignore generated installer file 2021-08-30 02:51:14 -07:00
1ecf2a4fdb CMakeLists: Allow specifying version suffix string 2021-08-30 02:50:02 -07:00
292b2b0d3b Base: Remove generated plugin macros file 2021-08-30 02:49:43 -07:00
b3676586e4 GitIgnore: Ignore generated plugin macros 2021-08-30 02:49:07 -07:00
6d882ba94f Merge branch 'master' of https://github.com/Palakis/obs-websocket 2021-08-30 02:31:03 -07:00
d669db24ac CI: Update some stuff 2021-08-30 02:30:48 -07:00
fc8dce45ee docs(ci): Update generated docs - ac78acd [skip ci] 2021-08-30 08:47:29 +00:00
ac78acd28c CI: Try to fix tag recognition (again) 2021-08-30 01:46:52 -07:00
f37edbd71c EventHandler: Include required event intent in emit 2021-08-30 01:39:46 -07:00
78f9c93739 Requests: Fix global realm storage on persistent data req's 2021-08-30 01:32:22 -07:00
96c5818395 ConnectInfo: Update QR code format 2021-08-30 01:11:29 -07:00
85fa41962d Merge branch 'master' of https://github.com/Palakis/obs-websocket 2021-08-29 17:02:29 -07:00
ccb42f1f0c README: Add simpleobsws
It's finally in beta, so we can go ahead and add it as officially
supporting 5.0
2021-08-28 05:33:06 -07:00
32 changed files with 956 additions and 875 deletions

View File

@ -1,356 +1,357 @@
name: "CI Multiplatform Build" name: "CI Multiplatform Build"
on: on:
push: push:
paths-ignore: paths-ignore:
- 'docs/**' - 'docs/**'
branches: branches:
- master - master
tags: tags:
- '[45].[0-9]+.[0-9]+*' - '[45].[0-9]+.[0-9]+*'
pull_request: pull_request:
paths-ignore: paths-ignore:
- 'docs/**' - 'docs/**'
- '**.md' - '**.md'
branches: branches:
- master - master
jobs: jobs:
windows: windows:
name: 'Windows 32/64-bit' name: 'Windows 32/64-bit'
runs-on: [windows-latest] runs-on: [windows-latest]
if: contains(github.event.head_commit.message, '[skip ci]') != true if: contains(github.event.head_commit.message, '[skip ci]') != true
env: env:
QT_CACHE_VERSION: '2' # Change whenever updating OBS dependencies URL, in order to force a cache reset QT_CACHE_VERSION: '2' # Change whenever updating OBS dependencies URL, in order to force a cache reset
QT_VERSION: '5.15.2' QT_VERSION: '5.15.2'
WINDOWS_DEPS_CACHE_VERSION: '1' # Change whenever updating Qt dependency URL, in order to force a cache reset WINDOWS_DEPS_CACHE_VERSION: '1' # Change whenever updating Qt dependency URL, in order to force a cache reset
WINDOWS_DEPS_VERSION: '2019' WINDOWS_DEPS_VERSION: '2019'
CMAKE_GENERATOR: "Visual Studio 16 2019" CMAKE_GENERATOR: "Visual Studio 16 2019"
CMAKE_SYSTEM_VERSION: "10.0" CMAKE_SYSTEM_VERSION: "10.0"
steps: steps:
- name: 'Add msbuild to PATH' - name: 'Add msbuild to PATH'
uses: microsoft/setup-msbuild@v1.0.2 uses: microsoft/setup-msbuild@v1.0.2
- name: 'Checkout obs-websocket' - name: 'Checkout obs-websocket'
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
path: ${{ github.workspace }}/obs-websocket path: ${{ github.workspace }}/obs-websocket
submodules: 'recursive' submodules: 'recursive'
- name: 'Checkout OBS-Studio' - name: 'Checkout OBS-Studio'
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
repository: obsproject/obs-studio repository: obsproject/obs-studio
path: ${{ github.workspace }}/obs-studio path: ${{ github.workspace }}/obs-studio
submodules: 'recursive' submodules: 'recursive'
- name: 'Get OBS-Studio Git Info' - name: 'Get OBS-Studio Git Info'
shell: bash shell: bash
working-directory: ${{ github.workspace }}/obs-studio working-directory: ${{ github.workspace }}/obs-studio
run: | run: |
git fetch --prune --unshallow git fetch --prune --unshallow
echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
- name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})' - name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})'
shell: bash shell: bash
working-directory: ${{ github.workspace }}/obs-studio working-directory: ${{ github.workspace }}/obs-studio
run: | run: |
git checkout ${{ env.OBS_GIT_TAG }} git checkout ${{ env.OBS_GIT_TAG }}
git submodule update git submodule update
- name: 'Get obs-websocket Git Info' - name: 'Get obs-websocket Git Info'
shell: bash shell: bash
working-directory: ${{ github.workspace }}/obs-websocket working-directory: ${{ github.workspace }}/obs-websocket
run: | run: |
git fetch --prune --unshallow git fetch --prune --unshallow
GIT_HASH=$(git rev-parse --short HEAD) GIT_HASH=$(git rev-parse --short HEAD)
echo "GIT_HASH=$GIT_HASH" >> $GITHUB_ENV echo "GIT_HASH=$GIT_HASH" >> $GITHUB_ENV
GIT_TAG=$(git describe --tags --abbrev=0) GIT_TAG=$(git describe --exact-match --tags --abbrev=0) || GIT_TAG=""
echo "GIT_TAG=$GIT_TAG" >> $GITHUB_ENV echo "GIT_TAG=$GIT_TAG" >> $GITHUB_ENV
if [ "$GIT_TAG" ] ; then \ if [ "$GIT_TAG" ] ; then \
VERSION="$GIT_TAG" ; \ VERSION="$GIT_TAG" \
else \ VERSION_SUFFIX=$(echo "$GIT_TAG" | cut -c6-20) ; \
VERSION="$GIT_HASH-git" ; \ else \
fi VERSION="$GIT_HASH-git" \
echo "PACKAGE_VERSION=$VERSION" >> $GITHUB_ENV VERSION_SUFFIX="-$GIT_HASH-git" ; \
- name: 'Restore Cached Qt' fi
id: qtcache echo "PACKAGE_VERSION=$VERSION" >> $GITHUB_ENV
uses: actions/cache@v2 echo "CMAKE_VERSION_SUFFIX=$VERSION_SUFFIX" >> $GITHUB_ENV
with: - name: 'Restore Cached Qt'
path: Qt_${{ env.QT_VERSION }}.7z id: qtcache
key: 'qtdep-${{ env.QT_CACHE_VERSION }} | ${{ runner.os }}' uses: actions/cache@v2
restore-keys: | with:
qtdep-${{ env.QT_CACHE_VERSION }} | ${{ runner.os }} path: Qt_${{ env.QT_VERSION }}.7z
- name: 'Download Prerequisite: Qt' key: 'qtdep-${{ env.QT_CACHE_VERSION }} | ${{ runner.os }}'
if: steps.qtcache.outputs.cache-hit != 'true' restore-keys: |
run: | qtdep-${{ env.QT_CACHE_VERSION }} | ${{ runner.os }}
curl -kLO https://tt2468.net/dl/Qt_${{ env.QT_VERSION }}.7z -f --retry 5 -C - - name: 'Download Prerequisite: Qt'
- name: 'Extract Prerequisite: Qt' if: steps.qtcache.outputs.cache-hit != 'true'
run: | run: |
7z x Qt_${{ env.QT_VERSION }}.7z -o"${{ github.workspace }}\cmbuild\QT" curl -kLO https://tt2468.net/dl/Qt_${{ env.QT_VERSION }}.7z -f --retry 5 -C -
- name: 'Restore Cached OBS-Studio Dependencies' - name: 'Extract Prerequisite: Qt'
id: obscache run: |
uses: actions/cache@v2 7z x Qt_${{ env.QT_VERSION }}.7z -o"${{ github.workspace }}\cmbuild\QT"
with: - name: 'Restore Cached OBS-Studio Dependencies'
path: ${{ github.workspace }}\cmbuild\deps\** id: obscache
key: 'obsdep-${{ env.WINDOWS_DEPS_CACHE_VERSION }} | ${{ runner.os }}' uses: actions/cache@v2
restore-keys: | with:
obsdep-${{ env.WINDOWS_DEPS_CACHE_VERSION }} | ${{ runner.os }} path: ${{ github.workspace }}\cmbuild\deps\**
- name: 'Install Prerequisite: Pre-built OBS-Studio dependencies' key: 'obsdep-${{ env.WINDOWS_DEPS_CACHE_VERSION }} | ${{ runner.os }}'
if: steps.obscache.outputs.cache-hit != 'true' restore-keys: |
run: | obsdep-${{ env.WINDOWS_DEPS_CACHE_VERSION }} | ${{ runner.os }}
curl -kLO https://cdn-fastly.obsproject.com/downloads/dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -f --retry 5 -C - - name: 'Install Prerequisite: Pre-built OBS-Studio dependencies'
7z x dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -o"${{ github.workspace }}\cmbuild\deps" if: steps.obscache.outputs.cache-hit != 'true'
- name: 'Restore OBS-Studio 32-bit Build v${{ env.OBS_GIT_TAG }} from Cache' run: |
id: build-cache-obs-32 curl -kLO https://cdn-fastly.obsproject.com/downloads/dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -f --retry 5 -C -
uses: actions/cache@v2 7z x dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -o"${{ github.workspace }}\cmbuild\deps"
env: - name: 'Restore OBS-Studio 32-bit Build v${{ env.OBS_GIT_TAG }} from Cache'
CACHE_NAME: 'build-cache-obs-32' id: build-cache-obs-32
with: uses: actions/cache@v2
path: ${{ github.workspace }}/obs-studio/build32 env:
key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }} CACHE_NAME: 'build-cache-obs-32'
restore-keys: | with:
${{ runner.os }}-${{ env.CACHE_NAME }}- path: ${{ github.workspace }}/obs-studio/build32
- name: 'Configure OBS-Studio 32-bit' key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }}
if: steps.build-cache-obs-32.outputs.cache-hit != 'true' restore-keys: |
working-directory: ${{ github.workspace }}/obs-studio ${{ runner.os }}-${{ env.CACHE_NAME }}-
run: | - name: 'Configure OBS-Studio 32-bit'
if(!(Test-Path -Path ".\build32")){New-Item -ItemType directory -Path .\build32} if: steps.build-cache-obs-32.outputs.cache-hit != 'true'
cd .\build32 working-directory: ${{ github.workspace }}/obs-studio
cmake -G "${{ env.CMAKE_GENERATOR }}" -A Win32 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win32" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES -DBUILD_BROWSER=OFF .. run: |
- name: 'Build OBS-Studio 32-bit' if(!(Test-Path -Path ".\build32")){New-Item -ItemType directory -Path .\build32}
if: steps.build-cache-obs-32.outputs.cache-hit != 'true' cd .\build32
working-directory: ${{ github.workspace }}/obs-studio cmake -G "${{ env.CMAKE_GENERATOR }}" -A Win32 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win32" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES -DBUILD_BROWSER=OFF ..
run: | - name: 'Build OBS-Studio 32-bit'
msbuild /m /p:Configuration=RelWithDebInfo .\build32\libobs\libobs.vcxproj if: steps.build-cache-obs-32.outputs.cache-hit != 'true'
msbuild /m /p:Configuration=RelWithDebInfo .\build32\UI\obs-frontend-api\obs-frontend-api.vcxproj working-directory: ${{ github.workspace }}/obs-studio
- name: 'Restore OBS-Studio 64-bit Build v${{ env.OBS_GIT_TAG }} from Cache' run: |
id: build-cache-obs-64 msbuild /m /p:Configuration=RelWithDebInfo .\build32\libobs\libobs.vcxproj
uses: actions/cache@v1 msbuild /m /p:Configuration=RelWithDebInfo .\build32\UI\obs-frontend-api\obs-frontend-api.vcxproj
env: - name: 'Restore OBS-Studio 64-bit Build v${{ env.OBS_GIT_TAG }} from Cache'
CACHE_NAME: 'build-cache-obs-64' id: build-cache-obs-64
with: uses: actions/cache@v1
path: ${{ github.workspace }}/obs-studio/build64 env:
key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }} CACHE_NAME: 'build-cache-obs-64'
restore-keys: | with:
${{ runner.os }}-${{ env.CACHE_NAME }}- path: ${{ github.workspace }}/obs-studio/build64
- name: 'Configure OBS-Studio 64-bit' key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }}
if: steps.build-cache-obs-64.outputs.cache-hit != 'true' restore-keys: |
working-directory: ${{ github.workspace }}/obs-studio ${{ runner.os }}-${{ env.CACHE_NAME }}-
run: | - name: 'Configure OBS-Studio 64-bit'
if(!(Test-Path -Path ".\build64")){New-Item -ItemType directory -Path .\build64} if: steps.build-cache-obs-64.outputs.cache-hit != 'true'
cd .\build64 working-directory: ${{ github.workspace }}/obs-studio
cmake -G "${{ env.CMAKE_GENERATOR }}" -A x64 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019_64" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win64" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES -DBUILD_BROWSER=OFF .. run: |
- name: 'Build OBS-Studio 64-bit' if(!(Test-Path -Path ".\build64")){New-Item -ItemType directory -Path .\build64}
if: steps.build-cache-obs-64.outputs.cache-hit != 'true' cd .\build64
working-directory: ${{ github.workspace }}/obs-studio cmake -G "${{ env.CMAKE_GENERATOR }}" -A x64 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019_64" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win64" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES -DBUILD_BROWSER=OFF ..
run: | - name: 'Build OBS-Studio 64-bit'
msbuild /m /p:Configuration=RelWithDebInfo .\build64\libobs\libobs.vcxproj if: steps.build-cache-obs-64.outputs.cache-hit != 'true'
msbuild /m /p:Configuration=RelWithDebInfo .\build64\UI\obs-frontend-api\obs-frontend-api.vcxproj working-directory: ${{ github.workspace }}/obs-studio
- name: 'Configure obs-websocket 32-bit' run: |
working-directory: ${{ github.workspace }}/obs-websocket msbuild /m /p:Configuration=RelWithDebInfo .\build64\libobs\libobs.vcxproj
run: | msbuild /m /p:Configuration=RelWithDebInfo .\build64\UI\obs-frontend-api\obs-frontend-api.vcxproj
mkdir .\build32 - name: 'Configure obs-websocket 32-bit'
cd .\build32 working-directory: ${{ github.workspace }}/obs-websocket
cmake -G "${{ env.CMAKE_GENERATOR }}" -A Win32 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019" -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" .. run: |
- name: 'Configure obs-websocket 64-bit' mkdir .\build32
working-directory: ${{ github.workspace }}/obs-websocket cd .\build32
run: | cmake -G "${{ env.CMAKE_GENERATOR }}" -A Win32 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019" -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" -DOBS_WEBSOCKET_VERSION_SUFFIX="${{ env.CMAKE_VERSION_SUFFIX }}" ..
mkdir .\build64 - name: 'Configure obs-websocket 64-bit'
cd .\build64 working-directory: ${{ github.workspace }}/obs-websocket
cmake -G "${{ env.CMAKE_GENERATOR }}" -A x64 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019_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" .. run: |
- name: 'Build obs-websocket 32-bit' mkdir .\build64
working-directory: ${{ github.workspace }}/obs-websocket cd .\build64
run: msbuild /m /p:Configuration=RelWithDebInfo .\build32\obs-websocket.sln cmake -G "${{ env.CMAKE_GENERATOR }}" -A x64 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019_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" -DOBS_WEBSOCKET_VERSION_SUFFIX="${{ env.CMAKE_VERSION_SUFFIX }}" ..
- name: 'Build obs-websocket 64-bit' - name: 'Build obs-websocket 32-bit'
working-directory: ${{ github.workspace }}/obs-websocket working-directory: ${{ github.workspace }}/obs-websocket
run: msbuild /m /p:Configuration=RelWithDebInfo .\build64\obs-websocket.sln run: msbuild /m /p:Configuration=RelWithDebInfo .\build32\obs-websocket.sln
- name: 'Set PR Artifact Filename' - name: 'Build obs-websocket 64-bit'
shell: bash working-directory: ${{ github.workspace }}/obs-websocket
run: | run: msbuild /m /p:Configuration=RelWithDebInfo .\build64\obs-websocket.sln
if [ "${{ env.GIT_TAG }}" ] ; then \ - name: 'Set PR Artifact Filename'
FILENAME="obs-websocket-${{ env.GIT_TAG }}-Windows" ; \ shell: bash
else \ run: |
FILENAME="obs-websocket-${{ env.GIT_HASH }}-git-Windows" ; \ echo "WIN_FILENAME=obs-websocket-${{ env.PACKAGE_VERSION }}-Windows" >> $GITHUB_ENV
fi - name: 'Package obs-websocket'
echo "WIN_FILENAME=$FILENAME" >> $GITHUB_ENV working-directory: ${{ github.workspace }}/obs-websocket
- name: 'Package obs-websocket' run: |
working-directory: ${{ github.workspace }}/obs-websocket mkdir package
run: | cd package
mkdir package 7z a "${{ env.WIN_FILENAME }}.zip" "..\release\*"
cd package iscc ..\installer\installer-windows.generated.iss /O. /F"${{ env.WIN_FILENAME }}-Installer"
7z a "${{ env.WIN_FILENAME }}.zip" "..\release\*" - name: 'Publish ${{ env.WIN_FILENAME }}.zip'
iscc ..\installer\installer-windows.generated.iss /O. /F"${{ env.WIN_FILENAME }}-Installer" if: success()
- name: 'Publish ${{ env.WIN_FILENAME }}.zip' uses: actions/upload-artifact@v2-preview
if: success() with:
uses: actions/upload-artifact@v2-preview name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-Windows'
with: path: ${{ github.workspace }}/obs-websocket/package/*.zip
name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-Windows' - name: 'Publish ${{ env.WIN_FILENAME }}-Installer.exe'
path: ${{ github.workspace }}/obs-websocket/package/*.zip if: success()
- name: 'Publish ${{ env.WIN_FILENAME }}-Installer.exe' uses: actions/upload-artifact@v2-preview
if: success() with:
uses: actions/upload-artifact@v2-preview name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-Windows-Installer'
with: path: ${{ github.workspace }}/obs-websocket/package/*.exe
name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-Windows-Installer' ubuntu64:
path: ${{ github.workspace }}/obs-websocket/package/*.exe name: "Linux/Ubuntu 64-bit"
ubuntu64: runs-on: [ubuntu-latest]
name: "Linux/Ubuntu 64-bit" if: contains(github.event.head_commit.message, '[skip ci]') != true
runs-on: [ubuntu-latest] steps:
if: contains(github.event.head_commit.message, '[skip ci]') != true - name: 'Checkout obs-websocket'
steps: uses: actions/checkout@v2
- name: 'Checkout obs-websocket' with:
uses: actions/checkout@v2 path: ${{ github.workspace }}/obs-websocket
with: submodules: 'recursive'
path: ${{ github.workspace }}/obs-websocket - name: 'Checkout OBS-Studio'
submodules: 'recursive' uses: actions/checkout@v2
- name: 'Checkout OBS-Studio' with:
uses: actions/checkout@v2 repository: obsproject/obs-studio
with: path: ${{ github.workspace }}/obs-studio
repository: obsproject/obs-studio submodules: 'recursive'
path: ${{ github.workspace }}/obs-studio - name: 'Get OBS-Studio Git Info'
submodules: 'recursive' shell: bash
- name: 'Get OBS-Studio Git Info' working-directory: ${{ github.workspace }}/obs-studio
shell: bash run: |
working-directory: ${{ github.workspace }}/obs-studio git fetch --prune --unshallow
run: | echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
git fetch --prune --unshallow echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})'
echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV shell: bash
- name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})' working-directory: ${{ github.workspace }}/obs-studio
shell: bash run: |
working-directory: ${{ github.workspace }}/obs-studio git checkout ${{ env.OBS_GIT_TAG }}
run: | git submodule update
git checkout ${{ env.OBS_GIT_TAG }} - name: 'Get obs-websocket git info'
git submodule update working-directory: ${{ github.workspace }}/obs-websocket
- name: 'Get obs-websocket git info' run: |
working-directory: ${{ github.workspace }}/obs-websocket git fetch --prune --unshallow
run: | GIT_HASH=$(git rev-parse --short HEAD)
git fetch --prune --unshallow echo "GIT_HASH=$GIT_HASH" >> $GITHUB_ENV
GIT_HASH=$(git rev-parse --short HEAD) GIT_TAG=$(git describe --exact-match --tags --abbrev=0) || GIT_TAG=""
echo "GIT_HASH=$GIT_HASH" >> $GITHUB_ENV echo "GIT_TAG=$GIT_TAG" >> $GITHUB_ENV
GIT_TAG=$(git describe --tags --abbrev=0) if [ "$GIT_TAG" ] ; then \
echo "GIT_TAG=$GIT_TAG" >> $GITHUB_ENV VERSION="$GIT_TAG" \
if [ "$GIT_TAG" ] ; then \ VERSION_SUFFIX=$(echo "$GIT_TAG" | cut -c6-20) ; \
VERSION="$GIT_TAG" ; \ else \
else \ VERSION="$GIT_HASH-git" \
VERSION="$GIT_HASH-git" ; \ VERSION_SUFFIX="-$GIT_HASH-git" ; \
fi fi
echo "PACKAGE_VERSION=$VERSION" >> $GITHUB_ENV echo "PACKAGE_VERSION=$VERSION" >> $GITHUB_ENV
- name: 'Install prerequisites (Apt)' echo "CMAKE_VERSION_SUFFIX=$VERSION_SUFFIX" >> $GITHUB_ENV
shell: bash - name: 'Install prerequisites (Apt)'
run: | shell: bash
sudo dpkg --add-architecture amd64 run: |
sudo apt-get -qq update sudo dpkg --add-architecture amd64
sudo apt-get install -y \ sudo apt-get -qq update
build-essential \ sudo apt-get install -y \
checkinstall \ build-essential \
cmake \ checkinstall \
libasound2-dev \ cmake \
libavcodec-dev \ libasound2-dev \
libavdevice-dev \ libavcodec-dev \
libavfilter-dev \ libavdevice-dev \
libavformat-dev \ libavfilter-dev \
libavutil-dev \ libavformat-dev \
libcurl4-openssl-dev \ libavutil-dev \
libfdk-aac-dev \ libcurl4-openssl-dev \
libfontconfig-dev \ libfdk-aac-dev \
libfreetype6-dev \ libfontconfig-dev \
libgl1-mesa-dev \ libfreetype6-dev \
libjack-jackd2-dev \ libgl1-mesa-dev \
libjansson-dev \ libjack-jackd2-dev \
libluajit-5.1-dev \ libjansson-dev \
libpulse-dev \ libluajit-5.1-dev \
libqt5x11extras5-dev \ libpulse-dev \
libspeexdsp-dev \ libqt5x11extras5-dev \
libswresample-dev \ libspeexdsp-dev \
libswscale-dev \ libswresample-dev \
libudev-dev \ libswscale-dev \
libv4l-dev \ libudev-dev \
libva-dev \ libv4l-dev \
libvlc-dev \ libva-dev \
libx11-dev \ libvlc-dev \
libx264-dev \ libx11-dev \
libxcb-randr0-dev \ libx264-dev \
libxcb-shm0-dev \ libxcb-randr0-dev \
libxcb-xinerama0-dev \ libxcb-shm0-dev \
libxcomposite-dev \ libxcb-xinerama0-dev \
libxinerama-dev \ libxcomposite-dev \
libmbedtls-dev \ libxinerama-dev \
pkg-config \ libmbedtls-dev \
python3-dev \ pkg-config \
qtbase5-dev \ python3-dev \
qtbase5-private-dev \ qtbase5-dev \
libqt5svg5-dev \ qtbase5-private-dev \
swig \ libqt5svg5-dev \
libxcb-randr0-dev \ swig \
libxcb-xfixes0-dev \ libxcb-randr0-dev \
libx11-xcb-dev \ libxcb-xfixes0-dev \
libxcb1-dev \ libx11-xcb-dev \
libxss-dev \ libxcb1-dev \
- name: 'Configure OBS-Studio' libxss-dev \
working-directory: ${{ github.workspace }}/obs-studio - name: 'Configure OBS-Studio'
shell: bash working-directory: ${{ github.workspace }}/obs-studio
run: | shell: bash
mkdir ./build run: |
cd ./build mkdir ./build
cmake -DDISABLE_PLUGINS=YES -DENABLE_SCRIPTING=NO -DUNIX_STRUCTURE=YES -DCMAKE_INSTALL_PREFIX=/usr .. cd ./build
- name: 'Build OBS-Studio' cmake -DDISABLE_PLUGINS=YES -DENABLE_SCRIPTING=NO -DUNIX_STRUCTURE=YES -DCMAKE_INSTALL_PREFIX=/usr ..
working-directory: ${{ github.workspace }}/obs-studio - name: 'Build OBS-Studio'
shell: bash working-directory: ${{ github.workspace }}/obs-studio
run: | shell: bash
set -e run: |
cd ./build set -e
make -j4 libobs obs-frontend-api cd ./build
- name: 'Install OBS-Studio' make -j4 libobs obs-frontend-api
working-directory: ${{ github.workspace }}/obs-studio - name: 'Install OBS-Studio'
shell: bash working-directory: ${{ github.workspace }}/obs-studio
run: | shell: bash
cd ./build run: |
sudo cp ./libobs/libobs.so /usr/lib cd ./build
sudo cp ./UI/obs-frontend-api/libobs-frontend-api.so /usr/lib sudo cp ./libobs/libobs.so /usr/lib
sudo mkdir -p /usr/include/obs sudo cp ./UI/obs-frontend-api/libobs-frontend-api.so /usr/lib
sudo cp ../UI/obs-frontend-api/obs-frontend-api.h /usr/include/obs/obs-frontend-api.h sudo mkdir -p /usr/include/obs
- name: 'Configure obs-websocket' sudo cp ../UI/obs-frontend-api/obs-frontend-api.h /usr/include/obs/obs-frontend-api.h
working-directory: ${{ github.workspace }}/obs-websocket - name: 'Configure obs-websocket'
shell: bash working-directory: ${{ github.workspace }}/obs-websocket
run: | shell: bash
mkdir ./build run: |
cd ./build mkdir ./build
if [ "${{ env.GIT_TAG }}" ] ; then \ cd ./build
cmake -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=TRUE -DCMAKE_BUILD_TYPE=Release .. ; \ if [ "${{ env.GIT_TAG }}" ] ; then \
else \ cmake -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=TRUE -DOBS_WEBSOCKET_VERSION_SUFFIX="${{ env.CMAKE_VERSION_SUFFIX }}" -DCMAKE_BUILD_TYPE=Release .. ; \
cmake -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=TRUE .. ; \ else \
fi cmake -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=TRUE -DOBS_WEBSOCKET_VERSION_SUFFIX="${{ env.CMAKE_VERSION_SUFFIX }}" .. ; \
- name: 'Build obs-websocket' fi
working-directory: ${{ github.workspace }}/obs-websocket - name: 'Build obs-websocket'
shell: bash working-directory: ${{ github.workspace }}/obs-websocket
run: | shell: bash
set -e run: |
cd ./build set -e
make -j4 cd ./build
- name: 'Set PR Artifact Filename' make -j4
shell: bash - name: 'Set PR Artifact Filename'
run: | shell: bash
if [ "${{ env.GIT_TAG }}" ] ; then \ run: |
FILENAME="obs-websocket-${{ env.GIT_TAG }}-Ubuntu64.deb" ; \ echo "LINUX_FILENAME=obs-websocket-${{ env.PACKAGE_VERSION }}-Ubuntu64.deb" >> $GITHUB_ENV
else \ - name: 'Package ${{ env.LINUX_FILENAME }}'
FILENAME="obs-websocket-${{ env.GIT_HASH }}-git-Ubuntu64.deb" ; \ if: success()
fi working-directory: ${{ github.workspace }}/obs-websocket
echo "FILENAME=$FILENAME" >> $GITHUB_ENV shell: bash
- name: 'Package ${{ env.FILENAME }}' run: |
if: success() if [ "${{ env.GIT_TAG }}" ] ; then \
working-directory: ${{ github.workspace }}/obs-websocket CHECKINSTALL_VERSION="${{ env.PACKAGE_VERSION }}" ; \
shell: bash else \
run: | CHECKINSTALL_VERSION="1-${{ env.PACKAGE_VERSION }}" ; \
cd ./build fi
sudo checkinstall -y --type=debian --fstrans=no -nodoc \ cd ./build
--backup=no --deldoc=yes --install=no --pkgname=obs-websocket --pkgversion=${{ env.PACKAGE_VERSION }} \ sudo checkinstall -y --type=debian --fstrans=no -nodoc \
--pkglicense="GPLv2.0" --maintainer="${{ github.event.pusher.email }}" --pkggroup="video" \ --backup=no --deldoc=yes --install=no --pkgname=obs-websocket --pkgversion="$CHECKINSTALL_VERSION" \
--pkgsource="${{ github.event.repository.html_url }}" \ --pkglicense="GPLv2.0" --maintainer="${{ github.event.pusher.email }}" --pkggroup="video" \
--requires="obs-studio,libqt5network5,libqt5concurrent5,qt5-image-formats-plugins" \ --pkgsource="${{ github.event.repository.html_url }}" \
--pakdir="../package" --requires="obs-studio,libqt5network5,libqt5concurrent5,qt5-image-formats-plugins" \
sudo chmod ao+r ../package/* --pakdir="../package"
sudo mv ../package/* ../package/${{ env.FILENAME }} sudo chmod ao+r ../package/*
cd - sudo mv ../package/* ../package/${{ env.LINUX_FILENAME }}
- name: 'Publish ${{ env.FILENAME }}' cd -
if: success() - name: 'Publish ${{ env.LINUX_FILENAME }}'
uses: actions/upload-artifact@v2-preview if: success()
with: uses: actions/upload-artifact@v2-preview
name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-Ubuntu64' with:
path: '${{ github.workspace }}/obs-websocket/package/*.deb' name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-Ubuntu64'
path: '${{ github.workspace }}/obs-websocket/package/*.deb'

2
.gitignore vendored
View File

@ -9,3 +9,5 @@
.idea .idea
.vscode .vscode
/docs/node_modules/ /docs/node_modules/
/src/plugin-macros.generated.h
/installer/installer-windows.generated.iss

View File

@ -1,10 +1,23 @@
cmake_minimum_required(VERSION 3.16...3.20) cmake_minimum_required(VERSION 3.16...3.20)
# Version variables
project(obs-websocket VERSION 5.0.0) project(obs-websocket VERSION 5.0.0)
set(OBS_WEBSOCKET_RPC_VERSION 1) set(OBS_WEBSOCKET_RPC_VERSION 1)
# Set correct version string
if(DEFINED OBS_WEBSOCKET_VERSION_SUFFIX AND NOT OBS_WEBSOCKET_VERSION_SUFFIX STREQUAL "")
set(OBS_WEBSOCKET_VERSION "${CMAKE_PROJECT_VERSION}${OBS_WEBSOCKET_VERSION_SUFFIX}")
message(WARNING "-----------------------------------\nVersion Suffix provided. OBS_WEBSOCKET_VERSION is now ${OBS_WEBSOCKET_VERSION}\n-----------------------------------")
else()
set(OBS_WEBSOCKET_VERSION "${CMAKE_PROJECT_VERSION}")
endif()
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Prohibit in-source builds # Prohibit in-source builds
file(TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" _LOC_PATH) file(TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" _LOC_PATH)
if(EXISTS "${LOC_PATH}") if(EXISTS "${LOC_PATH}")
@ -91,6 +104,7 @@ set(obs-websocket_SOURCES
src/requesthandler/RequestHandler_Sources.cpp src/requesthandler/RequestHandler_Sources.cpp
src/requesthandler/RequestHandler_Scenes.cpp src/requesthandler/RequestHandler_Scenes.cpp
src/requesthandler/RequestHandler_Inputs.cpp src/requesthandler/RequestHandler_Inputs.cpp
src/requesthandler/RequestHandler_SceneItems.cpp
src/requesthandler/RequestHandler_Stream.cpp src/requesthandler/RequestHandler_Stream.cpp
src/requesthandler/rpc/Request.cpp src/requesthandler/rpc/Request.cpp
src/requesthandler/rpc/RequestResult.cpp src/requesthandler/rpc/RequestResult.cpp

View File

@ -32,7 +32,7 @@ brew install obs-websocket
Here is a list of available web clients: (compatible with tablets and other touch interfaces) Here is a list of available web clients: (compatible with tablets and other touch interfaces)
- (No known clients supporting 5.0.0) - Python 3.7+ (Asyncio): [simpleobsws](https://github.com/IRLToolkit/simpleobsws/tree/master) by IRLToolkit
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. 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.

View File

@ -44,10 +44,9 @@ Here's info on how to connect to obs-websocket
These steps should be followed precisely. Failure to connect to the server as instructed will likely result in your client being treated in an undefined way. These steps should be followed precisely. Failure to connect to the server as instructed will likely result in your client being treated in an undefined way.
- Initial HTTP request made to the obs-websocket server. - Initial HTTP request made to the obs-websocket server.
- HTTP request headers can be used to set the websocket communication type. The default format is JSON. Example headers: - The `Sec-WebSocket-Protocol` header can be used to tell obs-websocket which kind of message encoding to use. By default, obs-websocket uses JSON over text. Available subprotocols:
- `Content-Type: application/json` - `obswebsocket.json` - JSON over text frames
- `Content-Type: application/msgpack` - `obswebsocket.msgpack` - MsgPack over binary frames
- If an invalid `Content-Type` is specified, the connection will be closed with [`WebSocketCloseCode::InvalidContentType`](#websocketclosecode-enum) after upgrade (but before `Hello`).
- Once the connection is upgraded, the websocket server will immediately send an [OpCode 0 `Hello`](#hello-opcode-0) message to the client. - Once the connection is upgraded, the websocket server will immediately send an [OpCode 0 `Hello`](#hello-opcode-0) message to the client.
@ -57,9 +56,9 @@ These steps should be followed precisely. Failure to connect to the server as in
- The client determines if the server's `rpcVersion` is supported, and if not it provides its closest supported version in `Identify`. - The client determines if the server's `rpcVersion` is supported, and if not it provides its closest supported version in `Identify`.
- The server receives and processes the `Identify` sent by the client. - The server receives and processes the `Identify` sent by the client.
- If authentication is required and the `Identify` message data does not contain an `authentication` string, or the string is not correct, the connection is dropped with [`WebSocketCloseCode::AuthenticationFailed`](#websocketclosecode-enum) - If authentication is required and the `Identify` message data does not contain an `authentication` string, or the string is not correct, the connection is closed with [`WebSocketCloseCode::AuthenticationFailed`](#websocketclosecode-enum)
- If the client has requested an `rpcVersion` which the server cannot use, the connection is dropped with [`WebSocketCloseCode::UnsupportedProtocolVersion`](#websocketclosecode-enum). This system allows both the server and client to have seamless backwards compatability. - If the client has requested an `rpcVersion` which the server cannot use, the connection is closed with [`WebSocketCloseCode::UnsupportedRpcVersion`](#websocketclosecode-enum). This system allows both the server and client to have seamless backwards compatability.
- If any other parameters are malformed (invalid type, etc), the connection is dropped with [`WebSocketCloseCode::InvalidIdentifyParameter`](#websocketclosecode-enum) - If any other parameters are malformed (invalid type, etc), the connection is closed with an appropriate close code.
- Once identification is processed on the server, the server responds to the client with an [OpCode 2 `Identified`](#identified-opcode-2). - Once identification is processed on the server, the server responds to the client with an [OpCode 2 `Identified`](#identified-opcode-2).
@ -68,10 +67,10 @@ These steps should be followed precisely. Failure to connect to the server as in
- At any time after a client has been identified, it may send an [OpCode 3 `Reidentify`](#reidentify-opcode-3) message to update certain allowed session parameters. The server will respond in the same way it does during initial identification. - At any time after a client has been identified, it may send an [OpCode 3 `Reidentify`](#reidentify-opcode-3) message to update certain allowed session parameters. The server will respond in the same way it does during initial identification.
#### Connection Notes #### Connection Notes
- If the Content Type is `application/msgpack`, all messages must be sent over binary. If it is `application/json`, all messages must be sent over text. - If a binary frame is received when using the `obswebsocket.json` (default) subprotocol, or a text frame is received while using the `obswebsocket.msgpack` subprotocol, the connection is closed with [`WebSocketCloseCode::MessageDecodeError`](#websocketclosecode-enum).
- The obs-websocket server listens for any messages containing a `request-type` key in the first level JSON from unidentified clients. If a message matches, the connection is dropped with [`WebSocketCloseCode::UnsupportedProtocolVersion`](#websocketclosecode-enum) and a warning is logged. - The obs-websocket server listens for any messages containing a `request-type` key in the first level JSON from unidentified clients. If a message matches, the connection is closed with [`WebSocketCloseCode::UnsupportedRpcVersion`](#websocketclosecode-enum) and a warning is logged.
- If a message with a `messageType` is not recognized to the obs-websocket server, the connection is dropped with [`WebSocketCloseCode::UnknownMessageType`](#websocketclosecode-enum). - If a message with a `messageType` is not recognized to the obs-websocket server, the connection is closed with [`WebSocketCloseCode::UnknownOpCode`](#websocketclosecode-enum).
- At no point may the client send any message other than a single `Identify` before it has received an `Identified`. Doing so will result in the connection being dropped by the server with [`WebSocketCloseCode::NotIdentified`](#websocketclosecode-enum). - At no point may the client send any message other than a single `Identify` before it has received an `Identified`. Doing so will result in the connection being closed with [`WebSocketCloseCode::NotIdentified`](#websocketclosecode-enum).
--- ---
@ -123,8 +122,6 @@ enum WebSocketCloseCode {
DontClose = 0, DontClose = 0,
// Reserved // Reserved
UnknownReason = 4000, UnknownReason = 4000,
// The requested `Content-Type` specified in the request HTTP header is invalid.
InvalidContentType = 4001,
// The server was unable to decode the incoming websocket message // The server was unable to decode the incoming websocket message
MessageDecodeError = 4002, MessageDecodeError = 4002,
// A data key is missing but required // A data key is missing but required
@ -191,11 +188,11 @@ enum RequestStatus {
Success = 100, Success = 100,
// The `requestType` key is missing from the request data // The `requestType` field is missing from the request data
MissingRequestType = 203, MissingRequestType = 203,
// The request type is invalid (does not exist) // The request type is invalid or does not exist
UnknownRequestType = 204, UnknownRequestType = 204,
// Generic error code (comment is expected to be provided) // Generic error code (comment required)
GenericError = 205, GenericError = 205,
// A required request parameter is missing // A required request parameter is missing
@ -203,10 +200,10 @@ enum RequestStatus {
// The request does not have a valid requestData object. // The request does not have a valid requestData object.
MissingRequestData = 301, MissingRequestData = 301,
// Generic invalid request parameter message // Generic invalid request parameter message (comment required)
InvalidRequestParameter = 400, InvalidRequestParameter = 400,
// A request parameter has the wrong data type // A request parameter has the wrong data type
InvalidRequestParameterDataType = 401, InvalidRequestParameterType = 401,
// A request parameter (float or int) is out of valid range // A request parameter (float or int) is out of valid range
RequestParameterOutOfRange = 402, RequestParameterOutOfRange = 402,
// A request parameter (string or array) is empty and cannot be // A request parameter (string or array) is empty and cannot be
@ -218,102 +215,36 @@ enum RequestStatus {
OutputRunning = 500, OutputRunning = 500,
// An output is not running and should be // An output is not running and should be
OutputNotRunning = 501, OutputNotRunning = 501,
// Stream is running and cannot be // An output is paused and should not be
StreamRunning = 502, OutputPaused = 502,
// Stream is not running and should be // An output is disabled and should not be
StreamNotRunning = 503, OutputDisabled = 503,
// Record is running and cannot be
RecordRunning = 504,
// Record is not running and should be
RecordNotRunning = 505,
// Record is paused and cannot be
RecordPaused = 506,
// Replay buffer is running and cannot be
ReplayBufferRunning = 507,
// Replay buffer is not running and should be
ReplayBufferNotRunning = 508,
// Replay buffer is disabled and cannot be
ReplayBufferDisabled = 509,
// Studio mode is active and cannot be // Studio mode is active and cannot be
StudioModeActive = 510, StudioModeActive = 504,
// Studio mode is not active and should be // Studio mode is not active and should be
StudioModeNotActive = 511, StudioModeNotActive = 505,
// Virtualcam is running and cannot be
VirtualcamRunning = 512,
// Virtualcam is not running and should be
VirtualcamNotRunning = 513,
// The specified source (obs_source_t) was of the invalid type (Eg. input instead of scene) // The resource was not found
InvalidSourceType = 600, ResourceNotFound = 600,
// The specified source (obs_source_t) was not found (generic for input, filter, transition, scene) // The resource already exists
SourceNotFound = 601, ResourceAlreadyExists = 601,
// The specified source (obs_source_t) already exists. Applicable to inputs, filters, transitions, scenes // The type of resource found is invalid
SourceAlreadyExists = 602, InvalidResourceType = 602,
// The specified input (obs_source_t-OBS_SOURCE_TYPE_FILTER) was not found // There are not enough instances of the resource in order to perform the request
InputNotFound = 603, NotEnoughResources = 603,
// The state of the resource is invalid. For example, if the resource is blocked from being accessed
InvalidResourceState = 604,
// The specified input (obs_source_t-OBS_SOURCE_TYPE_INPUT) had the wrong kind // The specified input (obs_source_t-OBS_SOURCE_TYPE_INPUT) had the wrong kind
InvalidInputKind = 604, InvalidInputKind = 605,
// The specified filter (obs_source_t-OBS_SOURCE_TYPE_FILTER) was not found
FilterNotFound = 605,
// The specified transition (obs_source_t-OBS_SOURCE_TYPE_TRANSITION) was not found
TransitionNotFound = 606,
// The specified transition (obs_source_t-OBS_SOURCE_TYPE_TRANSITION) does not support setting its position (transition is of fixed type)
TransitionDurationFixed = 607,
// The specified scene (obs_source_t-OBS_SOURCE_TYPE_SCENE), (obs_scene_t) was not found
SceneNotFound = 608,
// The specified scene item (obs_sceneitem_t) was not found
SceneItemNotFound = 609,
// The specified scene collection was not found
SceneCollectionNotFound = 610,
// The specified profile was not found
ProfileNotFound = 611,
// The specified output (obs_output_t) was not found
OutputNotFound = 612,
// The specified encoder (obs_encoder_t) was not found
EncoderNotFound = 613,
// The specified service (obs_service_t) was not found
ServiceNotFound = 614,
// The specified hotkey was not found
HotkeyNotFound = 615,
// The specified directory was not found
DirectoryNotFound = 616,
// The specified config item (config_t) was not found. Could be section or parameter name
ConfigParameterNotFound = 617,
// The specified property (obs_properties_t) was not found
PropertyNotFound = 618,
// The specififed key (OBS_KEY_*) was not found
KeyNotFound = 619,
// The specified data realm (OBS_WEBSOCKET_DATA_REALM_*) was not found
DataRealmNotFound = 620,
// The scene collection already exists
SceneCollectionAlreadyExists = 621,
// There are not enough scene collections to perform the action
NotEnoughSceneCollections = 622,
// The profile already exists
ProfileAlreadyExists = 623,
// There are not enough profiles to perform the action
NotEnoughProfiles = 624,
// There are not enough scenes to perform the action
NotEnoughScenes = 625,
// Processing the request failed unexpectedly // Creating the resource failed
RequestProcessingFailed = 700, ResourceCreationFailed = 700,
// Starting the Output failed // Performing an action on the resource failed
OutputStartFailed = 701, ResourceActionFailed = 701,
// Duplicating the scene item failed // Processing the request failed unexpectedly (comment required)
SceneItemDuplicationFailed = 702, RequestProcessingFailed = 702,
// Rendering the screenshot failed
ScreenshotRenderFailed = 703,
// Encoding the screenshot failed
ScreenshotEncodeFailed = 704,
// Saving the screenshot failed
ScreenshotSaveFailed = 705,
// Creating the directory failed
DirectoryCreationFailed = 706,
// The combination of request parameters cannot be used to perform an action // The combination of request parameters cannot be used to perform an action
CannotAct = 707, CannotAct = 703,
// Creation of a new stream service failed
StreamServiceCreationFailed = 708,
}; };
``` ```
@ -393,7 +324,7 @@ Authentication is not required
} }
``` ```
- `rpcVersion` is the version number that the client would like the obs-websocket server to use. - `rpcVersion` is the version number that the client would like the obs-websocket server to use.
- When `ignoreInvalidMessages` is true, the socket will not be closed for [`WebSocketCloseCode`](#websocketclosecode-enum): `MessageDecodeError`, `UnknownMessageType`, or `RequestMissingRequestId`. Instead, the message will be logged and dropped. - When `ignoreInvalidMessages` is true, the socket will not be closed for [`WebSocketCloseCode`](#websocketclosecode-enum): `MessageDecodeError`, `UnknownOpCode`, or `MissingDataKey`. Instead, the message will be logged and ignored.
- When `ignoreNonFatalRequestChecks` is true, requests will ignore checks which are not critical to the function of the request. Eg calling `DeleteScene` when the target scene does not exist would still return [`RequestStatus::Success`](#requeststatus-enum) if this flag is enabled. - When `ignoreNonFatalRequestChecks` is true, requests will ignore checks which are not critical to the function of the request. Eg calling `DeleteScene` when the target scene does not exist would still return [`RequestStatus::Success`](#requeststatus-enum) if this flag is enabled.
- `eventSubscriptions` is a bitmask of [`EventSubscriptions`](#eventsubscriptions-enum) items to subscribe to events and event categories at will. By default, all event categories are subscribed, except for events marked as high volume. High volume events must be explicitly subscribed to. - `eventSubscriptions` is a bitmask of [`EventSubscriptions`](#eventsubscriptions-enum) items to subscribe to events and event categories at will. By default, all event categories are subscribed, except for events marked as high volume. High volume events must be explicitly subscribed to.
@ -462,9 +393,11 @@ Authentication is not required
``` ```
{ {
"eventType": string, "eventType": string,
"eventIntent": number,
"eventData": object(optional) "eventData": object(optional)
} }
``` ```
- `eventIntent` is the original intent required to be subscribed to in order to receive the event.
**Example Message:** **Example Message:**
```json ```json
@ -472,6 +405,7 @@ Authentication is not required
"op": 2, "op": 2,
"d": { "d": {
"eventType": "StudioModeStateChanged", "eventType": "StudioModeStateChanged",
"eventIntent": 1,
"eventData": { "eventData": {
"studioModeEnabled": true "studioModeEnabled": true
} }

View File

@ -42,10 +42,9 @@ Here's info on how to connect to obs-websocket
These steps should be followed precisely. Failure to connect to the server as instructed will likely result in your client being treated in an undefined way. These steps should be followed precisely. Failure to connect to the server as instructed will likely result in your client being treated in an undefined way.
- Initial HTTP request made to the obs-websocket server. - Initial HTTP request made to the obs-websocket server.
- HTTP request headers can be used to set the websocket communication type. The default format is JSON. Example headers: - The `Sec-WebSocket-Protocol` header can be used to tell obs-websocket which kind of message encoding to use. By default, obs-websocket uses JSON over text. Available subprotocols:
- `Content-Type: application/json` - `obswebsocket.json` - JSON over text frames
- `Content-Type: application/msgpack` - `obswebsocket.msgpack` - MsgPack over binary frames
- If an invalid `Content-Type` is specified, the connection will be closed with [`WebSocketCloseCode::InvalidContentType`](#websocketclosecode-enum) after upgrade (but before `Hello`).
- Once the connection is upgraded, the websocket server will immediately send an [OpCode 0 `Hello`](#hello-opcode-0) message to the client. - Once the connection is upgraded, the websocket server will immediately send an [OpCode 0 `Hello`](#hello-opcode-0) message to the client.
@ -55,9 +54,9 @@ These steps should be followed precisely. Failure to connect to the server as in
- The client determines if the server's `rpcVersion` is supported, and if not it provides its closest supported version in `Identify`. - The client determines if the server's `rpcVersion` is supported, and if not it provides its closest supported version in `Identify`.
- The server receives and processes the `Identify` sent by the client. - The server receives and processes the `Identify` sent by the client.
- If authentication is required and the `Identify` message data does not contain an `authentication` string, or the string is not correct, the connection is dropped with [`WebSocketCloseCode::AuthenticationFailed`](#websocketclosecode-enum) - If authentication is required and the `Identify` message data does not contain an `authentication` string, or the string is not correct, the connection is closed with [`WebSocketCloseCode::AuthenticationFailed`](#websocketclosecode-enum)
- If the client has requested an `rpcVersion` which the server cannot use, the connection is dropped with [`WebSocketCloseCode::UnsupportedProtocolVersion`](#websocketclosecode-enum). This system allows both the server and client to have seamless backwards compatability. - If the client has requested an `rpcVersion` which the server cannot use, the connection is closed with [`WebSocketCloseCode::UnsupportedRpcVersion`](#websocketclosecode-enum). This system allows both the server and client to have seamless backwards compatability.
- If any other parameters are malformed (invalid type, etc), the connection is dropped with [`WebSocketCloseCode::InvalidIdentifyParameter`](#websocketclosecode-enum) - If any other parameters are malformed (invalid type, etc), the connection is closed with an appropriate close code.
- Once identification is processed on the server, the server responds to the client with an [OpCode 2 `Identified`](#identified-opcode-2). - Once identification is processed on the server, the server responds to the client with an [OpCode 2 `Identified`](#identified-opcode-2).
@ -66,10 +65,10 @@ These steps should be followed precisely. Failure to connect to the server as in
- At any time after a client has been identified, it may send an [OpCode 3 `Reidentify`](#reidentify-opcode-3) message to update certain allowed session parameters. The server will respond in the same way it does during initial identification. - At any time after a client has been identified, it may send an [OpCode 3 `Reidentify`](#reidentify-opcode-3) message to update certain allowed session parameters. The server will respond in the same way it does during initial identification.
#### Connection Notes #### Connection Notes
- If the Content Type is `application/msgpack`, all messages must be sent over binary. If it is `application/json`, all messages must be sent over text. - If a binary frame is received when using the `obswebsocket.json` (default) subprotocol, or a text frame is received while using the `obswebsocket.msgpack` subprotocol, the connection is closed with [`WebSocketCloseCode::MessageDecodeError`](#websocketclosecode-enum).
- The obs-websocket server listens for any messages containing a `request-type` key in the first level JSON from unidentified clients. If a message matches, the connection is dropped with [`WebSocketCloseCode::UnsupportedProtocolVersion`](#websocketclosecode-enum) and a warning is logged. - The obs-websocket server listens for any messages containing a `request-type` key in the first level JSON from unidentified clients. If a message matches, the connection is closed with [`WebSocketCloseCode::UnsupportedRpcVersion`](#websocketclosecode-enum) and a warning is logged.
- If a message with a `messageType` is not recognized to the obs-websocket server, the connection is dropped with [`WebSocketCloseCode::UnknownMessageType`](#websocketclosecode-enum). - If a message with a `messageType` is not recognized to the obs-websocket server, the connection is closed with [`WebSocketCloseCode::UnknownOpCode`](#websocketclosecode-enum).
- At no point may the client send any message other than a single `Identify` before it has received an `Identified`. Doing so will result in the connection being dropped by the server with [`WebSocketCloseCode::NotIdentified`](#websocketclosecode-enum). - At no point may the client send any message other than a single `Identify` before it has received an `Identified`. Doing so will result in the connection being closed with [`WebSocketCloseCode::NotIdentified`](#websocketclosecode-enum).
--- ---
@ -121,8 +120,6 @@ enum WebSocketCloseCode {
DontClose = 0, DontClose = 0,
// Reserved // Reserved
UnknownReason = 4000, UnknownReason = 4000,
// The requested `Content-Type` specified in the request HTTP header is invalid.
InvalidContentType = 4001,
// The server was unable to decode the incoming websocket message // The server was unable to decode the incoming websocket message
MessageDecodeError = 4002, MessageDecodeError = 4002,
// A data key is missing but required // A data key is missing but required
@ -189,11 +186,11 @@ enum RequestStatus {
Success = 100, Success = 100,
// The `requestType` key is missing from the request data // The `requestType` field is missing from the request data
MissingRequestType = 203, MissingRequestType = 203,
// The request type is invalid (does not exist) // The request type is invalid or does not exist
UnknownRequestType = 204, UnknownRequestType = 204,
// Generic error code (comment is expected to be provided) // Generic error code (comment required)
GenericError = 205, GenericError = 205,
// A required request parameter is missing // A required request parameter is missing
@ -201,10 +198,10 @@ enum RequestStatus {
// The request does not have a valid requestData object. // The request does not have a valid requestData object.
MissingRequestData = 301, MissingRequestData = 301,
// Generic invalid request parameter message // Generic invalid request parameter message (comment required)
InvalidRequestParameter = 400, InvalidRequestParameter = 400,
// A request parameter has the wrong data type // A request parameter has the wrong data type
InvalidRequestParameterDataType = 401, InvalidRequestParameterType = 401,
// A request parameter (float or int) is out of valid range // A request parameter (float or int) is out of valid range
RequestParameterOutOfRange = 402, RequestParameterOutOfRange = 402,
// A request parameter (string or array) is empty and cannot be // A request parameter (string or array) is empty and cannot be
@ -216,102 +213,36 @@ enum RequestStatus {
OutputRunning = 500, OutputRunning = 500,
// An output is not running and should be // An output is not running and should be
OutputNotRunning = 501, OutputNotRunning = 501,
// Stream is running and cannot be // An output is paused and should not be
StreamRunning = 502, OutputPaused = 502,
// Stream is not running and should be // An output is disabled and should not be
StreamNotRunning = 503, OutputDisabled = 503,
// Record is running and cannot be
RecordRunning = 504,
// Record is not running and should be
RecordNotRunning = 505,
// Record is paused and cannot be
RecordPaused = 506,
// Replay buffer is running and cannot be
ReplayBufferRunning = 507,
// Replay buffer is not running and should be
ReplayBufferNotRunning = 508,
// Replay buffer is disabled and cannot be
ReplayBufferDisabled = 509,
// Studio mode is active and cannot be // Studio mode is active and cannot be
StudioModeActive = 510, StudioModeActive = 504,
// Studio mode is not active and should be // Studio mode is not active and should be
StudioModeNotActive = 511, StudioModeNotActive = 505,
// Virtualcam is running and cannot be
VirtualcamRunning = 512,
// Virtualcam is not running and should be
VirtualcamNotRunning = 513,
// The specified source (obs_source_t) was of the invalid type (Eg. input instead of scene) // The resource was not found
InvalidSourceType = 600, ResourceNotFound = 600,
// The specified source (obs_source_t) was not found (generic for input, filter, transition, scene) // The resource already exists
SourceNotFound = 601, ResourceAlreadyExists = 601,
// The specified source (obs_source_t) already exists. Applicable to inputs, filters, transitions, scenes // The type of resource found is invalid
SourceAlreadyExists = 602, InvalidResourceType = 602,
// The specified input (obs_source_t-OBS_SOURCE_TYPE_FILTER) was not found // There are not enough instances of the resource in order to perform the request
InputNotFound = 603, NotEnoughResources = 603,
// The state of the resource is invalid. For example, if the resource is blocked from being accessed
InvalidResourceState = 604,
// The specified input (obs_source_t-OBS_SOURCE_TYPE_INPUT) had the wrong kind // The specified input (obs_source_t-OBS_SOURCE_TYPE_INPUT) had the wrong kind
InvalidInputKind = 604, InvalidInputKind = 605,
// The specified filter (obs_source_t-OBS_SOURCE_TYPE_FILTER) was not found
FilterNotFound = 605,
// The specified transition (obs_source_t-OBS_SOURCE_TYPE_TRANSITION) was not found
TransitionNotFound = 606,
// The specified transition (obs_source_t-OBS_SOURCE_TYPE_TRANSITION) does not support setting its position (transition is of fixed type)
TransitionDurationFixed = 607,
// The specified scene (obs_source_t-OBS_SOURCE_TYPE_SCENE), (obs_scene_t) was not found
SceneNotFound = 608,
// The specified scene item (obs_sceneitem_t) was not found
SceneItemNotFound = 609,
// The specified scene collection was not found
SceneCollectionNotFound = 610,
// The specified profile was not found
ProfileNotFound = 611,
// The specified output (obs_output_t) was not found
OutputNotFound = 612,
// The specified encoder (obs_encoder_t) was not found
EncoderNotFound = 613,
// The specified service (obs_service_t) was not found
ServiceNotFound = 614,
// The specified hotkey was not found
HotkeyNotFound = 615,
// The specified directory was not found
DirectoryNotFound = 616,
// The specified config item (config_t) was not found. Could be section or parameter name
ConfigParameterNotFound = 617,
// The specified property (obs_properties_t) was not found
PropertyNotFound = 618,
// The specififed key (OBS_KEY_*) was not found
KeyNotFound = 619,
// The specified data realm (OBS_WEBSOCKET_DATA_REALM_*) was not found
DataRealmNotFound = 620,
// The scene collection already exists
SceneCollectionAlreadyExists = 621,
// There are not enough scene collections to perform the action
NotEnoughSceneCollections = 622,
// The profile already exists
ProfileAlreadyExists = 623,
// There are not enough profiles to perform the action
NotEnoughProfiles = 624,
// There are not enough scenes to perform the action
NotEnoughScenes = 625,
// Processing the request failed unexpectedly // Creating the resource failed
RequestProcessingFailed = 700, ResourceCreationFailed = 700,
// Starting the Output failed // Performing an action on the resource failed
OutputStartFailed = 701, ResourceActionFailed = 701,
// Duplicating the scene item failed // Processing the request failed unexpectedly (comment required)
SceneItemDuplicationFailed = 702, RequestProcessingFailed = 702,
// Rendering the screenshot failed
ScreenshotRenderFailed = 703,
// Encoding the screenshot failed
ScreenshotEncodeFailed = 704,
// Saving the screenshot failed
ScreenshotSaveFailed = 705,
// Creating the directory failed
DirectoryCreationFailed = 706,
// The combination of request parameters cannot be used to perform an action // The combination of request parameters cannot be used to perform an action
CannotAct = 707, CannotAct = 703,
// Creation of a new stream service failed
StreamServiceCreationFailed = 708,
}; };
``` ```
@ -391,7 +322,7 @@ Authentication is not required
} }
``` ```
- `rpcVersion` is the version number that the client would like the obs-websocket server to use. - `rpcVersion` is the version number that the client would like the obs-websocket server to use.
- When `ignoreInvalidMessages` is true, the socket will not be closed for [`WebSocketCloseCode`](#websocketclosecode-enum): `MessageDecodeError`, `UnknownMessageType`, or `RequestMissingRequestId`. Instead, the message will be logged and dropped. - When `ignoreInvalidMessages` is true, the socket will not be closed for [`WebSocketCloseCode`](#websocketclosecode-enum): `MessageDecodeError`, `UnknownOpCode`, or `MissingDataKey`. Instead, the message will be logged and ignored.
- When `ignoreNonFatalRequestChecks` is true, requests will ignore checks which are not critical to the function of the request. Eg calling `DeleteScene` when the target scene does not exist would still return [`RequestStatus::Success`](#requeststatus-enum) if this flag is enabled. - When `ignoreNonFatalRequestChecks` is true, requests will ignore checks which are not critical to the function of the request. Eg calling `DeleteScene` when the target scene does not exist would still return [`RequestStatus::Success`](#requeststatus-enum) if this flag is enabled.
- `eventSubscriptions` is a bitmask of [`EventSubscriptions`](#eventsubscriptions-enum) items to subscribe to events and event categories at will. By default, all event categories are subscribed, except for events marked as high volume. High volume events must be explicitly subscribed to. - `eventSubscriptions` is a bitmask of [`EventSubscriptions`](#eventsubscriptions-enum) items to subscribe to events and event categories at will. By default, all event categories are subscribed, except for events marked as high volume. High volume events must be explicitly subscribed to.
@ -460,9 +391,11 @@ Authentication is not required
``` ```
{ {
"eventType": string, "eventType": string,
"eventIntent": number,
"eventData": object(optional) "eventData": object(optional)
} }
``` ```
- `eventIntent` is the original intent required to be subscribed to in order to receive the event.
**Example Message:** **Example Message:**
```json ```json
@ -470,6 +403,7 @@ Authentication is not required
"op": 2, "op": 2,
"d": { "d": {
"eventType": "StudioModeStateChanged", "eventType": "StudioModeStateChanged",
"eventIntent": 1,
"eventData": { "eventData": {
"studioModeEnabled": true "studioModeEnabled": true
} }

View File

@ -1,63 +0,0 @@
#define MyAppName "obs-websocket"
#define MyAppVersion "5.0.0"
#define MyAppPublisher "Stephane Lepin"
#define MyAppURL "http://github.com/Palakis/obs-websocket"
[Setup]
; NOTE: The value of AppId uniquely identifies this application.
; Do not use the same AppId value in installers for other applications.
; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
AppId={{117EE44F-48E1-49E5-A381-CC8D9195CF35}
AppName={#MyAppName}
AppVersion={#MyAppVersion}
AppPublisher={#MyAppPublisher}
AppPublisherURL={#MyAppURL}
AppSupportURL={#MyAppURL}
AppUpdatesURL={#MyAppURL}
DefaultDirName={code:GetDirName}
DefaultGroupName={#MyAppName}
OutputBaseFilename=obs-websocket-{#MyAppVersion}-Windows-Installer
Compression=lzma
SolidCompression=yes
DirExistsWarning=no
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
[Files]
Source: "..\release\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "..\LICENSE"; Flags: dontcopy
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons]
Name: "{group}\{cm:ProgramOnTheWeb,{#MyAppName}}"; Filename: "{#MyAppURL}"
Name: "{group}\{cm:UninstallProgram,{#MyAppName}}"; Filename: "{uninstallexe}"
[Code]
procedure InitializeWizard();
var
GPLText: AnsiString;
Page: TOutputMsgMemoWizardPage;
begin
ExtractTemporaryFile('LICENSE');
LoadStringFromFile(ExpandConstant('{tmp}\LICENSE'), GPLText);
Page := CreateOutputMsgMemoPage(wpWelcome,
'License Information', 'Please review the license terms before installing obs-websocket',
'Press Page Down to see the rest of the agreement. Once you are aware of your rights, click Next to continue.',
String(GPLText)
);
end;
// credit where it's due :
// following function come from https://github.com/Xaymar/obs-studio_amf-encoder-plugin/blob/master/%23Resources/Installer.in.iss#L45
function GetDirName(Value: string): string;
var
InstallPath: string;
begin
// initialize default path, which will be returned when the following registry
// key queries fail due to missing keys or for some different reason
Result := '{pf}\obs-studio';
// query the first registry value; if this succeeds, return the obtained value
if RegQueryStringValue(HKLM32, 'SOFTWARE\OBS Studio', '', InstallPath) then
Result := InstallPath
end;

View File

@ -1,5 +1,5 @@
#define MyAppName "obs-websocket" #define MyAppName "obs-websocket"
#define MyAppVersion "@CMAKE_PROJECT_VERSION@" #define MyAppVersion "@OBS_WEBSOCKET_VERSION@"
#define MyAppPublisher "Stephane Lepin" #define MyAppPublisher "Stephane Lepin"
#define MyAppURL "http://github.com/Palakis/obs-websocket" #define MyAppURL "http://github.com/Palakis/obs-websocket"

View File

@ -26,6 +26,11 @@ WebSocketServer::WebSocketServer() :
_server.set_reuse_addr(true); _server.set_reuse_addr(true);
#endif #endif
_server.set_validate_handler(
websocketpp::lib::bind(
&WebSocketServer::onValidate, this, websocketpp::lib::placeholders::_1
)
);
_server.set_open_handler( _server.set_open_handler(
websocketpp::lib::bind( websocketpp::lib::bind(
&WebSocketServer::onOpen, this, websocketpp::lib::placeholders::_1 &WebSocketServer::onOpen, this, websocketpp::lib::placeholders::_1
@ -199,6 +204,7 @@ void WebSocketServer::BroadcastEvent(uint64_t requiredIntent, std::string eventT
json eventMessage; json eventMessage;
eventMessage["op"] = 5; eventMessage["op"] = 5;
eventMessage["d"]["eventType"] = eventType; eventMessage["d"]["eventType"] = eventType;
eventMessage["d"]["eventIntent"] = requiredIntent;
if (eventData.is_object()) if (eventData.is_object())
eventMessage["d"]["eventData"] = eventData; eventMessage["d"]["eventData"] = eventData;
@ -244,6 +250,21 @@ void WebSocketServer::BroadcastEvent(uint64_t requiredIntent, std::string eventT
}); });
} }
bool WebSocketServer::onValidate(websocketpp::connection_hdl hdl)
{
auto conn = _server.get_con_from_hdl(hdl);
std::vector<std::string> requestedSubprotocols = conn->get_requested_subprotocols();
for (auto subprotocol : requestedSubprotocols) {
if (subprotocol == "obswebsocket.json" || subprotocol == "obswebsocket.msgpack") {
conn->select_subprotocol(subprotocol);
break;
}
}
return true;
}
void WebSocketServer::onOpen(websocketpp::connection_hdl hdl) void WebSocketServer::onOpen(websocketpp::connection_hdl hdl)
{ {
auto conn = _server.get_con_from_hdl(hdl); auto conn = _server.get_con_from_hdl(hdl);
@ -258,16 +279,12 @@ void WebSocketServer::onOpen(websocketpp::connection_hdl hdl)
session->SetRemoteAddress(conn->get_remote_endpoint()); session->SetRemoteAddress(conn->get_remote_endpoint());
session->SetConnectedAt(QDateTime::currentSecsSinceEpoch()); session->SetConnectedAt(QDateTime::currentSecsSinceEpoch());
session->SetAuthenticationRequired(AuthenticationRequired); session->SetAuthenticationRequired(AuthenticationRequired);
std::string contentType = conn->get_request_header("Content-Type"); std::string selectedSubprotocol = conn->get_subprotocol();
if (contentType == "") { if (!selectedSubprotocol.empty()) {
; if (selectedSubprotocol == "obswebsocket.json")
} else if (contentType == "application/json") { session->SetEncoding(WebSocketEncoding::Json);
session->SetEncoding(WebSocketEncoding::Json); else if (selectedSubprotocol == "obswebsocket.msgpack")
} else if (contentType == "application/msgpack") { session->SetEncoding(WebSocketEncoding::MsgPack);
session->SetEncoding(WebSocketEncoding::MsgPack);
} else {
conn->close(WebSocketCloseCode::InvalidContentType, "Your HTTP `Content-Type` header specifies an invalid encoding type.");
return;
} }
// Build `Hello` // Build `Hello`
@ -438,7 +455,7 @@ void WebSocketServer::onMessage(websocketpp::connection_hdl hdl, websocketpp::se
if (!incomingMessage.contains("op")) { if (!incomingMessage.contains("op")) {
if (!session->IgnoreInvalidMessages()) { if (!session->IgnoreInvalidMessages()) {
ret.closeCode = WebSocketServer::WebSocketCloseCode::UnknownOpCode; ret.closeCode = WebSocketServer::WebSocketCloseCode::UnknownOpCode;
ret.closeReason = std::string("Your request is missing an `op`."); ret.closeReason = "Your request is missing an `op`.";
goto skipProcessing; goto skipProcessing;
} }
return; return;

View File

@ -46,8 +46,6 @@ class WebSocketServer : QObject
DontClose = 0, DontClose = 0,
// Reserved // Reserved
UnknownReason = 4000, UnknownReason = 4000,
// The requested `Content-Type` specified in the request HTTP header is invalid.
InvalidContentType = 4001,
// The server was unable to decode the incoming websocket message // The server was unable to decode the incoming websocket message
MessageDecodeError = 4002, MessageDecodeError = 4002,
// A data key is missing but required // A data key is missing but required
@ -99,6 +97,7 @@ class WebSocketServer : QObject
private: private:
void ServerRunner(); void ServerRunner();
bool onValidate(websocketpp::connection_hdl hdl);
void onOpen(websocketpp::connection_hdl hdl); void onOpen(websocketpp::connection_hdl hdl);
void onClose(websocketpp::connection_hdl hdl); void onClose(websocketpp::connection_hdl hdl);
void onMessage(websocketpp::connection_hdl hdl, websocketpp::server<websocketpp::config::asio>::message_ptr message); void onMessage(websocketpp::connection_hdl hdl, websocketpp::server<websocketpp::config::asio>::message_ptr message);

View File

@ -62,6 +62,7 @@ void EventHandler::ConnectSourceSignals(obs_source_t *source) // Applies to inpu
signal_handler_connect(sh, "volume", HandleInputVolumeChanged, this); signal_handler_connect(sh, "volume", HandleInputVolumeChanged, this);
signal_handler_connect(sh, "audio_sync", HandleInputAudioSyncOffsetChanged, this); signal_handler_connect(sh, "audio_sync", HandleInputAudioSyncOffsetChanged, this);
signal_handler_connect(sh, "audio_mixers", HandleInputAudioTracksChanged, this); signal_handler_connect(sh, "audio_mixers", HandleInputAudioTracksChanged, this);
//signal_handler_connect(sh, "audio_monitoring", HandleInputAudioMonitorTypeChanged, this);
if (sourceType == OBS_SOURCE_TYPE_INPUT) { if (sourceType == OBS_SOURCE_TYPE_INPUT) {
signal_handler_connect(sh, "media_started", HandleMediaInputPlaybackStarted, this); signal_handler_connect(sh, "media_started", HandleMediaInputPlaybackStarted, this);
@ -101,6 +102,7 @@ void EventHandler::DisconnectSourceSignals(obs_source_t *source)
signal_handler_disconnect(sh, "volume", HandleInputVolumeChanged, this); signal_handler_disconnect(sh, "volume", HandleInputVolumeChanged, this);
signal_handler_disconnect(sh, "audio_sync", HandleInputAudioSyncOffsetChanged, this); signal_handler_disconnect(sh, "audio_sync", HandleInputAudioSyncOffsetChanged, this);
signal_handler_disconnect(sh, "audio_mixers", HandleInputAudioTracksChanged, this); signal_handler_disconnect(sh, "audio_mixers", HandleInputAudioTracksChanged, this);
//signal_handler_disconnect(sh, "audio_monitoring", HandleInputAudioMonitorTypeChanged, this);
signal_handler_disconnect(sh, "media_started", HandleMediaInputPlaybackStarted, this); signal_handler_disconnect(sh, "media_started", HandleMediaInputPlaybackStarted, this);
signal_handler_disconnect(sh, "media_ended", HandleMediaInputPlaybackEnded, this); signal_handler_disconnect(sh, "media_ended", HandleMediaInputPlaybackEnded, this);
signal_handler_disconnect(sh, "media_pause", SourceMediaPauseMultiHandler, this); signal_handler_disconnect(sh, "media_pause", SourceMediaPauseMultiHandler, this);

View File

@ -75,6 +75,7 @@ class EventHandler
static void HandleInputVolumeChanged(void *param, calldata_t *data); // Direct callback static void HandleInputVolumeChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputAudioSyncOffsetChanged(void *param, calldata_t *data); // Direct callback static void HandleInputAudioSyncOffsetChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputAudioTracksChanged(void *param, calldata_t *data); // Direct callback static void HandleInputAudioTracksChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputAudioMonitorTypeChanged(void *param, calldata_t *data); // Direct callback
// Transitions // Transitions
void HandleTransitionCreated(obs_source_t *source); void HandleTransitionCreated(obs_source_t *source);

View File

@ -149,3 +149,36 @@ void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data)
eventData["inputAudioTracks"] = inputAudioTracks; eventData["inputAudioTracks"] = inputAudioTracks;
eventHandler->_webSocketServer->BroadcastEvent(EventSubscription::Inputs, "InputAudioTracksChanged", eventData); eventHandler->_webSocketServer->BroadcastEvent(EventSubscription::Inputs, "InputAudioTracksChanged", eventData);
} }
void EventHandler::HandleInputAudioMonitorTypeChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
return;
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT)
return;
enum obs_monitoring_type monitorType = (obs_monitoring_type)calldata_int(data, "type");
std::string monitorTypeString;
switch (monitorType) {
default:
case OBS_MONITORING_TYPE_NONE:
monitorTypeString = "OBS_WEBSOCKET_MONITOR_TYPE_NONE";
break;
case OBS_MONITORING_TYPE_MONITOR_ONLY:
monitorTypeString = "OBS_WEBSOCKET_MONITOR_TYPE_MONITOR_ONLY";
break;
case OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT:
monitorTypeString = "OBS_WEBSOCKET_MONITOR_TYPE_MONITOR_AND_OUTPUT";
break;
}
json eventData;
eventData["inputName"] = obs_source_get_name(source);
eventData["monitorType"] = monitorTypeString;
eventHandler->_webSocketServer->BroadcastEvent(EventSubscription::Inputs, "InputAudioMonitorTypeChanged", eventData);
}

View File

@ -1,5 +1,6 @@
#include <QClipboard> #include <QClipboard>
#include <QPainter> #include <QPainter>
#include <QUrl>
#include <obs-module.h> #include <obs-module.h>
#include "ConnectInfo.h" #include "ConnectInfo.h"
@ -45,7 +46,7 @@ void ConnectInfo::showEvent(QShowEvent *event)
QString serverPassword; QString serverPassword;
if (conf->AuthRequired) { if (conf->AuthRequired) {
ui->copyServerPasswordButton->setEnabled(true); ui->copyServerPasswordButton->setEnabled(true);
serverPassword = conf->ServerPassword; serverPassword = QUrl::toPercentEncoding(conf->ServerPassword);
} else { } else {
ui->copyServerPasswordButton->setEnabled(false); ui->copyServerPasswordButton->setEnabled(false);
serverPassword = obs_module_text("OBSWebSocket.ConnectInfo.ServerPasswordPlaceholderText"); serverPassword = obs_module_text("OBSWebSocket.ConnectInfo.ServerPasswordPlaceholderText");
@ -54,9 +55,9 @@ void ConnectInfo::showEvent(QShowEvent *event)
QString connectString; QString connectString;
if (conf->AuthRequired) if (conf->AuthRequired)
connectString = QString("obswebsocket|%1:%2|%3").arg(serverIp).arg(serverPort).arg(serverPassword); connectString = QString("obsws://%1:%2/%3").arg(serverIp).arg(serverPort).arg(serverPassword);
else else
connectString = QString("obswebsocket|%1:%2").arg(serverIp).arg(serverPort); connectString = QString("obsws://%1:%2").arg(serverIp).arg(serverPort);
DrawQr(connectString); DrawQr(connectString);
} }

View File

@ -29,6 +29,7 @@ void ___data_array_dummy_addref(obs_data_array_t*) {};
void ___output_dummy_addref(obs_output_t*) {}; void ___output_dummy_addref(obs_output_t*) {};
void ___data_item_dummy_addref(obs_data_item_t*) {}; void ___data_item_dummy_addref(obs_data_item_t*) {};
void ___data_item_release(obs_data_item_t* dataItem){ obs_data_item_release(&dataItem); }; void ___data_item_release(obs_data_item_t* dataItem){ obs_data_item_release(&dataItem); };
void ___properties_dummy_addref(obs_properties_t*) {};
bool obs_module_load(void) bool obs_module_load(void)
{ {

View File

@ -18,6 +18,7 @@ void ___data_array_dummy_addref(obs_data_array_t*);
void ___output_dummy_addref(obs_output_t*); void ___output_dummy_addref(obs_output_t*);
void ___data_item_dummy_addref(obs_data_item_t*); void ___data_item_dummy_addref(obs_data_item_t*);
void ___data_item_release(obs_data_item_t*); void ___data_item_release(obs_data_item_t*);
void ___properties_dummy_addref(obs_properties_t*);
using OBSSourceAutoRelease = using OBSSourceAutoRelease =
OBSRef<obs_source_t*, ___source_dummy_addref, obs_source_release>; OBSRef<obs_source_t*, ___source_dummy_addref, obs_source_release>;
@ -31,6 +32,8 @@ using OBSOutputAutoRelease =
OBSRef<obs_output_t*, ___output_dummy_addref, obs_output_release>; OBSRef<obs_output_t*, ___output_dummy_addref, obs_output_release>;
using OBSDataItemAutoRelease = using OBSDataItemAutoRelease =
OBSRef<obs_data_item_t*, ___data_item_dummy_addref, ___data_item_release>; OBSRef<obs_data_item_t*, ___data_item_dummy_addref, ___data_item_release>;
using OBSPropertiesAutoDestroy =
OBSRef<obs_properties_t*, ___properties_dummy_addref, obs_properties_destroy>;
class Config; class Config;
typedef std::shared_ptr<Config> ConfigPtr; typedef std::shared_ptr<Config> ConfigPtr;

View File

@ -1,28 +0,0 @@
/*
obs-websocket
Copyright (C) 2021 Kyle Manning <tt2468@irltoolkit.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#pragma once
#include <util/base.h>
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)
#define OBS_WEBSOCKET_VERSION "5.0.0"
#define OBS_WEBSOCKET_RPC_VERSION 1
#define QT_TO_UTF8(str) str.toUtf8().constData()

View File

@ -21,7 +21,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <util/base.h> #include <util/base.h>
#define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__) #define blog(level, msg, ...) blog(level, "[obs-websocket] " msg, ##__VA_ARGS__)
#define OBS_WEBSOCKET_VERSION "@CMAKE_PROJECT_VERSION@" #define OBS_WEBSOCKET_VERSION "@OBS_WEBSOCKET_VERSION@"
#define OBS_WEBSOCKET_RPC_VERSION @OBS_WEBSOCKET_RPC_VERSION@ #define OBS_WEBSOCKET_RPC_VERSION @OBS_WEBSOCKET_RPC_VERSION@

View File

@ -59,9 +59,22 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"ToggleInputMute", &RequestHandler::ToggleInputMute}, {"ToggleInputMute", &RequestHandler::ToggleInputMute},
{"GetInputVolume", &RequestHandler::GetInputVolume}, {"GetInputVolume", &RequestHandler::GetInputVolume},
{"SetInputVolume", &RequestHandler::SetInputVolume}, {"SetInputVolume", &RequestHandler::SetInputVolume},
{"GetInputAudioSyncOffset", &RequestHandler::GetInputAudioSyncOffset},
{"SetInputAudioSyncOffset", &RequestHandler::SetInputAudioSyncOffset},
{"GetInputAudioMonitorType", &RequestHandler::GetInputAudioMonitorType},
{"SetInputAudioMonitorType", &RequestHandler::SetInputAudioMonitorType},
{"GetInputPropertiesListPropertyItems", &RequestHandler::GetInputPropertiesListPropertyItems},
{"PressInputPropertiesButton", &RequestHandler::PressInputPropertiesButton},
// Scene Items
{"GetSceneItemList", &RequestHandler::GetSceneItemList},
{"GetGroupSceneItemList", &RequestHandler::GetGroupSceneItemList},
{"CreateSceneItem", &RequestHandler::CreateSceneItem},
{"RemoveSceneItem", &RequestHandler::RemoveSceneItem},
// Stream // Stream
{"GetStreamStatus", &RequestHandler::GetStreamStatus}, {"GetStreamStatus", &RequestHandler::GetStreamStatus},
{"ToggleStream", &RequestHandler::ToggleStream},
{"StartStream", &RequestHandler::StartStream}, {"StartStream", &RequestHandler::StartStream},
{"StopStream", &RequestHandler::StopStream}, {"StopStream", &RequestHandler::StopStream},
}; };
@ -69,7 +82,7 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
RequestResult RequestHandler::ProcessRequest(const Request& request) RequestResult RequestHandler::ProcessRequest(const Request& request)
{ {
if (!request.RequestData.is_null() && !request.RequestData.is_object()) if (!request.RequestData.is_null() && !request.RequestData.is_object())
return RequestResult::Error(RequestStatus::InvalidRequestParameterDataType, "Your request data is not an object."); return RequestResult::Error(RequestStatus::InvalidRequestParameterType, "Your request data is not an object.");
if (request.RequestType.empty()) if (request.RequestType.empty())
return RequestResult::Error(RequestStatus::MissingRequestType, "Your request is missing a `requestType`"); return RequestResult::Error(RequestStatus::MissingRequestType, "Your request is missing a `requestType`");

View File

@ -8,6 +8,7 @@
#include "rpc/RequestResult.h" #include "rpc/RequestResult.h"
#include "../obs-websocket.h" #include "../obs-websocket.h"
#include "../utils/Obs.h" #include "../utils/Obs.h"
#include "../plugin-macros.generated.h"
class RequestHandler; class RequestHandler;
typedef RequestResult(RequestHandler::*RequestMethodHandler)(const Request&); typedef RequestResult(RequestHandler::*RequestMethodHandler)(const Request&);
@ -74,9 +75,22 @@ class RequestHandler {
RequestResult ToggleInputMute(const Request&); RequestResult ToggleInputMute(const Request&);
RequestResult GetInputVolume(const Request&); RequestResult GetInputVolume(const Request&);
RequestResult SetInputVolume(const Request&); RequestResult SetInputVolume(const Request&);
RequestResult GetInputAudioSyncOffset(const Request&);
RequestResult SetInputAudioSyncOffset(const Request&);
RequestResult GetInputAudioMonitorType(const Request&);
RequestResult SetInputAudioMonitorType(const Request&);
RequestResult GetInputPropertiesListPropertyItems(const Request&);
RequestResult PressInputPropertiesButton(const Request&);
// Scene Items
RequestResult GetSceneItemList(const Request&);
RequestResult GetGroupSceneItemList(const Request&);
RequestResult CreateSceneItem(const Request&);
RequestResult RemoveSceneItem(const Request&);
// Stream // Stream
RequestResult GetStreamStatus(const Request&); RequestResult GetStreamStatus(const Request&);
RequestResult ToggleStream(const Request&);
RequestResult StartStream(const Request&); RequestResult StartStream(const Request&);
RequestResult StopStream(const Request&); RequestResult StopStream(const Request&);

View File

@ -2,7 +2,6 @@
#include <util/config-file.h> #include <util/config-file.h>
#include "RequestHandler.h" #include "RequestHandler.h"
#include "../plugin-macros.generated.h"
RequestResult RequestHandler::GetPersistentData(const Request& request) RequestResult RequestHandler::GetPersistentData(const Request& request)
{ {
@ -16,11 +15,11 @@ RequestResult RequestHandler::GetPersistentData(const Request& request)
std::string persistentDataPath = Utils::Obs::StringHelper::GetCurrentProfilePath(); std::string persistentDataPath = Utils::Obs::StringHelper::GetCurrentProfilePath();
if (realm == "OBS_WEBSOCKET_DATA_REALM_GLOBAL") if (realm == "OBS_WEBSOCKET_DATA_REALM_GLOBAL")
persistentDataPath += "../../../obsWebSocketPersistentData.json"; persistentDataPath += "/../../../obsWebSocketPersistentData.json";
else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE") else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE")
persistentDataPath += "/obsWebSocketPersistentData.json"; persistentDataPath += "/obsWebSocketPersistentData.json";
else else
return RequestResult::Error(RequestStatus::DataRealmNotFound, "You have specified an invalid persistent data realm."); return RequestResult::Error(RequestStatus::ResourceNotFound, "You have specified an invalid persistent data realm.");
json responseData; json responseData;
json persistentData; json persistentData;
@ -45,11 +44,11 @@ RequestResult RequestHandler::SetPersistentData(const Request& request)
std::string persistentDataPath = Utils::Obs::StringHelper::GetCurrentProfilePath(); std::string persistentDataPath = Utils::Obs::StringHelper::GetCurrentProfilePath();
if (realm == "OBS_WEBSOCKET_DATA_REALM_GLOBAL") if (realm == "OBS_WEBSOCKET_DATA_REALM_GLOBAL")
persistentDataPath += "../../../obsWebSocketPersistentData.json"; persistentDataPath += "/../../../obsWebSocketPersistentData.json";
else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE") else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE")
persistentDataPath += "/obsWebSocketPersistentData.json"; persistentDataPath += "/obsWebSocketPersistentData.json";
else else
return RequestResult::Error(RequestStatus::DataRealmNotFound, "You have specified an invalid persistent data realm."); return RequestResult::Error(RequestStatus::ResourceNotFound, "You have specified an invalid persistent data realm.");
json persistentData = json::object(); json persistentData = json::object();
Utils::Json::GetJsonFileContent(persistentDataPath, persistentData); Utils::Json::GetJsonFileContent(persistentDataPath, persistentData);
@ -79,7 +78,7 @@ RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request)
auto sceneCollections = Utils::Obs::ListHelper::GetSceneCollectionList(); auto sceneCollections = Utils::Obs::ListHelper::GetSceneCollectionList();
if (std::find(sceneCollections.begin(), sceneCollections.end(), sceneCollectionName) == sceneCollections.end()) if (std::find(sceneCollections.begin(), sceneCollections.end(), sceneCollectionName) == sceneCollections.end())
return RequestResult::Error(RequestStatus::SceneCollectionNotFound); return RequestResult::Error(RequestStatus::ResourceNotFound);
std::string currentSceneCollectionName = Utils::Obs::StringHelper::GetCurrentSceneCollection(); std::string currentSceneCollectionName = Utils::Obs::StringHelper::GetCurrentSceneCollection();
// Avoid queueing tasks if nothing will change // Avoid queueing tasks if nothing will change
@ -103,7 +102,7 @@ RequestResult RequestHandler::CreateSceneCollection(const Request& request)
auto sceneCollections = Utils::Obs::ListHelper::GetSceneCollectionList(); auto sceneCollections = Utils::Obs::ListHelper::GetSceneCollectionList();
if (std::find(sceneCollections.begin(), sceneCollections.end(), sceneCollectionName) != sceneCollections.end()) if (std::find(sceneCollections.begin(), sceneCollections.end(), sceneCollectionName) != sceneCollections.end())
return RequestResult::Error(RequestStatus::SceneCollectionAlreadyExists); return RequestResult::Error(RequestStatus::ResourceAlreadyExists);
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window()); QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window());
bool success = false; bool success = false;
@ -133,7 +132,7 @@ RequestResult RequestHandler::SetCurrentProfile(const Request& request)
auto profiles = Utils::Obs::ListHelper::GetProfileList(); auto profiles = Utils::Obs::ListHelper::GetProfileList();
if (std::find(profiles.begin(), profiles.end(), profileName) == profiles.end()) if (std::find(profiles.begin(), profiles.end(), profileName) == profiles.end())
return RequestResult::Error(RequestStatus::ProfileNotFound); return RequestResult::Error(RequestStatus::ResourceNotFound);
std::string currentProfileName = Utils::Obs::StringHelper::GetCurrentProfile(); std::string currentProfileName = Utils::Obs::StringHelper::GetCurrentProfile();
// Avoid queueing tasks if nothing will change // Avoid queueing tasks if nothing will change
@ -157,7 +156,7 @@ RequestResult RequestHandler::CreateProfile(const Request& request)
auto profiles = Utils::Obs::ListHelper::GetProfileList(); auto profiles = Utils::Obs::ListHelper::GetProfileList();
if (std::find(profiles.begin(), profiles.end(), profileName) != profiles.end()) if (std::find(profiles.begin(), profiles.end(), profileName) != profiles.end())
return RequestResult::Error(RequestStatus::ProfileAlreadyExists); return RequestResult::Error(RequestStatus::ResourceAlreadyExists);
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window()); QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window());
QMetaObject::invokeMethod(mainWindow, "NewProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName))); QMetaObject::invokeMethod(mainWindow, "NewProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName)));
@ -176,10 +175,10 @@ RequestResult RequestHandler::RemoveProfile(const Request& request)
auto profiles = Utils::Obs::ListHelper::GetProfileList(); auto profiles = Utils::Obs::ListHelper::GetProfileList();
if (std::find(profiles.begin(), profiles.end(), profileName) == profiles.end()) if (std::find(profiles.begin(), profiles.end(), profileName) == profiles.end())
return RequestResult::Error(RequestStatus::ProfileNotFound); return RequestResult::Error(RequestStatus::ResourceNotFound);
if (profiles.size() < 2) if (profiles.size() < 2)
return RequestResult::Error(RequestStatus::NotEnoughProfiles); return RequestResult::Error(RequestStatus::NotEnoughResources);
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window()); QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window());
QMetaObject::invokeMethod(mainWindow, "DeleteProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName))); QMetaObject::invokeMethod(mainWindow, "DeleteProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName)));
@ -233,12 +232,12 @@ RequestResult RequestHandler::SetProfileParameter(const Request& request)
// Using check helpers here would just make the logic more complicated // Using check helpers here would just make the logic more complicated
if (!request.RequestData.contains("parameterValue") || request.RequestData["parameterValue"].is_null()) { if (!request.RequestData.contains("parameterValue") || request.RequestData["parameterValue"].is_null()) {
if (!config_remove_value(profile, parameterCategory.c_str(), parameterName.c_str())) if (!config_remove_value(profile, parameterCategory.c_str(), parameterName.c_str()))
return RequestResult::Error(RequestStatus::ConfigParameterNotFound); return RequestResult::Error(RequestStatus::ResourceNotFound, "There are no existing instances of that profile parameter.");
} else if (request.RequestData["parameterValue"].is_string()) { } else if (request.RequestData["parameterValue"].is_string()) {
std::string parameterValue = request.RequestData["parameterValue"]; std::string parameterValue = request.RequestData["parameterValue"];
config_set_string(profile, parameterCategory.c_str(), parameterName.c_str(), parameterValue.c_str()); config_set_string(profile, parameterCategory.c_str(), parameterName.c_str(), parameterValue.c_str());
} else { } else {
return RequestResult::Error(RequestStatus::InvalidRequestParameterDataType, "The parameter `parameterValue` must be a string."); return RequestResult::Error(RequestStatus::InvalidRequestParameterType, "The parameter `parameterValue` must be a string.");
} }
return RequestResult::Success(); return RequestResult::Success();
@ -322,7 +321,7 @@ RequestResult RequestHandler::GetStreamServiceSettings(const Request& request)
RequestResult RequestHandler::SetStreamServiceSettings(const Request& request) RequestResult RequestHandler::SetStreamServiceSettings(const Request& request)
{ {
if (obs_frontend_streaming_active()) if (obs_frontend_streaming_active())
return RequestResult::Error(RequestStatus::StreamRunning); return RequestResult::Error(RequestStatus::OutputRunning, "You cannot change stream service settings while streaming.");
RequestStatus::RequestStatus statusCode; RequestStatus::RequestStatus statusCode;
std::string comment; std::string comment;
@ -350,7 +349,7 @@ RequestResult RequestHandler::SetStreamServiceSettings(const Request& request)
OBSService newStreamService = obs_service_create(requestedStreamServiceType.c_str(), "obs_websocket_custom_service", requestedStreamServiceSettings, NULL); OBSService newStreamService = obs_service_create(requestedStreamServiceType.c_str(), "obs_websocket_custom_service", requestedStreamServiceSettings, NULL);
// TODO: Check service type here, instead of relying on service creation to fail. // TODO: Check service type here, instead of relying on service creation to fail.
if (!newStreamService) if (!newStreamService)
return RequestResult::Error(RequestStatus::StreamServiceCreationFailed, "Creating the stream service with the requested streamServiceType failed. It may be an invalid type."); return RequestResult::Error(RequestStatus::ResourceCreationFailed, "Creating the stream service with the requested streamServiceType failed. It may be an invalid type.");
obs_frontend_set_streaming_service(newStreamService); obs_frontend_set_streaming_service(newStreamService);
} }

View File

@ -4,7 +4,6 @@
#include "../eventhandler/types/EventSubscription.h" #include "../eventhandler/types/EventSubscription.h"
#include "../obs-websocket.h" #include "../obs-websocket.h"
#include "../WebSocketServer.h" #include "../WebSocketServer.h"
#include "../plugin-macros.generated.h"
RequestResult RequestHandler::GetVersion(const Request& request) RequestResult RequestHandler::GetVersion(const Request& request)
{ {
@ -66,7 +65,7 @@ RequestResult RequestHandler::TriggerHotkeyByName(const Request& request)
obs_hotkey_t *hotkey = Utils::Obs::SearchHelper::GetHotkeyByName(request.RequestData["hotkeyName"]); obs_hotkey_t *hotkey = Utils::Obs::SearchHelper::GetHotkeyByName(request.RequestData["hotkeyName"]);
if (!hotkey) if (!hotkey)
return RequestResult::Error(RequestStatus::HotkeyNotFound); return RequestResult::Error(RequestStatus::ResourceNotFound, "No hotkeys were found by that name.");
obs_hotkey_trigger_routed_callback(obs_hotkey_get_id(hotkey), true); obs_hotkey_trigger_routed_callback(obs_hotkey_get_id(hotkey), true);

View File

@ -1,5 +1,4 @@
#include "RequestHandler.h" #include "RequestHandler.h"
#include "../plugin-macros.generated.h"
RequestResult RequestHandler::GetInputList(const Request& request) RequestResult RequestHandler::GetInputList(const Request& request)
{ {
@ -50,7 +49,7 @@ RequestResult RequestHandler::CreateInput(const Request& request)
std::string inputName = request.RequestData["inputName"]; std::string inputName = request.RequestData["inputName"];
OBSSourceAutoRelease existingInput = obs_get_source_by_name(inputName.c_str()); OBSSourceAutoRelease existingInput = obs_get_source_by_name(inputName.c_str());
if (existingInput) if (existingInput)
return RequestResult::Error(RequestStatus::SourceAlreadyExists, "A source already exists by that input name."); return RequestResult::Error(RequestStatus::ResourceAlreadyExists, "A source already exists by that input name.");
std::string inputKind = request.RequestData["inputKind"]; std::string inputKind = request.RequestData["inputKind"];
@ -99,7 +98,7 @@ RequestResult RequestHandler::SetInputName(const Request& request)
OBSSourceAutoRelease existingSource = obs_get_source_by_name(newInputName.c_str()); OBSSourceAutoRelease existingSource = obs_get_source_by_name(newInputName.c_str());
if (existingSource) if (existingSource)
return RequestResult::Error(RequestStatus::SourceAlreadyExists, "A source already exists by that new input name."); return RequestResult::Error(RequestStatus::ResourceAlreadyExists, "A source already exists by that new input name.");
obs_source_set_name(input, newInputName.c_str()); obs_source_set_name(input, newInputName.c_str());
@ -268,3 +267,141 @@ RequestResult RequestHandler::SetInputVolume(const Request& request)
return RequestResult::Success(); return RequestResult::Success();
} }
RequestResult RequestHandler::GetInputAudioSyncOffset(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!input)
return RequestResult::Error(statusCode, comment);
json responseData;
// Offset is stored in nanoseconds in OBS.
responseData["inputAudioSyncOffset"] = obs_source_get_sync_offset(input) / 1000000;
return RequestResult::Success(responseData);
}
RequestResult RequestHandler::SetInputAudioSyncOffset(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!(input && request.ValidateNumber("inputAudioSyncOffset", statusCode, comment, -950, 20000)))
return RequestResult::Error(statusCode, comment);
int64_t syncOffset = request.RequestData["inputAudioSyncOffset"];
obs_source_set_sync_offset(input, syncOffset * 1000000);
return RequestResult::Success();
}
RequestResult RequestHandler::GetInputAudioMonitorType(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!input)
return RequestResult::Error(statusCode, comment);
json responseData;
responseData["monitorType"] = Utils::Obs::StringHelper::GetInputMonitorTypeString(input);
return RequestResult::Success(responseData);
}
RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!(input && request.ValidateString("monitorType", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
enum obs_monitoring_type monitorType;
std::string monitorTypeString = request.RequestData["monitorType"];
if (monitorTypeString == "OBS_MONITORING_TYPE_NONE")
monitorType = OBS_MONITORING_TYPE_NONE;
else if (monitorTypeString == "OBS_MONITORING_TYPE_MONITOR_ONLY")
monitorType = OBS_MONITORING_TYPE_MONITOR_ONLY;
else if (monitorTypeString == "OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT")
monitorType = OBS_MONITORING_TYPE_MONITOR_AND_OUTPUT;
else
return RequestResult::Error(RequestStatus::InvalidRequestParameter, std::string("Unknown monitor type: ") + monitorTypeString);
obs_source_set_monitoring_type(input, monitorType);
return RequestResult::Success();
}
std::vector<json> GetListPropertyItems(obs_property_t *property)
{
std::vector<json> ret;
enum obs_combo_format itemFormat = obs_property_list_format(property);
size_t itemCount = obs_property_list_item_count(property);
for (size_t i = 0; i < itemCount; i++) {
json itemData;
itemData["itemName"] = obs_property_list_item_name(property, i);
itemData["itemEnabled"] = !obs_property_list_item_disabled(property, i);
if (itemFormat == OBS_COMBO_FORMAT_INT) {
itemData["itemValue"] = obs_property_list_item_int(property, i);
} else if (itemFormat == OBS_COMBO_FORMAT_FLOAT) {
itemData["itemValue"] = obs_property_list_item_float(property, i);
} else if (itemFormat == OBS_COMBO_FORMAT_STRING) {
itemData["itemValue"] = obs_property_list_item_string(property, i);
}
ret.push_back(itemData);
}
return ret;
}
RequestResult RequestHandler::GetInputPropertiesListPropertyItems(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!(input && request.ValidateString("propertyName", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
std::string propertyName = request.RequestData["propertyName"];
OBSPropertiesAutoDestroy inputProperties = obs_source_properties(input);
obs_property_t *property = obs_properties_get(inputProperties, propertyName.c_str());
if (!property)
return RequestResult::Error(RequestStatus::ResourceNotFound, "Unable to find a property by that name.");
if (obs_property_get_type(property) != OBS_PROPERTY_LIST)
return RequestResult::Error(RequestStatus::InvalidResourceType, "The property found is not a list.");
json responseData;
responseData["propertyItems"] = GetListPropertyItems(property);
return RequestResult::Success(responseData);
}
RequestResult RequestHandler::PressInputPropertiesButton(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!(input && request.ValidateString("propertyName", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
std::string propertyName = request.RequestData["propertyName"];
OBSPropertiesAutoDestroy inputProperties = obs_source_properties(input);
obs_property_t *property = obs_properties_get(inputProperties, propertyName.c_str());
if (!property)
return RequestResult::Error(RequestStatus::ResourceNotFound, "Unable to find a property by that name.");
if (obs_property_get_type(property) != OBS_PROPERTY_BUTTON)
return RequestResult::Error(RequestStatus::InvalidResourceType, "The property found is not a button.");
if (!obs_property_enabled(property))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The property item found is not enabled.");
obs_property_button_clicked(property, input);
return RequestResult::Success();
}

View File

@ -0,0 +1,71 @@
#include "RequestHandler.h"
RequestResult RequestHandler::GetSceneItemList(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease scene = request.ValidateScene("sceneName", statusCode, comment);
if (!scene)
return RequestResult::Error(statusCode, comment);
json responseData;
responseData["sceneItems"] = Utils::Obs::ListHelper::GetSceneItemList(obs_scene_from_source(scene));
return RequestResult::Success(responseData);
}
RequestResult RequestHandler::GetGroupSceneItemList(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease scene = request.ValidateScene("sceneName", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_GROUP_ONLY);
if (!scene)
return RequestResult::Error(statusCode, comment);
json responseData;
responseData["sceneItems"] = Utils::Obs::ListHelper::GetSceneItemList(obs_group_from_source(scene));
return RequestResult::Success(responseData);
}
RequestResult RequestHandler::CreateSceneItem(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease sceneSource = request.ValidateScene("sceneName", statusCode, comment);
if (!sceneSource)
return RequestResult::Error(statusCode, comment);
OBSScene scene = obs_scene_from_source(sceneSource);
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment);
if (!source)
return RequestResult::Error(statusCode, comment);
if (request.RequestData["sceneName"] == request.RequestData["sourceName"])
return RequestResult::Error(RequestStatus::CannotAct, "You cannot create scene item of a scene within itself.");
bool sceneItemEnabled = true;
if (request.RequestData.contains("sceneItemEnabled") && request.RequestData["sceneItemEnabled"].is_boolean())
sceneItemEnabled = request.RequestData["sceneItemEnabled"];
obs_sceneitem_t *sceneItem = Utils::Obs::ActionHelper::CreateSceneItem(source, scene, sceneItemEnabled);
json responseData;
responseData["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
return RequestResult::Success(responseData);
}
RequestResult RequestHandler::RemoveSceneItem(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment);
if (!sceneItem)
return RequestResult::Error(statusCode, comment);
obs_sceneitem_remove(sceneItem);
return RequestResult::Success();
}

View File

@ -1,5 +1,4 @@
#include "RequestHandler.h" #include "RequestHandler.h"
#include "../plugin-macros.generated.h"
RequestResult RequestHandler::GetSceneList(const Request& request) RequestResult RequestHandler::GetSceneList(const Request& request)
{ {
@ -79,7 +78,7 @@ RequestResult RequestHandler::CreateScene(const Request& request)
OBSSourceAutoRelease scene = obs_get_source_by_name(sceneName.c_str()); OBSSourceAutoRelease scene = obs_get_source_by_name(sceneName.c_str());
if (scene) if (scene)
return RequestResult::Error(RequestStatus::SourceAlreadyExists, "A source already exists by that scene name."); return RequestResult::Error(RequestStatus::ResourceAlreadyExists, "A source already exists by that scene name.");
obs_scene_t *createdScene = obs_scene_create(sceneName.c_str()); obs_scene_t *createdScene = obs_scene_create(sceneName.c_str());
obs_scene_release(createdScene); obs_scene_release(createdScene);
@ -95,6 +94,9 @@ RequestResult RequestHandler::RemoveScene(const Request& request)
if (!scene) if (!scene)
return RequestResult::Error(statusCode, comment); return RequestResult::Error(statusCode, comment);
if (Utils::Obs::NumberHelper::GetSceneCount() < 2)
return RequestResult::Error(RequestStatus::NotEnoughResources, "You cannot remove the last scene in the collection.");
obs_source_remove(scene); obs_source_remove(scene);
return RequestResult::Success(); return RequestResult::Success();
@ -112,7 +114,7 @@ RequestResult RequestHandler::SetSceneName(const Request& request)
OBSSourceAutoRelease existingSource = obs_get_source_by_name(newSceneName.c_str()); OBSSourceAutoRelease existingSource = obs_get_source_by_name(newSceneName.c_str());
if (existingSource) if (existingSource)
return RequestResult::Error(RequestStatus::SourceAlreadyExists, "A source already exists by that new scene name."); return RequestResult::Error(RequestStatus::ResourceAlreadyExists, "A source already exists by that new scene name.");
obs_source_set_name(scene, newSceneName.c_str()); obs_source_set_name(scene, newSceneName.c_str());

View File

@ -5,7 +5,6 @@
#include <QDir> #include <QDir>
#include "RequestHandler.h" #include "RequestHandler.h"
#include "../plugin-macros.generated.h"
QImage TakeSourceScreenshot(obs_source_t *source, bool &success, uint32_t requestedWidth = 0, uint32_t requestedHeight = 0) QImage TakeSourceScreenshot(obs_source_t *source, bool &success, uint32_t requestedWidth = 0, uint32_t requestedHeight = 0)
{ {
@ -102,10 +101,10 @@ RequestResult RequestHandler::GetSourceActive(const Request& request)
OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.c_str()); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.c_str());
if (!source) if (!source)
return RequestResult::Error(RequestStatus::SourceNotFound); return RequestResult::Error(RequestStatus::ResourceNotFound);
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT && obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE) if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT && obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE)
return RequestResult::Error(RequestStatus::InvalidSourceType, "The specified source is not an input or a scene."); return RequestResult::Error(RequestStatus::InvalidResourceType, "The specified source is not an input or a scene.");
json responseData; json responseData;
responseData["videoActive"] = obs_source_active(source); responseData["videoActive"] = obs_source_active(source);
@ -124,10 +123,10 @@ RequestResult RequestHandler::GetSourceScreenshot(const Request& request)
OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.c_str()); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.c_str());
if (!source) if (!source)
return RequestResult::Error(RequestStatus::SourceNotFound); return RequestResult::Error(RequestStatus::ResourceNotFound);
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT && obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE) if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT && obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE)
return RequestResult::Error(RequestStatus::InvalidSourceType, "The specified source is not an input or a scene."); return RequestResult::Error(RequestStatus::InvalidResourceType, "The specified source is not an input or a scene.");
std::string imageFormat = request.RequestData["imageFormat"]; std::string imageFormat = request.RequestData["imageFormat"];
@ -163,14 +162,14 @@ RequestResult RequestHandler::GetSourceScreenshot(const Request& request)
QImage renderedImage = TakeSourceScreenshot(source, success, requestedWidth, requestedHeight); QImage renderedImage = TakeSourceScreenshot(source, success, requestedWidth, requestedHeight);
if (!success) if (!success)
return RequestResult::Error(RequestStatus::ScreenshotRenderFailed); return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Failed to render screenshot.");
QByteArray encodedImgBytes; QByteArray encodedImgBytes;
QBuffer buffer(&encodedImgBytes); QBuffer buffer(&encodedImgBytes);
buffer.open(QBuffer::WriteOnly); buffer.open(QBuffer::WriteOnly);
if (!renderedImage.save(&buffer, imageFormat.c_str(), compressionQuality)) if (!renderedImage.save(&buffer, imageFormat.c_str(), compressionQuality))
return RequestResult::Error(RequestStatus::ScreenshotEncodeFailed); return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Failed to encode screenshot.");
buffer.close(); buffer.close();
@ -192,10 +191,10 @@ RequestResult RequestHandler::SaveSourceScreenshot(const Request& request)
OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.c_str()); OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.c_str());
if (!source) if (!source)
return RequestResult::Error(RequestStatus::SourceNotFound); return RequestResult::Error(RequestStatus::ResourceNotFound, "No source was found by that name.");
if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT && obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE) if (obs_source_get_type(source) != OBS_SOURCE_TYPE_INPUT && obs_source_get_type(source) != OBS_SOURCE_TYPE_SCENE)
return RequestResult::Error(RequestStatus::InvalidSourceType, "The specified source is not an input or a scene."); return RequestResult::Error(RequestStatus::InvalidResourceType, "The specified source is not an input or a scene.");
std::string imageFormat = request.RequestData["imageFormat"]; std::string imageFormat = request.RequestData["imageFormat"];
@ -231,18 +230,18 @@ RequestResult RequestHandler::SaveSourceScreenshot(const Request& request)
QImage renderedImage = TakeSourceScreenshot(source, success, requestedWidth, requestedHeight); QImage renderedImage = TakeSourceScreenshot(source, success, requestedWidth, requestedHeight);
if (!success) if (!success)
return RequestResult::Error(RequestStatus::ScreenshotRenderFailed); return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Failed to render screenshot.");
std::string imageFilePath = request.RequestData["imageFilePath"]; std::string imageFilePath = request.RequestData["imageFilePath"];
QFileInfo filePathInfo(QString::fromStdString(imageFilePath)); QFileInfo filePathInfo(QString::fromStdString(imageFilePath));
if (!filePathInfo.absoluteDir().exists()) if (!filePathInfo.absoluteDir().exists())
return RequestResult::Error(RequestStatus::DirectoryNotFound, "The directory for your file path does not exist."); return RequestResult::Error(RequestStatus::ResourceNotFound, "The directory for your file path does not exist.");
QString absoluteFilePath = filePathInfo.absoluteFilePath(); QString absoluteFilePath = filePathInfo.absoluteFilePath();
if (!renderedImage.save(absoluteFilePath, imageFormat.c_str(), compressionQuality)) if (!renderedImage.save(absoluteFilePath, imageFormat.c_str(), compressionQuality))
return RequestResult::Error(RequestStatus::ScreenshotSaveFailed); return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Failed to save screenshot.");
return RequestResult::Success(); return RequestResult::Success();
} }

View File

@ -1,5 +1,4 @@
#include "RequestHandler.h" #include "RequestHandler.h"
#include "../plugin-macros.generated.h"
RequestResult RequestHandler::GetStreamStatus(const Request& request) RequestResult RequestHandler::GetStreamStatus(const Request& request)
{ {
@ -17,10 +16,24 @@ RequestResult RequestHandler::GetStreamStatus(const Request& request)
return RequestResult::Success(responseData); return RequestResult::Success(responseData);
} }
RequestResult RequestHandler::ToggleStream(const Request& request)
{
json responseData;
if (obs_frontend_streaming_active()) {
obs_frontend_streaming_stop();
responseData["outputActive"] = false;
} else {
obs_frontend_streaming_start();
responseData["outputActive"] = true;
}
return RequestResult::Success(responseData);
}
RequestResult RequestHandler::StartStream(const Request& request) RequestResult RequestHandler::StartStream(const Request& request)
{ {
if (obs_frontend_streaming_active()) if (obs_frontend_streaming_active())
return RequestResult::Error(RequestStatus::StreamRunning); return RequestResult::Error(RequestStatus::OutputRunning);
// TODO: Call signal directly to perform blocking wait // TODO: Call signal directly to perform blocking wait
obs_frontend_streaming_start(); obs_frontend_streaming_start();
@ -31,7 +44,7 @@ RequestResult RequestHandler::StartStream(const Request& request)
RequestResult RequestHandler::StopStream(const Request& request) RequestResult RequestHandler::StopStream(const Request& request)
{ {
if (!obs_frontend_streaming_active()) if (!obs_frontend_streaming_active())
return RequestResult::Error(RequestStatus::StreamNotRunning); return RequestResult::Error(RequestStatus::OutputNotRunning);
// TODO: Call signal directly to perform blocking wait // TODO: Call signal directly to perform blocking wait
obs_frontend_streaming_stop(); obs_frontend_streaming_stop();

View File

@ -42,7 +42,7 @@ const bool Request::ValidateNumber(const std::string keyName, RequestStatus::Req
return false; return false;
if (!RequestData[keyName].is_number()) { if (!RequestData[keyName].is_number()) {
statusCode = RequestStatus::InvalidRequestParameterDataType; statusCode = RequestStatus::InvalidRequestParameterType;
comment = std::string("The parameter `") + keyName + "` must be a number."; comment = std::string("The parameter `") + keyName + "` must be a number.";
return false; return false;
} }
@ -68,7 +68,7 @@ const bool Request::ValidateString(const std::string keyName, RequestStatus::Req
return false; return false;
if (!RequestData[keyName].is_string()) { if (!RequestData[keyName].is_string()) {
statusCode = RequestStatus::InvalidRequestParameterDataType; statusCode = RequestStatus::InvalidRequestParameterType;
comment = std::string("The parameter `") + keyName + "` must be a string."; comment = std::string("The parameter `") + keyName + "` must be a string.";
return false; return false;
} }
@ -88,7 +88,7 @@ const bool Request::ValidateBoolean(const std::string keyName, RequestStatus::Re
return false; return false;
if (!RequestData[keyName].is_boolean()) { if (!RequestData[keyName].is_boolean()) {
statusCode = RequestStatus::InvalidRequestParameterDataType; statusCode = RequestStatus::InvalidRequestParameterType;
comment = std::string("The parameter `") + keyName + "` must be boolean."; comment = std::string("The parameter `") + keyName + "` must be boolean.";
return false; return false;
} }
@ -102,7 +102,7 @@ const bool Request::ValidateObject(const std::string keyName, RequestStatus::Req
return false; return false;
if (!RequestData[keyName].is_object()) { if (!RequestData[keyName].is_object()) {
statusCode = RequestStatus::InvalidRequestParameterDataType; statusCode = RequestStatus::InvalidRequestParameterType;
comment = std::string("The parameter `") + keyName + "` must be an object."; comment = std::string("The parameter `") + keyName + "` must be an object.";
return false; return false;
} }
@ -122,7 +122,7 @@ const bool Request::ValidateArray(const std::string keyName, RequestStatus::Requ
return false; return false;
if (!RequestData[keyName].is_array()) { if (!RequestData[keyName].is_array()) {
statusCode = RequestStatus::InvalidRequestParameterDataType; statusCode = RequestStatus::InvalidRequestParameterType;
comment = std::string("The parameter `") + keyName + "` must be an array."; comment = std::string("The parameter `") + keyName + "` must be an array.";
return false; return false;
} }
@ -136,33 +136,47 @@ const bool Request::ValidateArray(const std::string keyName, RequestStatus::Requ
return true; return true;
} }
obs_source_t *Request::ValidateScene(const std::string keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const obs_source_t *Request::ValidateSource(const std::string keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const
{ {
if (!ValidateString(keyName, statusCode, comment)) if (!ValidateString(keyName, statusCode, comment))
return nullptr; return nullptr;
std::string sceneName = RequestData[keyName]; std::string sourceName = RequestData[keyName];
obs_source_t *ret = obs_get_source_by_name(sceneName.c_str()); obs_source_t *ret = obs_get_source_by_name(sourceName.c_str());
if (!ret) { if (!ret) {
statusCode = RequestStatus::SceneNotFound; statusCode = RequestStatus::ResourceNotFound;
comment = std::string("No scene was found by the name of `") + sceneName + "`."; comment = std::string("No source was found by the name of `") + sourceName + "`.";
return nullptr; return nullptr;
} }
return ret;
}
obs_source_t *Request::ValidateScene(const std::string keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter) const
{
obs_source_t *ret = ValidateSource(keyName, statusCode, comment);
if (!ret)
return nullptr;
if (obs_source_get_type(ret) != OBS_SOURCE_TYPE_SCENE) { if (obs_source_get_type(ret) != OBS_SOURCE_TYPE_SCENE) {
obs_source_release(ret); obs_source_release(ret);
statusCode = RequestStatus::InvalidSourceType; statusCode = RequestStatus::InvalidResourceType;
comment = "The specified source is not a scene."; comment = "The specified source is not a scene.";
return nullptr; return nullptr;
} }
OBSScene scene = obs_scene_from_source(ret); bool isGroup = obs_source_is_group(ret);
if (obs_scene_is_group(scene)) { if (filter == OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY && isGroup) {
obs_source_release(ret); obs_source_release(ret);
statusCode = RequestStatus::InvalidSourceType; statusCode = RequestStatus::InvalidResourceType;
comment = "The specified source is not a scene."; comment = "The specified source is not a scene.";
return nullptr; return nullptr;
} else if (filter == OBS_WEBSOCKET_SCENE_FILTER_GROUP_ONLY && !isGroup) {
obs_source_release(ret);
statusCode = RequestStatus::InvalidResourceType;
comment = "The specified source is not a group.";
return nullptr;
} }
return ret; return ret;
@ -170,24 +184,41 @@ obs_source_t *Request::ValidateScene(const std::string keyName, RequestStatus::R
obs_source_t *Request::ValidateInput(const std::string keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const obs_source_t *Request::ValidateInput(const std::string keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const
{ {
if (!ValidateString(keyName, statusCode, comment)) obs_source_t *ret = ValidateSource(keyName, statusCode, comment);
if (!ret)
return nullptr; return nullptr;
std::string inputName = RequestData[keyName];
obs_source_t *ret = obs_get_source_by_name(inputName.c_str());
if (!ret) {
statusCode = RequestStatus::InputNotFound;
comment = std::string("No input was found by the name of `") + inputName + "`.";
return nullptr;
}
if (obs_source_get_type(ret) != OBS_SOURCE_TYPE_INPUT) { if (obs_source_get_type(ret) != OBS_SOURCE_TYPE_INPUT) {
obs_source_release(ret); obs_source_release(ret);
statusCode = RequestStatus::InvalidSourceType; statusCode = RequestStatus::InvalidResourceType;
comment = "The specified source is not an input."; comment = "The specified source is not an input.";
return nullptr; return nullptr;
} }
return ret; return ret;
} }
obs_sceneitem_t *Request::ValidateSceneItem(const std::string sceneKeyName, const std::string sceneItemIdKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter) const
{
OBSSource sceneSource = ValidateScene(sceneKeyName, statusCode, comment, filter);
obs_source_release(sceneSource);
if (!sceneSource)
return nullptr;
if (!ValidateNumber(sceneItemIdKeyName, statusCode, comment, 0))
return nullptr;
OBSScene scene = obs_scene_from_source(sceneSource);
int64_t sceneItemId = RequestData[sceneItemIdKeyName];
OBSSceneItem sceneItem = obs_scene_find_sceneitem_by_id(scene, sceneItemId);
if (!sceneItem) {
statusCode = RequestStatus::ResourceNotFound;
comment = std::string("No scene items were found in scene `") + RequestData[sceneKeyName].get<std::string>() + "` with the ID `" + std::to_string(sceneItemId) + "`.";
return nullptr;
}
obs_sceneitem_addref(sceneItem);
return sceneItem;
}

View File

@ -4,6 +4,12 @@
#include "../../WebSocketSession.h" #include "../../WebSocketSession.h"
#include "../../utils/Json.h" #include "../../utils/Json.h"
enum ObsWebSocketSceneFilter {
OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY,
OBS_WEBSOCKET_SCENE_FILTER_GROUP_ONLY,
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP,
};
struct Request struct Request
{ {
Request(SessionPtr session, const std::string requestType, const json requestData = nullptr); Request(SessionPtr session, const std::string requestType, const json requestData = nullptr);
@ -20,8 +26,11 @@ struct Request
const bool ValidateObject(const std::string keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const; const bool ValidateObject(const std::string keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const;
const bool ValidateArray(const std::string keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const; const bool ValidateArray(const std::string keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const bool allowEmpty = false) const;
obs_source_t *ValidateScene(const std::string keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const; // All return values have incremented refcounts
obs_source_t *ValidateSource(const std::string keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const;
obs_source_t *ValidateScene(const std::string keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
obs_source_t *ValidateInput(const std::string keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const; obs_source_t *ValidateInput(const std::string keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const;
obs_sceneitem_t *ValidateSceneItem(const std::string sceneKeyName, const std::string sceneItemIdKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
SessionPtr Session; SessionPtr Session;
const uint8_t RpcVersion; const uint8_t RpcVersion;

View File

@ -11,9 +11,9 @@ namespace RequestStatus {
// The `requestType` field is missing from the request data // The `requestType` field is missing from the request data
MissingRequestType = 203, MissingRequestType = 203,
// The request type is invalid (does not exist) // The request type is invalid or does not exist
UnknownRequestType = 204, UnknownRequestType = 204,
// Generic error code (comment is expected to be provided) // Generic error code (comment required)
GenericError = 205, GenericError = 205,
// A required request parameter is missing // A required request parameter is missing
@ -21,10 +21,10 @@ namespace RequestStatus {
// The request does not have a valid requestData object. // The request does not have a valid requestData object.
MissingRequestData = 301, MissingRequestData = 301,
// Generic invalid request parameter message // Generic invalid request parameter message (comment required)
InvalidRequestParameter = 400, InvalidRequestParameter = 400,
// A request parameter has the wrong data type // A request parameter has the wrong data type
InvalidRequestParameterDataType = 401, InvalidRequestParameterType = 401,
// A request parameter (float or int) is out of valid range // A request parameter (float or int) is out of valid range
RequestParameterOutOfRange = 402, RequestParameterOutOfRange = 402,
// A request parameter (string or array) is empty and cannot be // A request parameter (string or array) is empty and cannot be
@ -36,101 +36,35 @@ namespace RequestStatus {
OutputRunning = 500, OutputRunning = 500,
// An output is not running and should be // An output is not running and should be
OutputNotRunning = 501, OutputNotRunning = 501,
// Stream is running and cannot be // An output is paused and should not be
StreamRunning = 502, OutputPaused = 502,
// Stream is not running and should be // An output is disabled and should not be
StreamNotRunning = 503, OutputDisabled = 503,
// Record is running and cannot be
RecordRunning = 504,
// Record is not running and should be
RecordNotRunning = 505,
// Record is paused and cannot be
RecordPaused = 506,
// Replay buffer is running and cannot be
ReplayBufferRunning = 507,
// Replay buffer is not running and should be
ReplayBufferNotRunning = 508,
// Replay buffer is disabled and cannot be
ReplayBufferDisabled = 509,
// Studio mode is active and cannot be // Studio mode is active and cannot be
StudioModeActive = 510, StudioModeActive = 504,
// Studio mode is not active and should be // Studio mode is not active and should be
StudioModeNotActive = 511, StudioModeNotActive = 505,
// Virtualcam is running and cannot be
VirtualcamRunning = 512,
// Virtualcam is not running and should be
VirtualcamNotRunning = 513,
// The specified source (obs_source_t) was of the invalid type (Eg. input instead of scene) // The resource was not found
InvalidSourceType = 600, ResourceNotFound = 600,
// The specified source (obs_source_t) was not found (generic for input, filter, transition, scene) // The resource already exists
SourceNotFound = 601, ResourceAlreadyExists = 601,
// The specified source (obs_source_t) already exists. Applicable to inputs, filters, transitions, scenes // The type of resource found is invalid
SourceAlreadyExists = 602, InvalidResourceType = 602,
// The specified input (obs_source_t-OBS_SOURCE_TYPE_FILTER) was not found // There are not enough instances of the resource in order to perform the request
InputNotFound = 603, NotEnoughResources = 603,
// The state of the resource is invalid. For example, if the resource is blocked from being accessed
InvalidResourceState = 604,
// The specified input (obs_source_t-OBS_SOURCE_TYPE_INPUT) had the wrong kind // The specified input (obs_source_t-OBS_SOURCE_TYPE_INPUT) had the wrong kind
InvalidInputKind = 604, InvalidInputKind = 605,
// The specified filter (obs_source_t-OBS_SOURCE_TYPE_FILTER) was not found
FilterNotFound = 605,
// The specified transition (obs_source_t-OBS_SOURCE_TYPE_TRANSITION) was not found
TransitionNotFound = 606,
// The specified transition (obs_source_t-OBS_SOURCE_TYPE_TRANSITION) does not support setting its position (transition is of fixed type)
TransitionDurationFixed = 607,
// The specified scene (obs_source_t-OBS_SOURCE_TYPE_SCENE), (obs_scene_t) was not found
SceneNotFound = 608,
// The specified scene item (obs_sceneitem_t) was not found
SceneItemNotFound = 609,
// The specified scene collection was not found
SceneCollectionNotFound = 610,
// The specified profile was not found
ProfileNotFound = 611,
// The specified output (obs_output_t) was not found
OutputNotFound = 612,
// The specified encoder (obs_encoder_t) was not found
EncoderNotFound = 613,
// The specified service (obs_service_t) was not found
ServiceNotFound = 614,
// The specified hotkey was not found
HotkeyNotFound = 615,
// The specified directory was not found
DirectoryNotFound = 616,
// The specified config item (config_t) was not found. Could be section or parameter name
ConfigParameterNotFound = 617,
// The specified property (obs_properties_t) was not found
PropertyNotFound = 618,
// The specififed key (OBS_KEY_*) was not found
KeyNotFound = 619,
// The specified data realm (OBS_WEBSOCKET_DATA_REALM_*) was not found
DataRealmNotFound = 620,
// The scene collection already exists
SceneCollectionAlreadyExists = 621,
// There are not enough scene collections to perform the action
NotEnoughSceneCollections = 622,
// The profile already exists
ProfileAlreadyExists = 623,
// There are not enough profiles to perform the action
NotEnoughProfiles = 624,
// There are not enough scenes to perform the action
NotEnoughScenes = 625,
// Processing the request failed unexpectedly // Creating the resource failed
RequestProcessingFailed = 700, ResourceCreationFailed = 700,
// Starting the Output failed // Performing an action on the resource failed
OutputStartFailed = 701, ResourceActionFailed = 701,
// Duplicating the scene item failed // Processing the request failed unexpectedly (comment required)
SceneItemDuplicationFailed = 702, RequestProcessingFailed = 702,
// Rendering the screenshot failed
ScreenshotRenderFailed = 703,
// Encoding the screenshot failed
ScreenshotEncodeFailed = 704,
// Saving the screenshot failed
ScreenshotSaveFailed = 705,
// Creating the directory failed
DirectoryCreationFailed = 706,
// The combination of request parameters cannot be used to perform an action // The combination of request parameters cannot be used to perform an action
CannotAct = 707, CannotAct = 703,
// Creation of a new stream service failed
StreamServiceCreationFailed = 708,
}; };
}; };

View File

@ -153,6 +153,24 @@ uint64_t Utils::Obs::NumberHelper::GetOutputDuration(obs_output_t *output)
return util_mul_div64(totalFrames, frameTimeNs, 1000000ULL); return util_mul_div64(totalFrames, frameTimeNs, 1000000ULL);
} }
size_t Utils::Obs::NumberHelper::GetSceneCount()
{
size_t ret;
auto sceneEnumProc = [](void *param, obs_source_t *scene) {
auto ret = reinterpret_cast<size_t*>(param);
if (obs_source_is_group(scene))
return true;
(*ret)++;
return true;
};
obs_enum_scenes(sceneEnumProc, &ret);
return ret;
}
std::vector<std::string> Utils::Obs::ListHelper::GetSceneCollectionList() std::vector<std::string> Utils::Obs::ListHelper::GetSceneCollectionList()
{ {
char** sceneCollections = obs_frontend_get_scene_collections(); char** sceneCollections = obs_frontend_get_scene_collections();
@ -217,42 +235,32 @@ std::vector<json> Utils::Obs::ListHelper::GetSceneList()
std::vector<json> Utils::Obs::ListHelper::GetSceneItemList(obs_scene_t *scene, bool basic) std::vector<json> Utils::Obs::ListHelper::GetSceneItemList(obs_scene_t *scene, bool basic)
{ {
std::vector<json> ret; std::pair<std::vector<json>, bool> enumData;
enumData.second = basic;
if (basic) { obs_scene_enum_items(scene, [](obs_scene_t* scene, obs_sceneitem_t* sceneItem, void* param) {
obs_scene_enum_items(scene, [](obs_scene_t* scene, obs_sceneitem_t* sceneItem, void* param) { auto enumData = reinterpret_cast<std::pair<std::vector<json>, bool>*>(param);
auto ret = reinterpret_cast<std::vector<json>*>(param);
json item;
item["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
// Should be slightly faster than calling obs_sceneitem_get_order_position()
item["sceneItemIndex"] = ret->size();
ret->push_back(item);
return true;
}, &ret);
} else {
obs_scene_enum_items(scene, [](obs_scene_t* scene, obs_sceneitem_t* sceneItem, void* param) {
auto ret = reinterpret_cast<std::vector<json>*>(param);
json item;
item["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
// Should be slightly faster than calling obs_sceneitem_get_order_position()
item["sceneItemIndex"] = enumData->first.size();
if (!enumData->second) {
OBSSource itemSource = obs_sceneitem_get_source(sceneItem); OBSSource itemSource = obs_sceneitem_get_source(sceneItem);
json item;
item["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
item["sceneItemIndex"] = ret->size();
item["sourceName"] = obs_source_get_name(itemSource); item["sourceName"] = obs_source_get_name(itemSource);
item["sourceType"] = StringHelper::GetSourceTypeString(itemSource); item["sourceType"] = StringHelper::GetSourceTypeString(itemSource);
if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_INPUT) if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_INPUT)
item["inputKind"] = obs_source_get_id(itemSource); item["inputKind"] = obs_source_get_id(itemSource);
else if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_SCENE)
item["isGroup"] = obs_source_is_group(itemSource);
}
ret->push_back(item); enumData->first.push_back(item);
return true; return true;
}, &ret); }, &enumData);
}
return ret; return enumData.first;
} }
std::vector<json> Utils::Obs::ListHelper::GetTransitionList() std::vector<json> Utils::Obs::ListHelper::GetTransitionList()
@ -369,7 +377,7 @@ obs_hotkey_t *Utils::Obs::SearchHelper::GetHotkeyByName(std::string name)
} }
struct CreateSceneItemData { struct CreateSceneItemData {
obs_source_t *input; obs_source_t *source;
bool sceneItemEnabled; bool sceneItemEnabled;
obs_sceneitem_t *sceneItem; obs_sceneitem_t *sceneItem;
}; };
@ -377,19 +385,19 @@ struct CreateSceneItemData {
void CreateSceneItemHelper(void *_data, obs_scene_t *scene) void CreateSceneItemHelper(void *_data, obs_scene_t *scene)
{ {
auto *data = reinterpret_cast<CreateSceneItemData*>(_data); auto *data = reinterpret_cast<CreateSceneItemData*>(_data);
data->sceneItem = obs_scene_add(scene, data->input); data->sceneItem = obs_scene_add(scene, data->source);
obs_sceneitem_set_visible(data->sceneItem, data->sceneItemEnabled); obs_sceneitem_set_visible(data->sceneItem, data->sceneItemEnabled);
} }
obs_sceneitem_t *Utils::Obs::ActionHelper::CreateSceneItem(obs_source_t *input, obs_scene_t *scene, bool sceneItemEnabled) obs_sceneitem_t *Utils::Obs::ActionHelper::CreateSceneItem(obs_source_t *source, obs_scene_t *scene, bool sceneItemEnabled)
{ {
// Sanity check for valid scene // Sanity check for valid scene
if (!(input && scene)) if (!(source && scene))
return nullptr; return nullptr;
// Create data struct and populate for scene item creation // Create data struct and populate for scene item creation
CreateSceneItemData data; CreateSceneItemData data;
data.input = input; data.source = source;
data.sceneItemEnabled = sceneItemEnabled; data.sceneItemEnabled = sceneItemEnabled;
// Enter graphics context and create the scene item // Enter graphics context and create the scene item

View File

@ -40,6 +40,7 @@ namespace Utils {
namespace NumberHelper { namespace NumberHelper {
uint64_t GetOutputDuration(obs_output_t *output); uint64_t GetOutputDuration(obs_output_t *output);
size_t GetSceneCount();
} }
namespace ListHelper { namespace ListHelper {
@ -63,7 +64,7 @@ namespace Utils {
} }
namespace ActionHelper { namespace ActionHelper {
obs_sceneitem_t *CreateSceneItem(obs_source_t *input, obs_scene_t *scene, bool sceneItemEnabled = true); obs_sceneitem_t *CreateSceneItem(obs_source_t *source, obs_scene_t *scene, bool sceneItemEnabled = true);
obs_sceneitem_t *CreateInput(std::string inputName, std::string inputKind, obs_data_t *inputSettings, obs_scene_t *scene, bool sceneItemEnabled = true); obs_sceneitem_t *CreateInput(std::string inputName, std::string inputKind, obs_data_t *inputSettings, obs_scene_t *scene, bool sceneItemEnabled = true);
} }
} }