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

2
.gitignore vendored
View File

@ -9,3 +9,5 @@
.idea
.vscode
/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)
# Version variables
project(obs-websocket VERSION 5.0.0)
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_REQUIRED ON)
# Prohibit in-source builds
file(TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" _LOC_PATH)
if(EXISTS "${LOC_PATH}")
@ -91,6 +104,7 @@ set(obs-websocket_SOURCES
src/requesthandler/RequestHandler_Sources.cpp
src/requesthandler/RequestHandler_Scenes.cpp
src/requesthandler/RequestHandler_Inputs.cpp
src/requesthandler/RequestHandler_SceneItems.cpp
src/requesthandler/RequestHandler_Stream.cpp
src/requesthandler/rpc/Request.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)
- (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.

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.
- 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:
- `Content-Type: application/json`
- `Content-Type: application/msgpack`
- If an invalid `Content-Type` is specified, the connection will be closed with [`WebSocketCloseCode::InvalidContentType`](#websocketclosecode-enum) after upgrade (but before `Hello`).
- 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:
- `obswebsocket.json` - JSON over text frames
- `obswebsocket.msgpack` - MsgPack over binary frames
- 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 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 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 any other parameters are malformed (invalid type, etc), the connection is dropped with [`WebSocketCloseCode::InvalidIdentifyParameter`](#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 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 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).
@ -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.
#### 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.
- 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.
- If a message with a `messageType` is not recognized to the obs-websocket server, the connection is dropped with [`WebSocketCloseCode::UnknownMessageType`](#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).
- 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 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 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 closed with [`WebSocketCloseCode::NotIdentified`](#websocketclosecode-enum).
---
@ -123,8 +122,6 @@ enum WebSocketCloseCode {
DontClose = 0,
// Reserved
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
MessageDecodeError = 4002,
// A data key is missing but required
@ -191,11 +188,11 @@ enum RequestStatus {
Success = 100,
// The `requestType` key is missing from the request data
// The `requestType` field is missing from the request data
MissingRequestType = 203,
// The request type is invalid (does not exist)
// The request type is invalid or does not exist
UnknownRequestType = 204,
// Generic error code (comment is expected to be provided)
// Generic error code (comment required)
GenericError = 205,
// A required request parameter is missing
@ -203,10 +200,10 @@ enum RequestStatus {
// The request does not have a valid requestData object.
MissingRequestData = 301,
// Generic invalid request parameter message
// Generic invalid request parameter message (comment required)
InvalidRequestParameter = 400,
// A request parameter has the wrong data type
InvalidRequestParameterDataType = 401,
InvalidRequestParameterType = 401,
// A request parameter (float or int) is out of valid range
RequestParameterOutOfRange = 402,
// A request parameter (string or array) is empty and cannot be
@ -218,102 +215,36 @@ enum RequestStatus {
OutputRunning = 500,
// An output is not running and should be
OutputNotRunning = 501,
// Stream is running and cannot be
StreamRunning = 502,
// Stream is not running and should be
StreamNotRunning = 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,
// An output is paused and should not be
OutputPaused = 502,
// An output is disabled and should not be
OutputDisabled = 503,
// Studio mode is active and cannot be
StudioModeActive = 510,
StudioModeActive = 504,
// Studio mode is not active and should be
StudioModeNotActive = 511,
// Virtualcam is running and cannot be
VirtualcamRunning = 512,
// Virtualcam is not running and should be
VirtualcamNotRunning = 513,
StudioModeNotActive = 505,
// The specified source (obs_source_t) was of the invalid type (Eg. input instead of scene)
InvalidSourceType = 600,
// The specified source (obs_source_t) was not found (generic for input, filter, transition, scene)
SourceNotFound = 601,
// The specified source (obs_source_t) already exists. Applicable to inputs, filters, transitions, scenes
SourceAlreadyExists = 602,
// The specified input (obs_source_t-OBS_SOURCE_TYPE_FILTER) was not found
InputNotFound = 603,
// The resource was not found
ResourceNotFound = 600,
// The resource already exists
ResourceAlreadyExists = 601,
// The type of resource found is invalid
InvalidResourceType = 602,
// There are not enough instances of the resource in order to perform the request
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
InvalidInputKind = 604,
// 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,
InvalidInputKind = 605,
// Processing the request failed unexpectedly
RequestProcessingFailed = 700,
// Starting the Output failed
OutputStartFailed = 701,
// Duplicating the scene item failed
SceneItemDuplicationFailed = 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,
// Creating the resource failed
ResourceCreationFailed = 700,
// Performing an action on the resource failed
ResourceActionFailed = 701,
// Processing the request failed unexpectedly (comment required)
RequestProcessingFailed = 702,
// The combination of request parameters cannot be used to perform an action
CannotAct = 707,
// Creation of a new stream service failed
StreamServiceCreationFailed = 708,
CannotAct = 703,
};
```
@ -393,7 +324,7 @@ Authentication is not required
}
```
- `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.
- `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,
"eventIntent": number,
"eventData": object(optional)
}
```
- `eventIntent` is the original intent required to be subscribed to in order to receive the event.
**Example Message:**
```json
@ -472,6 +405,7 @@ Authentication is not required
"op": 2,
"d": {
"eventType": "StudioModeStateChanged",
"eventIntent": 1,
"eventData": {
"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.
- 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:
- `Content-Type: application/json`
- `Content-Type: application/msgpack`
- If an invalid `Content-Type` is specified, the connection will be closed with [`WebSocketCloseCode::InvalidContentType`](#websocketclosecode-enum) after upgrade (but before `Hello`).
- 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:
- `obswebsocket.json` - JSON over text frames
- `obswebsocket.msgpack` - MsgPack over binary frames
- 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 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 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 any other parameters are malformed (invalid type, etc), the connection is dropped with [`WebSocketCloseCode::InvalidIdentifyParameter`](#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 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 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).
@ -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.
#### 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.
- 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.
- If a message with a `messageType` is not recognized to the obs-websocket server, the connection is dropped with [`WebSocketCloseCode::UnknownMessageType`](#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).
- 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 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 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 closed with [`WebSocketCloseCode::NotIdentified`](#websocketclosecode-enum).
---
@ -121,8 +120,6 @@ enum WebSocketCloseCode {
DontClose = 0,
// Reserved
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
MessageDecodeError = 4002,
// A data key is missing but required
@ -189,11 +186,11 @@ enum RequestStatus {
Success = 100,
// The `requestType` key is missing from the request data
// The `requestType` field is missing from the request data
MissingRequestType = 203,
// The request type is invalid (does not exist)
// The request type is invalid or does not exist
UnknownRequestType = 204,
// Generic error code (comment is expected to be provided)
// Generic error code (comment required)
GenericError = 205,
// A required request parameter is missing
@ -201,10 +198,10 @@ enum RequestStatus {
// The request does not have a valid requestData object.
MissingRequestData = 301,
// Generic invalid request parameter message
// Generic invalid request parameter message (comment required)
InvalidRequestParameter = 400,
// A request parameter has the wrong data type
InvalidRequestParameterDataType = 401,
InvalidRequestParameterType = 401,
// A request parameter (float or int) is out of valid range
RequestParameterOutOfRange = 402,
// A request parameter (string or array) is empty and cannot be
@ -216,102 +213,36 @@ enum RequestStatus {
OutputRunning = 500,
// An output is not running and should be
OutputNotRunning = 501,
// Stream is running and cannot be
StreamRunning = 502,
// Stream is not running and should be
StreamNotRunning = 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,
// An output is paused and should not be
OutputPaused = 502,
// An output is disabled and should not be
OutputDisabled = 503,
// Studio mode is active and cannot be
StudioModeActive = 510,
StudioModeActive = 504,
// Studio mode is not active and should be
StudioModeNotActive = 511,
// Virtualcam is running and cannot be
VirtualcamRunning = 512,
// Virtualcam is not running and should be
VirtualcamNotRunning = 513,
StudioModeNotActive = 505,
// The specified source (obs_source_t) was of the invalid type (Eg. input instead of scene)
InvalidSourceType = 600,
// The specified source (obs_source_t) was not found (generic for input, filter, transition, scene)
SourceNotFound = 601,
// The specified source (obs_source_t) already exists. Applicable to inputs, filters, transitions, scenes
SourceAlreadyExists = 602,
// The specified input (obs_source_t-OBS_SOURCE_TYPE_FILTER) was not found
InputNotFound = 603,
// The resource was not found
ResourceNotFound = 600,
// The resource already exists
ResourceAlreadyExists = 601,
// The type of resource found is invalid
InvalidResourceType = 602,
// There are not enough instances of the resource in order to perform the request
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
InvalidInputKind = 604,
// 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,
InvalidInputKind = 605,
// Processing the request failed unexpectedly
RequestProcessingFailed = 700,
// Starting the Output failed
OutputStartFailed = 701,
// Duplicating the scene item failed
SceneItemDuplicationFailed = 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,
// Creating the resource failed
ResourceCreationFailed = 700,
// Performing an action on the resource failed
ResourceActionFailed = 701,
// Processing the request failed unexpectedly (comment required)
RequestProcessingFailed = 702,
// The combination of request parameters cannot be used to perform an action
CannotAct = 707,
// Creation of a new stream service failed
StreamServiceCreationFailed = 708,
CannotAct = 703,
};
```
@ -391,7 +322,7 @@ Authentication is not required
}
```
- `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.
- `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,
"eventIntent": number,
"eventData": object(optional)
}
```
- `eventIntent` is the original intent required to be subscribed to in order to receive the event.
**Example Message:**
```json
@ -470,6 +403,7 @@ Authentication is not required
"op": 2,
"d": {
"eventType": "StudioModeStateChanged",
"eventIntent": 1,
"eventData": {
"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 MyAppVersion "@CMAKE_PROJECT_VERSION@"
#define MyAppVersion "@OBS_WEBSOCKET_VERSION@"
#define MyAppPublisher "Stephane Lepin"
#define MyAppURL "http://github.com/Palakis/obs-websocket"

View File

@ -26,6 +26,11 @@ WebSocketServer::WebSocketServer() :
_server.set_reuse_addr(true);
#endif
_server.set_validate_handler(
websocketpp::lib::bind(
&WebSocketServer::onValidate, this, websocketpp::lib::placeholders::_1
)
);
_server.set_open_handler(
websocketpp::lib::bind(
&WebSocketServer::onOpen, this, websocketpp::lib::placeholders::_1
@ -199,6 +204,7 @@ void WebSocketServer::BroadcastEvent(uint64_t requiredIntent, std::string eventT
json eventMessage;
eventMessage["op"] = 5;
eventMessage["d"]["eventType"] = eventType;
eventMessage["d"]["eventIntent"] = requiredIntent;
if (eventData.is_object())
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)
{
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->SetConnectedAt(QDateTime::currentSecsSinceEpoch());
session->SetAuthenticationRequired(AuthenticationRequired);
std::string contentType = conn->get_request_header("Content-Type");
if (contentType == "") {
;
} else if (contentType == "application/json") {
session->SetEncoding(WebSocketEncoding::Json);
} else if (contentType == "application/msgpack") {
session->SetEncoding(WebSocketEncoding::MsgPack);
} else {
conn->close(WebSocketCloseCode::InvalidContentType, "Your HTTP `Content-Type` header specifies an invalid encoding type.");
return;
std::string selectedSubprotocol = conn->get_subprotocol();
if (!selectedSubprotocol.empty()) {
if (selectedSubprotocol == "obswebsocket.json")
session->SetEncoding(WebSocketEncoding::Json);
else if (selectedSubprotocol == "obswebsocket.msgpack")
session->SetEncoding(WebSocketEncoding::MsgPack);
}
// Build `Hello`
@ -438,7 +455,7 @@ void WebSocketServer::onMessage(websocketpp::connection_hdl hdl, websocketpp::se
if (!incomingMessage.contains("op")) {
if (!session->IgnoreInvalidMessages()) {
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;
}
return;

View File

@ -46,8 +46,6 @@ class WebSocketServer : QObject
DontClose = 0,
// Reserved
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
MessageDecodeError = 4002,
// A data key is missing but required
@ -99,6 +97,7 @@ class WebSocketServer : QObject
private:
void ServerRunner();
bool onValidate(websocketpp::connection_hdl hdl);
void onOpen(websocketpp::connection_hdl hdl);
void onClose(websocketpp::connection_hdl hdl);
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, "audio_sync", HandleInputAudioSyncOffsetChanged, this);
signal_handler_connect(sh, "audio_mixers", HandleInputAudioTracksChanged, this);
//signal_handler_connect(sh, "audio_monitoring", HandleInputAudioMonitorTypeChanged, this);
if (sourceType == OBS_SOURCE_TYPE_INPUT) {
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, "audio_sync", HandleInputAudioSyncOffsetChanged, 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_ended", HandleMediaInputPlaybackEnded, 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 HandleInputAudioSyncOffsetChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputAudioTracksChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputAudioMonitorTypeChanged(void *param, calldata_t *data); // Direct callback
// Transitions
void HandleTransitionCreated(obs_source_t *source);

View File

@ -149,3 +149,36 @@ void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data)
eventData["inputAudioTracks"] = inputAudioTracks;
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 <QPainter>
#include <QUrl>
#include <obs-module.h>
#include "ConnectInfo.h"
@ -45,7 +46,7 @@ void ConnectInfo::showEvent(QShowEvent *event)
QString serverPassword;
if (conf->AuthRequired) {
ui->copyServerPasswordButton->setEnabled(true);
serverPassword = conf->ServerPassword;
serverPassword = QUrl::toPercentEncoding(conf->ServerPassword);
} else {
ui->copyServerPasswordButton->setEnabled(false);
serverPassword = obs_module_text("OBSWebSocket.ConnectInfo.ServerPasswordPlaceholderText");
@ -54,9 +55,9 @@ void ConnectInfo::showEvent(QShowEvent *event)
QString connectString;
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
connectString = QString("obswebsocket|%1:%2").arg(serverIp).arg(serverPort);
connectString = QString("obsws://%1:%2").arg(serverIp).arg(serverPort);
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 ___data_item_dummy_addref(obs_data_item_t*) {};
void ___data_item_release(obs_data_item_t* dataItem){ obs_data_item_release(&dataItem); };
void ___properties_dummy_addref(obs_properties_t*) {};
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 ___data_item_dummy_addref(obs_data_item_t*);
void ___data_item_release(obs_data_item_t*);
void ___properties_dummy_addref(obs_properties_t*);
using OBSSourceAutoRelease =
OBSRef<obs_source_t*, ___source_dummy_addref, obs_source_release>;
@ -31,6 +32,8 @@ using OBSOutputAutoRelease =
OBSRef<obs_output_t*, ___output_dummy_addref, obs_output_release>;
using OBSDataItemAutoRelease =
OBSRef<obs_data_item_t*, ___data_item_dummy_addref, ___data_item_release>;
using OBSPropertiesAutoDestroy =
OBSRef<obs_properties_t*, ___properties_dummy_addref, obs_properties_destroy>;
class Config;
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>
#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@

View File

@ -59,9 +59,22 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"ToggleInputMute", &RequestHandler::ToggleInputMute},
{"GetInputVolume", &RequestHandler::GetInputVolume},
{"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
{"GetStreamStatus", &RequestHandler::GetStreamStatus},
{"ToggleStream", &RequestHandler::ToggleStream},
{"StartStream", &RequestHandler::StartStream},
{"StopStream", &RequestHandler::StopStream},
};
@ -69,7 +82,7 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
RequestResult RequestHandler::ProcessRequest(const Request& request)
{
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())
return RequestResult::Error(RequestStatus::MissingRequestType, "Your request is missing a `requestType`");

View File

@ -8,6 +8,7 @@
#include "rpc/RequestResult.h"
#include "../obs-websocket.h"
#include "../utils/Obs.h"
#include "../plugin-macros.generated.h"
class RequestHandler;
typedef RequestResult(RequestHandler::*RequestMethodHandler)(const Request&);
@ -74,9 +75,22 @@ class RequestHandler {
RequestResult ToggleInputMute(const Request&);
RequestResult GetInputVolume(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
RequestResult GetStreamStatus(const Request&);
RequestResult ToggleStream(const Request&);
RequestResult StartStream(const Request&);
RequestResult StopStream(const Request&);

View File

@ -2,7 +2,6 @@
#include <util/config-file.h>
#include "RequestHandler.h"
#include "../plugin-macros.generated.h"
RequestResult RequestHandler::GetPersistentData(const Request& request)
{
@ -16,11 +15,11 @@ RequestResult RequestHandler::GetPersistentData(const Request& request)
std::string persistentDataPath = Utils::Obs::StringHelper::GetCurrentProfilePath();
if (realm == "OBS_WEBSOCKET_DATA_REALM_GLOBAL")
persistentDataPath += "../../../obsWebSocketPersistentData.json";
persistentDataPath += "/../../../obsWebSocketPersistentData.json";
else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE")
persistentDataPath += "/obsWebSocketPersistentData.json";
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 persistentData;
@ -45,11 +44,11 @@ RequestResult RequestHandler::SetPersistentData(const Request& request)
std::string persistentDataPath = Utils::Obs::StringHelper::GetCurrentProfilePath();
if (realm == "OBS_WEBSOCKET_DATA_REALM_GLOBAL")
persistentDataPath += "../../../obsWebSocketPersistentData.json";
persistentDataPath += "/../../../obsWebSocketPersistentData.json";
else if (realm == "OBS_WEBSOCKET_DATA_REALM_PROFILE")
persistentDataPath += "/obsWebSocketPersistentData.json";
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();
Utils::Json::GetJsonFileContent(persistentDataPath, persistentData);
@ -79,7 +78,7 @@ RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request)
auto sceneCollections = Utils::Obs::ListHelper::GetSceneCollectionList();
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();
// Avoid queueing tasks if nothing will change
@ -103,7 +102,7 @@ RequestResult RequestHandler::CreateSceneCollection(const Request& request)
auto sceneCollections = Utils::Obs::ListHelper::GetSceneCollectionList();
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());
bool success = false;
@ -133,7 +132,7 @@ RequestResult RequestHandler::SetCurrentProfile(const Request& request)
auto profiles = Utils::Obs::ListHelper::GetProfileList();
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();
// Avoid queueing tasks if nothing will change
@ -157,7 +156,7 @@ RequestResult RequestHandler::CreateProfile(const Request& request)
auto profiles = Utils::Obs::ListHelper::GetProfileList();
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());
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();
if (std::find(profiles.begin(), profiles.end(), profileName) == profiles.end())
return RequestResult::Error(RequestStatus::ProfileNotFound);
return RequestResult::Error(RequestStatus::ResourceNotFound);
if (profiles.size() < 2)
return RequestResult::Error(RequestStatus::NotEnoughProfiles);
return RequestResult::Error(RequestStatus::NotEnoughResources);
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window());
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
if (!request.RequestData.contains("parameterValue") || request.RequestData["parameterValue"].is_null()) {
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()) {
std::string parameterValue = request.RequestData["parameterValue"];
config_set_string(profile, parameterCategory.c_str(), parameterName.c_str(), parameterValue.c_str());
} 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();
@ -322,7 +321,7 @@ RequestResult RequestHandler::GetStreamServiceSettings(const Request& request)
RequestResult RequestHandler::SetStreamServiceSettings(const Request& request)
{
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;
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);
// TODO: Check service type here, instead of relying on service creation to fail.
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);
}

View File

@ -4,7 +4,6 @@
#include "../eventhandler/types/EventSubscription.h"
#include "../obs-websocket.h"
#include "../WebSocketServer.h"
#include "../plugin-macros.generated.h"
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"]);
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);

View File

@ -1,5 +1,4 @@
#include "RequestHandler.h"
#include "../plugin-macros.generated.h"
RequestResult RequestHandler::GetInputList(const Request& request)
{
@ -50,7 +49,7 @@ RequestResult RequestHandler::CreateInput(const Request& request)
std::string inputName = request.RequestData["inputName"];
OBSSourceAutoRelease existingInput = obs_get_source_by_name(inputName.c_str());
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"];
@ -99,7 +98,7 @@ RequestResult RequestHandler::SetInputName(const Request& request)
OBSSourceAutoRelease existingSource = obs_get_source_by_name(newInputName.c_str());
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());
@ -268,3 +267,141 @@ RequestResult RequestHandler::SetInputVolume(const Request& request)
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 "../plugin-macros.generated.h"
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());
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_release(createdScene);
@ -95,6 +94,9 @@ RequestResult RequestHandler::RemoveScene(const Request& request)
if (!scene)
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);
return RequestResult::Success();
@ -112,7 +114,7 @@ RequestResult RequestHandler::SetSceneName(const Request& request)
OBSSourceAutoRelease existingSource = obs_get_source_by_name(newSceneName.c_str());
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());

View File

@ -5,7 +5,6 @@
#include <QDir>
#include "RequestHandler.h"
#include "../plugin-macros.generated.h"
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());
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)
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;
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());
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)
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"];
@ -163,14 +162,14 @@ RequestResult RequestHandler::GetSourceScreenshot(const Request& request)
QImage renderedImage = TakeSourceScreenshot(source, success, requestedWidth, requestedHeight);
if (!success)
return RequestResult::Error(RequestStatus::ScreenshotRenderFailed);
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Failed to render screenshot.");
QByteArray encodedImgBytes;
QBuffer buffer(&encodedImgBytes);
buffer.open(QBuffer::WriteOnly);
if (!renderedImage.save(&buffer, imageFormat.c_str(), compressionQuality))
return RequestResult::Error(RequestStatus::ScreenshotEncodeFailed);
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Failed to encode screenshot.");
buffer.close();
@ -192,10 +191,10 @@ RequestResult RequestHandler::SaveSourceScreenshot(const Request& request)
OBSSourceAutoRelease source = obs_get_source_by_name(sourceName.c_str());
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)
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"];
@ -231,18 +230,18 @@ RequestResult RequestHandler::SaveSourceScreenshot(const Request& request)
QImage renderedImage = TakeSourceScreenshot(source, success, requestedWidth, requestedHeight);
if (!success)
return RequestResult::Error(RequestStatus::ScreenshotRenderFailed);
return RequestResult::Error(RequestStatus::RequestProcessingFailed, "Failed to render screenshot.");
std::string imageFilePath = request.RequestData["imageFilePath"];
QFileInfo filePathInfo(QString::fromStdString(imageFilePath));
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();
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();
}

View File

@ -1,5 +1,4 @@
#include "RequestHandler.h"
#include "../plugin-macros.generated.h"
RequestResult RequestHandler::GetStreamStatus(const Request& request)
{
@ -17,10 +16,24 @@ RequestResult RequestHandler::GetStreamStatus(const Request& request)
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)
{
if (obs_frontend_streaming_active())
return RequestResult::Error(RequestStatus::StreamRunning);
return RequestResult::Error(RequestStatus::OutputRunning);
// TODO: Call signal directly to perform blocking wait
obs_frontend_streaming_start();
@ -31,7 +44,7 @@ RequestResult RequestHandler::StartStream(const Request& request)
RequestResult RequestHandler::StopStream(const Request& request)
{
if (!obs_frontend_streaming_active())
return RequestResult::Error(RequestStatus::StreamNotRunning);
return RequestResult::Error(RequestStatus::OutputNotRunning);
// TODO: Call signal directly to perform blocking wait
obs_frontend_streaming_stop();

View File

@ -42,7 +42,7 @@ const bool Request::ValidateNumber(const std::string keyName, RequestStatus::Req
return false;
if (!RequestData[keyName].is_number()) {
statusCode = RequestStatus::InvalidRequestParameterDataType;
statusCode = RequestStatus::InvalidRequestParameterType;
comment = std::string("The parameter `") + keyName + "` must be a number.";
return false;
}
@ -68,7 +68,7 @@ const bool Request::ValidateString(const std::string keyName, RequestStatus::Req
return false;
if (!RequestData[keyName].is_string()) {
statusCode = RequestStatus::InvalidRequestParameterDataType;
statusCode = RequestStatus::InvalidRequestParameterType;
comment = std::string("The parameter `") + keyName + "` must be a string.";
return false;
}
@ -88,7 +88,7 @@ const bool Request::ValidateBoolean(const std::string keyName, RequestStatus::Re
return false;
if (!RequestData[keyName].is_boolean()) {
statusCode = RequestStatus::InvalidRequestParameterDataType;
statusCode = RequestStatus::InvalidRequestParameterType;
comment = std::string("The parameter `") + keyName + "` must be boolean.";
return false;
}
@ -102,7 +102,7 @@ const bool Request::ValidateObject(const std::string keyName, RequestStatus::Req
return false;
if (!RequestData[keyName].is_object()) {
statusCode = RequestStatus::InvalidRequestParameterDataType;
statusCode = RequestStatus::InvalidRequestParameterType;
comment = std::string("The parameter `") + keyName + "` must be an object.";
return false;
}
@ -122,7 +122,7 @@ const bool Request::ValidateArray(const std::string keyName, RequestStatus::Requ
return false;
if (!RequestData[keyName].is_array()) {
statusCode = RequestStatus::InvalidRequestParameterDataType;
statusCode = RequestStatus::InvalidRequestParameterType;
comment = std::string("The parameter `") + keyName + "` must be an array.";
return false;
}
@ -136,33 +136,47 @@ const bool Request::ValidateArray(const std::string keyName, RequestStatus::Requ
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))
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) {
statusCode = RequestStatus::SceneNotFound;
comment = std::string("No scene was found by the name of `") + sceneName + "`.";
statusCode = RequestStatus::ResourceNotFound;
comment = std::string("No source was found by the name of `") + sourceName + "`.";
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) {
obs_source_release(ret);
statusCode = RequestStatus::InvalidSourceType;
statusCode = RequestStatus::InvalidResourceType;
comment = "The specified source is not a scene.";
return nullptr;
}
OBSScene scene = obs_scene_from_source(ret);
if (obs_scene_is_group(scene)) {
bool isGroup = obs_source_is_group(ret);
if (filter == OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY && isGroup) {
obs_source_release(ret);
statusCode = RequestStatus::InvalidSourceType;
statusCode = RequestStatus::InvalidResourceType;
comment = "The specified source is not a scene.";
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;
@ -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
{
if (!ValidateString(keyName, statusCode, comment))
obs_source_t *ret = ValidateSource(keyName, statusCode, comment);
if (!ret)
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) {
obs_source_release(ret);
statusCode = RequestStatus::InvalidSourceType;
statusCode = RequestStatus::InvalidResourceType;
comment = "The specified source is not an input.";
return nullptr;
}
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 "../../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
{
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 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_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;
const uint8_t RpcVersion;

View File

@ -11,9 +11,9 @@ namespace RequestStatus {
// The `requestType` field is missing from the request data
MissingRequestType = 203,
// The request type is invalid (does not exist)
// The request type is invalid or does not exist
UnknownRequestType = 204,
// Generic error code (comment is expected to be provided)
// Generic error code (comment required)
GenericError = 205,
// A required request parameter is missing
@ -21,10 +21,10 @@ namespace RequestStatus {
// The request does not have a valid requestData object.
MissingRequestData = 301,
// Generic invalid request parameter message
// Generic invalid request parameter message (comment required)
InvalidRequestParameter = 400,
// A request parameter has the wrong data type
InvalidRequestParameterDataType = 401,
InvalidRequestParameterType = 401,
// A request parameter (float or int) is out of valid range
RequestParameterOutOfRange = 402,
// A request parameter (string or array) is empty and cannot be
@ -36,101 +36,35 @@ namespace RequestStatus {
OutputRunning = 500,
// An output is not running and should be
OutputNotRunning = 501,
// Stream is running and cannot be
StreamRunning = 502,
// Stream is not running and should be
StreamNotRunning = 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,
// An output is paused and should not be
OutputPaused = 502,
// An output is disabled and should not be
OutputDisabled = 503,
// Studio mode is active and cannot be
StudioModeActive = 510,
StudioModeActive = 504,
// Studio mode is not active and should be
StudioModeNotActive = 511,
// Virtualcam is running and cannot be
VirtualcamRunning = 512,
// Virtualcam is not running and should be
VirtualcamNotRunning = 513,
StudioModeNotActive = 505,
// The specified source (obs_source_t) was of the invalid type (Eg. input instead of scene)
InvalidSourceType = 600,
// The specified source (obs_source_t) was not found (generic for input, filter, transition, scene)
SourceNotFound = 601,
// The specified source (obs_source_t) already exists. Applicable to inputs, filters, transitions, scenes
SourceAlreadyExists = 602,
// The specified input (obs_source_t-OBS_SOURCE_TYPE_FILTER) was not found
InputNotFound = 603,
// The resource was not found
ResourceNotFound = 600,
// The resource already exists
ResourceAlreadyExists = 601,
// The type of resource found is invalid
InvalidResourceType = 602,
// There are not enough instances of the resource in order to perform the request
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
InvalidInputKind = 604,
// 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,
InvalidInputKind = 605,
// Processing the request failed unexpectedly
RequestProcessingFailed = 700,
// Starting the Output failed
OutputStartFailed = 701,
// Duplicating the scene item failed
SceneItemDuplicationFailed = 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,
// Creating the resource failed
ResourceCreationFailed = 700,
// Performing an action on the resource failed
ResourceActionFailed = 701,
// Processing the request failed unexpectedly (comment required)
RequestProcessingFailed = 702,
// The combination of request parameters cannot be used to perform an action
CannotAct = 707,
// Creation of a new stream service failed
StreamServiceCreationFailed = 708,
CannotAct = 703,
};
};

View File

@ -153,6 +153,24 @@ uint64_t Utils::Obs::NumberHelper::GetOutputDuration(obs_output_t *output)
return util_mul_div64(totalFrames, frameTimeNs, 1000000ULL);
}
size_t Utils::Obs::NumberHelper::GetSceneCount()
{
size_t ret;
auto sceneEnumProc = [](void *param, obs_source_t *scene) {
auto ret = reinterpret_cast<size_t*>(param);
if (obs_source_is_group(scene))
return true;
(*ret)++;
return true;
};
obs_enum_scenes(sceneEnumProc, &ret);
return ret;
}
std::vector<std::string> Utils::Obs::ListHelper::GetSceneCollectionList()
{
char** sceneCollections = obs_frontend_get_scene_collections();
@ -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> 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) {
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);
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);
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);
json item;
item["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
item["sceneItemIndex"] = ret->size();
item["sourceName"] = obs_source_get_name(itemSource);
item["sourceType"] = StringHelper::GetSourceTypeString(itemSource);
if (obs_source_get_type(itemSource) == OBS_SOURCE_TYPE_INPUT)
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;
}, &ret);
}
return true;
}, &enumData);
return ret;
return enumData.first;
}
std::vector<json> Utils::Obs::ListHelper::GetTransitionList()
@ -369,7 +377,7 @@ obs_hotkey_t *Utils::Obs::SearchHelper::GetHotkeyByName(std::string name)
}
struct CreateSceneItemData {
obs_source_t *input;
obs_source_t *source;
bool sceneItemEnabled;
obs_sceneitem_t *sceneItem;
};
@ -377,19 +385,19 @@ struct CreateSceneItemData {
void CreateSceneItemHelper(void *_data, obs_scene_t *scene)
{
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_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
if (!(input && scene))
if (!(source && scene))
return nullptr;
// Create data struct and populate for scene item creation
CreateSceneItemData data;
data.input = input;
data.source = source;
data.sceneItemEnabled = sceneItemEnabled;
// Enter graphics context and create the scene item

View File

@ -40,6 +40,7 @@ namespace Utils {
namespace NumberHelper {
uint64_t GetOutputDuration(obs_output_t *output);
size_t GetSceneCount();
}
namespace ListHelper {
@ -63,7 +64,7 @@ namespace Utils {
}
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);
}
}