Compare commits

..

50 Commits

Author SHA1 Message Date
3362d3f998 ci(macos): bump Packages version 2022-02-13 23:51:39 +01:00
7ca8140a34 ci(macos): use a common password for keychain import steps 2022-02-13 23:47:32 +01:00
eeb7bac4b7 ci(macos): import installer certificate in existing keychain 2022-02-01 10:19:36 +01:00
7113055218 ci(macos): configure productsign with installer certificate 2022-02-01 10:12:42 +01:00
1844f85e1f CI: Add MacOS CI
Add MacOS CI for `master`

Co-authored-by: tt2468 <tt2468@gmail.com>
2022-01-18 19:26:42 -08:00
bc0b499944 docs(ci): Update generated docs - ae906bb [skip ci] 2022-01-19 03:24:00 +00:00
ae906bb283 RequestHandler: Add VirtualCam requests 2022-01-18 19:23:06 -08:00
63dfed1cf9 docs(ci): Update generated docs - 873eade [skip ci] 2022-01-09 06:14:13 +00:00
873eadec05 requesthandler: Fix documentation of dB value input
Max dB value is 26dB, not -26dB.
2022-01-08 22:13:53 -08:00
dea0fcd561 Base: Add logging for compile time ASIO version 2022-01-07 23:00:48 -08:00
9f7beb1c0d deps: Downgrade asio to 1.12.1
Even though we statically link ASIO, it has issues with the
kqueue_reactor() on macos segfaulting when other plugins using
different ASIO versions are installed. So that means we're stuck using
1.12.1 until we can find some kind of fix for the crash issue.
2022-01-04 00:42:09 -08:00
db9f4b24df docs(ci): Update generated docs - 6035294 [skip ci] 2022-01-03 21:54:54 +00:00
6035294339 requesthandler: Add GetSourceFilter 2022-01-03 13:54:27 -08:00
6a2d5968ad requesthandler: Add private source settings get/set requests
It was requested via Discord to be able to modify the private settings
of any private source, since that functionality is used by some client
software to store stateful data. As private settings are in territory
that no normal user should ever tread into, these requests will be left
undocumented.
2022-01-01 17:43:26 -08:00
8f2d266dec docs(ci): Update generated docs - fe64620 [skip ci] 2022-01-01 02:06:35 +00:00
fe64620731 requesthandler: Add scene item blend mode requests 2021-12-31 18:05:05 -08:00
24e43d0276 requesthandler: Add GetSpecialInputs 2021-12-31 16:49:18 -08:00
c0308d6ce1 docs(ci): Update generated docs - 506a916 [skip ci] 2021-12-31 23:27:15 +00:00
506a9167c3 requesthandler: Add SetInputAudioTracks 2021-12-31 15:26:54 -08:00
043444cad5 workflows: Enable plugin tests on nightly linux builds 2021-12-31 15:26:54 -08:00
35c8a87def requesthandler: Profile requests if PLUGIN_TESTS is enabled 2021-12-31 15:26:54 -08:00
e451a8d6b0 requesthandler: Use unordered_map for request table
Shaves like 0.0005ms off of request time, but still worth noting.
2021-12-31 15:26:54 -08:00
02bcc0ac1b docs(ci): Update generated docs - 702f88c [skip ci] 2021-12-31 22:08:43 +00:00
702f88cea8 requesthandler: Add GetInputAudioTracks 2021-12-31 14:08:22 -08:00
6d216e0412 docs: Fix docs of InputAudioTracksChanged 2021-12-31 14:08:22 -08:00
e6761cf286 docs(ci): Update generated docs - 00dd8d7 [skip ci] 2021-12-30 09:19:59 +00:00
00dd8d7821 lib: Add version define 2021-12-30 01:15:54 -08:00
e43ebde794 Base: Use static_cast in place of reinterpret_cast
static_cast is a much safer cast method
2021-12-30 00:21:29 -08:00
4a2654d095 RequestHandler: Add GetGroupList 2021-12-30 00:12:41 -08:00
6291cb1532 docs(ci): Update generated docs - a90dafb [skip ci] 2021-12-30 05:12:42 +00:00
a90dafb971 Merge pull request #885 from obsproject/feature/input-audio-requests-events
Input audio requests and events
2021-12-29 21:12:23 -08:00
3a96b585ce docs(ci): Update generated docs - 12c6527 [skip ci] 2021-12-30 05:09:00 +00:00
12c6527442 Merge pull request #884 from obsproject/feature/ui-dialog-requests
RequestHandler: Add input open dialog requests
2021-12-29 21:08:41 -08:00
31997db509 EventHandler: Uncomment audio_monitoring signal 2021-12-29 21:05:28 -08:00
85f65952bd Base: Update issue template 2021-12-29 21:04:54 -08:00
9113ff9021 RequestHandler: Add audio balance requests 2021-12-29 21:03:16 -08:00
1ed095de48 EventHandler: Add InputAudioBalanceChanged 2021-12-29 21:03:16 -08:00
a94ac24027 RequestHandler: Add input open dialog requests
Adds
- `OpenInputPropertiesDialog`
- `OpenInputFiltersDialog`
- `OpenInputInteractDialog`
2021-12-29 21:00:11 -08:00
903b7d4171 Merge pull request #860 from obsproject/fix/audio_monitoring_check
Requests: Add support check for monitoring in `SetInputAudioMonitoringType`
2021-12-29 20:59:38 -08:00
a59ce69ba1 Merge pull request #857 from obsproject/fix/remove-old-ifdefs
Base: Remove old ifdefs
2021-12-29 20:54:24 -08:00
195c4a3ca9 Merge pull request #873 from obsproject/fix/inputvolumemeters-check
ObsVolumeMeter: Reenable check for valid input
2021-12-29 20:54:00 -08:00
3b2369ae97 Requests: Add support check for SetInputAudioMonitorType 2021-12-29 20:50:27 -08:00
af634b63fd Merge pull request #854 from obsproject/request/removeinput
Requests: Enable RemoveInput
2021-12-29 20:49:09 -08:00
444685c89d Utils: Reenable check for valid input in volumemeter 2021-12-29 20:45:20 -08:00
05aba45809 Base: Remove old ifdefs
It was a very cool method to save our precious std::strtoll method,
but will no longer be needed on the next OBS release.
2021-12-29 20:40:49 -08:00
1f1a8926b1 Requests: Enable RemoveInput 2021-12-29 20:40:14 -08:00
0c5d4ba3fb Merge pull request #852 from obsproject/event/changing_events
Events: Reenable *Changing events (second time)
2021-12-29 20:39:33 -08:00
947450ce4e Revert "Revert "Events: Re-enable *Changing events""
This reverts commit c60d09246c.
2021-12-29 20:29:45 -08:00
63cc1f316d Merge pull request #850 from obsproject/request/getrecorddirectory
Requests: Enable GetRecordDirectory
2021-12-29 20:28:44 -08:00
38157579a6 Requests: Enable GetRecordDirectory 2021-12-29 19:53:10 -08:00
45 changed files with 2348 additions and 810 deletions

View File

@ -1,11 +1,11 @@
name: "CI Multiplatform Build"
name: 'CI Multiplatform Build'
on:
push:
paths-ignore:
- 'docs/**'
branches:
- master
- '*'
tags:
- '[45].[0-9]+.[0-9]+*'
pull_request:
@ -25,8 +25,8 @@ jobs:
QT_VERSION: '5.15.2'
WINDOWS_DEPS_CACHE_VERSION: '1' # Change whenever updating Qt dependency URL, in order to force a cache reset
WINDOWS_DEPS_VERSION: '2019'
CMAKE_GENERATOR: "Visual Studio 16 2019"
CMAKE_SYSTEM_VERSION: "10.0"
CMAKE_GENERATOR: 'Visual Studio 16 2019'
CMAKE_SYSTEM_VERSION: '10.0'
steps:
- name: 'Add msbuild to PATH'
uses: microsoft/setup-msbuild@v1.0.2
@ -35,20 +35,20 @@ jobs:
with:
path: ${{ github.workspace }}/obs-websocket
submodules: 'recursive'
- name: 'Checkout OBS-Studio'
- name: 'Checkout OBS Studio'
uses: actions/checkout@v2
with:
repository: obsproject/obs-studio
path: ${{ github.workspace }}/obs-studio
submodules: 'recursive'
- name: 'Get OBS-Studio Git Info'
- name: 'Get OBS Studio Git Info'
shell: bash
working-directory: ${{ github.workspace }}/obs-studio
run: |
git fetch --prune --unshallow
echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
- name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})'
- name: 'Checkout last OBS Studio release (${{ env.OBS_GIT_TAG }})'
shell: bash
working-directory: ${{ github.workspace }}/obs-studio
run: |
@ -78,8 +78,6 @@ jobs:
with:
path: Qt_${{ env.QT_VERSION }}.7z
key: 'qtdep-${{ env.QT_CACHE_VERSION }} | ${{ runner.os }}'
restore-keys: |
qtdep-${{ env.QT_CACHE_VERSION }} | ${{ runner.os }}
- name: 'Download Prerequisite: Qt'
if: steps.qtcache.outputs.cache-hit != 'true'
run: |
@ -87,20 +85,18 @@ jobs:
- name: 'Extract Prerequisite: Qt'
run: |
7z x Qt_${{ env.QT_VERSION }}.7z -o"${{ github.workspace }}\cmbuild\QT"
- name: 'Restore Cached OBS-Studio Dependencies'
- name: 'Restore Cached OBS Studio Dependencies'
id: obscache
uses: actions/cache@v2
with:
path: ${{ github.workspace }}\cmbuild\deps\**
key: 'obsdep-${{ env.WINDOWS_DEPS_CACHE_VERSION }} | ${{ runner.os }}'
restore-keys: |
obsdep-${{ env.WINDOWS_DEPS_CACHE_VERSION }} | ${{ runner.os }}
- name: 'Install Prerequisite: Pre-built OBS-Studio dependencies'
- name: 'Install Prerequisite: Pre-built OBS Studio dependencies'
if: steps.obscache.outputs.cache-hit != 'true'
run: |
curl -kLO https://cdn-fastly.obsproject.com/downloads/dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -f --retry 5 -C -
7z x dependencies${{ env.WINDOWS_DEPS_VERSION }}.zip -o"${{ github.workspace }}\cmbuild\deps"
- name: 'Restore OBS-Studio 32-bit Build v${{ env.OBS_GIT_TAG }} from Cache'
- name: 'Restore OBS Studio 32-bit Build v${{ env.OBS_GIT_TAG }} from Cache'
id: build-cache-obs-32
uses: actions/cache@v2
env:
@ -108,22 +104,20 @@ jobs:
with:
path: ${{ github.workspace }}/obs-studio/build32
key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }}
restore-keys: |
${{ runner.os }}-${{ env.CACHE_NAME }}-
- name: 'Configure OBS-Studio 32-bit'
- name: 'Configure OBS Studio 32-bit'
if: steps.build-cache-obs-32.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio
run: |
if(!(Test-Path -Path ".\build32")){New-Item -ItemType directory -Path .\build32}
cd .\build32
cmake -G "${{ env.CMAKE_GENERATOR }}" -A Win32 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win32" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES -DBUILD_BROWSER=OFF ..
- name: 'Build OBS-Studio 32-bit'
- name: 'Build OBS Studio 32-bit'
if: steps.build-cache-obs-32.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio
run: |
msbuild /m /p:Configuration=RelWithDebInfo .\build32\libobs\libobs.vcxproj
msbuild /m /p:Configuration=RelWithDebInfo .\build32\UI\obs-frontend-api\obs-frontend-api.vcxproj
- name: 'Restore OBS-Studio 64-bit Build v${{ env.OBS_GIT_TAG }} from Cache'
- name: 'Restore OBS Studio 64-bit Build v${{ env.OBS_GIT_TAG }} from Cache'
id: build-cache-obs-64
uses: actions/cache@v1
env:
@ -131,16 +125,14 @@ jobs:
with:
path: ${{ github.workspace }}/obs-studio/build64
key: ${{ runner.os }}-${{ env.CACHE_NAME }}-${{ env.OBS_GIT_TAG }}
restore-keys: |
${{ runner.os }}-${{ env.CACHE_NAME }}-
- name: 'Configure OBS-Studio 64-bit'
- name: 'Configure OBS Studio 64-bit'
if: steps.build-cache-obs-64.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio
run: |
if(!(Test-Path -Path ".\build64")){New-Item -ItemType directory -Path .\build64}
cd .\build64
cmake -G "${{ env.CMAKE_GENERATOR }}" -A x64 -DCMAKE_SYSTEM_VERSION="${{ env.CMAKE_SYSTEM_VERSION }}" -DQTDIR="${{ github.workspace }}\cmbuild\QT\${{ env.QT_VERSION }}\msvc2019_64" -DDepsPath="${{ github.workspace }}\cmbuild\deps\win64" -DCOPIED_DEPENDENCIES=NO -DCOPY_DEPENDENCIES=YES -DBUILD_BROWSER=OFF ..
- name: 'Build OBS-Studio 64-bit'
- name: 'Build OBS Studio 64-bit'
if: steps.build-cache-obs-64.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio
run: |
@ -188,7 +180,7 @@ jobs:
name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-Windows-Installer'
path: ${{ github.workspace }}/obs-websocket/package/*.exe
ubuntu64:
name: "Linux/Ubuntu 64-bit"
name: 'Linux/Ubuntu 64-bit'
runs-on: [ubuntu-latest]
if: contains(github.event.head_commit.message, '[skip ci]') != true
steps:
@ -197,20 +189,20 @@ jobs:
with:
path: ${{ github.workspace }}/obs-websocket
submodules: 'recursive'
- name: 'Checkout OBS-Studio'
- name: 'Checkout OBS Studio'
uses: actions/checkout@v2
with:
repository: obsproject/obs-studio
path: ${{ github.workspace }}/obs-studio
submodules: 'recursive'
- name: 'Get OBS-Studio Git Info'
- name: 'Get OBS Studio Git Info'
shell: bash
working-directory: ${{ github.workspace }}/obs-studio
run: |
git fetch --prune --unshallow
echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
- name: 'Checkout last OBS-Studio release (${{ env.OBS_GIT_TAG }})'
- name: 'Checkout last OBS Studio release (${{ env.OBS_GIT_TAG }})'
shell: bash
working-directory: ${{ github.workspace }}/obs-studio
run: |
@ -284,21 +276,21 @@ jobs:
libx11-xcb-dev \
libxcb1-dev \
libxss-dev \
- name: 'Configure OBS-Studio'
- name: 'Configure OBS Studio'
working-directory: ${{ github.workspace }}/obs-studio
shell: bash
run: |
mkdir ./build
cd ./build
cmake -DDISABLE_PLUGINS=YES -DENABLE_SCRIPTING=NO -DUNIX_STRUCTURE=YES -DCMAKE_INSTALL_PREFIX=/usr ..
- name: 'Build OBS-Studio'
- name: 'Build OBS Studio'
working-directory: ${{ github.workspace }}/obs-studio
shell: bash
run: |
set -e
cd ./build
make -j4 libobs obs-frontend-api
- name: 'Install OBS-Studio'
- name: 'Install OBS Studio'
working-directory: ${{ github.workspace }}/obs-studio
shell: bash
run: |
@ -316,7 +308,7 @@ jobs:
if [ "${{ env.GIT_TAG }}" ] ; then \
cmake -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=TRUE -DOBS_WEBSOCKET_VERSION_SUFFIX="${{ env.CMAKE_VERSION_SUFFIX }}" -DCMAKE_BUILD_TYPE=Release .. ; \
else \
cmake -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=TRUE -DOBS_WEBSOCKET_VERSION_SUFFIX="${{ env.CMAKE_VERSION_SUFFIX }}" .. ; \
cmake -DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs -DCMAKE_INSTALL_PREFIX=/usr -DUSE_UBUNTU_FIX=TRUE -DOBS_WEBSOCKET_VERSION_SUFFIX="${{ env.CMAKE_VERSION_SUFFIX }}" -DPLUGIN_TESTS=TRUE .. ; \
fi
- name: 'Build obs-websocket'
working-directory: ${{ github.workspace }}/obs-websocket
@ -355,3 +347,210 @@ jobs:
with:
name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-Ubuntu64'
path: '${{ github.workspace }}/obs-websocket/package/*.deb'
macOS:
name: 'macOS 64-bit'
runs-on: [macos-latest]
if: contains(github.event.head_commit.message, '[skip ci]') != true
env:
MACOS_DEPS_VERSION: '2022-01-01'
MACOS_DEPS_CACHE_VERSION: '2' # Change whenever updating dependencies version, in order to force a cache reset
steps:
- name: 'Checkout obs-websocket'
uses: actions/checkout@v2
with:
path: ${{ github.workspace }}/obs-websocket
submodules: 'recursive'
- name: 'Checkout OBS Studio'
uses: actions/checkout@v2
with:
repository: obsproject/obs-studio
path: ${{ github.workspace }}/obs-studio
submodules: 'recursive'
- name: 'Install Prerequisite: Binary Signing Certificate'
uses: apple-actions/import-codesign-certs@v1
with:
p12-file-base64: ${{ secrets.MACOS_SIGNING_CERT }}
p12-password: ${{ secrets.MACOS_SIGNING_CERT_PASSWORD }}
create-keychain: true
keychain-password: ${{ secrets.MACOS_TEMP_CI_KEYCHAIN_PASSWORD }}
- name: 'Install Prerequisite: Installer Signing Certificate'
uses: apple-actions/import-codesign-certs@v1
with:
p12-file-base64: ${{ secrets.MACOS_INSTALLER_CERT }}
p12-password: ${{ secrets.MACOS_INSTALLER_CERT_PASSWORD }}
create-keychain: false
keychain-password: ${{ secrets.MACOS_TEMP_CI_KEYCHAIN_PASSWORD }}
- name: 'Get OBS Studio Git Info'
shell: bash
working-directory: ${{ github.workspace }}/obs-studio
run: |
git fetch --prune --unshallow
echo "OBS_GIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
echo "OBS_GIT_TAG=$(git describe --tags --abbrev=0)" >> $GITHUB_ENV
- name: 'Checkout last OBS Studio release (${{ env.OBS_GIT_TAG }})'
shell: bash
working-directory: ${{ github.workspace }}/obs-studio
run: |
git checkout ${{ env.OBS_GIT_TAG }}
git submodule update
- name: 'Get obs-websocket git info'
shell: bash
working-directory: ${{ github.workspace }}/obs-websocket
run: |
git fetch --prune --unshallow
GIT_HASH=$(git rev-parse --short HEAD)
echo "GIT_HASH=$GIT_HASH" >> $GITHUB_ENV
GIT_TAG=$(git describe --exact-match --tags --abbrev=0) || GIT_TAG=""
echo "GIT_TAG=$GIT_TAG" >> $GITHUB_ENV
if [ "$GIT_TAG" ] ; then \
VERSION="$GIT_TAG" \
VERSION_SUFFIX=$(echo "$GIT_TAG" | cut -c6-20) ; \
else \
VERSION="$GIT_HASH-git" \
VERSION_SUFFIX="-$GIT_HASH-git" ; \
fi
echo "PACKAGE_VERSION=$VERSION" >> $GITHUB_ENV
echo "CMAKE_VERSION_SUFFIX=$VERSION_SUFFIX" >> $GITHUB_ENV
- name: 'Install Packages'
shell: bash
run: |
curl -L -O http://s.sudre.free.fr/Software/files/Packages.dmg
sudo hdiutil attach ./Packages.dmg
sudo installer -pkg /Volumes/Packages\ 1.2.10/Install\ Packages.pkg -target /
- name: 'Restore Cached Qt & OBS Studio dependencies'
id: deps-cache
uses: actions/cache@v2
with:
path: ${{ github.workspace }}/obsdeps/**
key: 'deps-cache-${{ env.MACOS_DEPS_CACHE_VERSION }} | ${{ runner.os }}'
- name: 'Install Prerequisite: Qt + OBS Studio dependencies'
if: steps.deps-cache.outputs.cache-hit != 'true'
shell: bash
run: |
mkdir -p obsdeps
curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.MACOS_DEPS_VERSION }}/macos-deps-qt-${{ env.MACOS_DEPS_VERSION }}-universal.tar.xz
tar -xf macos-deps-qt-${{ env.MACOS_DEPS_VERSION }}-universal.tar.xz -C "./obsdeps"
curl -L -O https://github.com/obsproject/obs-deps/releases/download/${{ env.MACOS_DEPS_VERSION }}/macos-deps-${{ env.MACOS_DEPS_VERSION }}-universal.tar.xz
tar -xf macos-deps-${{ env.MACOS_DEPS_VERSION }}-universal.tar.xz -C "./obsdeps"
- run: xattr -r -d com.apple.quarantine ./obsdeps
shell: bash
- name: 'Configue OBS Studio'
if: steps.cache-obs-build.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio
shell: bash
run: |
mkdir -p ./build
cd ./build
cmake .. \
-DQTDIR=${{ github.workspace }}/obsdeps \
-DDepsPath=${{ github.workspace }}/obsdeps \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 \
-DDISABLE_PLUGINS=true \
-DENABLE_SCRIPTING=0 \
-DCMAKE_PREFIX_PATH=${{ github.workspace }}/obsdeps/lib/cmake
- name: 'Build OBS Studio'
if: steps.cache-obs-build.outputs.cache-hit != 'true'
working-directory: ${{ github.workspace }}/obs-studio/build
shell: bash
run: |
set -e
make -j4 libobs obs-frontend-api
- name: 'Configure obs-websocket'
working-directory: ${{ github.workspace }}/obs-websocket
shell: bash
run: |
mkdir -p build
cd build
cmake .. \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 \
-DQTDIR=${{ github.workspace }}/obsdeps \
-DLIBOBS_INCLUDE_DIR=${{ github.workspace }}/obs-studio/libobs \
-DLIBOBS_LIB=${{ github.workspace }}/obs-studio/libobs \
-DOBS_FRONTEND_LIB="${{ github.workspace }}/obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=/usr
- name: 'Build obs-websocket'
working-directory: ${{ github.workspace }}/obs-websocket/build
shell: bash
run: |
set -e
make -j4
- name: 'Relink Qt'
shell: bash
working-directory: ${{ github.workspace }}/obs-websocket/build
run: |
install_name_tool \
-change /tmp/obsdeps/lib/QtWidgets.framework/Versions/5/QtWidgets \
@executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets \
-change /tmp/obsdeps/lib/QtGui.framework/Versions/5/QtGui \
@executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui \
-change /tmp/obsdeps/lib/QtCore.framework/Versions/5/QtCore \
@executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore \
-change /tmp/obsdeps/lib/QtNetwork.framework/Versions/5/QtNetwork \
@executable_path/../Frameworks/QtNetwork.framework/Versions/5/QtNetwork \
-change /tmp/obsdeps/lib/QtSvg.framework/Versions/5/QtSvg \
@executable_path/../Frameworks/QtSvg.framework/Versions/5/QtSvg \
./obs-websocket.so
- name: 'Sign plugin binary'
shell: bash
working-directory: ${{ github.workspace }}/obs-websocket/build
run: |
codesign --sign "${{ secrets.MACOS_SIGNING_IDENTITY }}" ./obs-websocket.so
- name: 'Set PR Artifact Filename'
shell: bash
run: |
echo "MACOS_FILENAME=obs-websocket-${{ env.PACKAGE_VERSION }}-macOS.pkg" >> $GITHUB_ENV
echo "MACOS_FILENAME_UNSIGNED=obs-websocket-${{ env.PACKAGE_VERSION }}-macOS-Unsigned.pkg" >> $GITHUB_ENV
- name: 'Package ${{ env.MACOS_FILENAME_UNSIGNED }}'
if: success()
working-directory: ${{ github.workspace }}/obs-websocket
shell: bash
run: |
packagesbuild ./CI/macos/obs-websocket.pkgproj
mv ./release/obs-websocket.pkg ./release/${{ env.MACOS_FILENAME_UNSIGNED }}
- name: 'Sign plugin package'
if: ${{ env.GIT_TAG != '' }}
shell: bash
working-directory: ${{ github.workspace }}/obs-websocket
run: |
productsign \
--sign "${{ secrets.MACOS_INSTALLER_IDENTITY }}" \
./release/${{ env.MACOS_FILENAME_UNSIGNED }} \
./release/${{ env.MACOS_FILENAME }}
rm ./release/${{ env.MACOS_FILENAME_UNSIGNED }}
- name: 'Notarize package'
if: ${{ env.GIT_TAG != '' }}
shell: bash
working-directory: ${{ github.workspace }}/obs-websocket
run: |
zip -r ./release/${{ env.MACOS_FILENAME }}.zip ./release/${{ env.MACOS_FILENAME }}
UPLOAD_RESULT=$(xcrun altool --notarize-app \
--primary-bundle-id "com.obsproject.obs-websocket" \
--username "${{ secrets.MACOS_NOTARIZATION_USERNAME }}" \
--password "${{ secrets.MACOS_NOTARIZATION_PASSWORD }}" \
--asc-provider "${{ secrets.ASC_PROVIDER_SHORTNAME }}" \
--file "./release/${{ env.MACOS_FILENAME }}.zip")
rm ./release/${{ env.MACOS_FILENAME }}.zip
REQUEST_UUID=$(echo $UPLOAD_RESULT | awk -F ' = ' '/RequestUUID/ {print $2}')
# Pieces of code borrowed from rednoah/notarized-app
while sleep 30 && date; do
CHECK_RESULT=$(xcrun altool \
--notarization-info "$REQUEST_UUID" \
--username "${{ secrets.MACOS_NOTARIZATION_USERNAME }}" \
--password "${{ secrets.MACOS_NOTARIZATION_PASSWORD }}" \
--asc-provider "${{ secrets.ASC_PROVIDER_SHORTNAME }}")
if ! grep -q "Status: in progress" <<< "$CHECK_RESULT"; then
xcrun stapler staple ./release/${{ env.MACOS_FILENAME }}
break
fi
done
- name: 'Publish Packages'
if: success()
uses: actions/upload-artifact@v2-preview
with:
name: 'obs-websocket-${{ env.PACKAGE_VERSION }}-macOS'
path: '${{ github.workspace }}/obs-websocket/release/*.pkg'

View File

@ -1,5 +0,0 @@
brew "jack"
brew "speexdsp"
brew "cmake"
brew "freetype"
brew "fdk-aac"

View File

@ -1,27 +0,0 @@
#!/bin/sh
OSTYPE=$(uname)
if [ "${OSTYPE}" != "Darwin" ]; then
echo "[obs-websocket - Error] macOS build script can be run on Darwin-type OS only."
exit 1
fi
HAS_CMAKE=$(type cmake 2>/dev/null)
if [ "${HAS_CMAKE}" = "" ]; then
echo "[obs-websocket - Error] CMake not installed - please run 'install-dependencies-macos.sh' first."
exit 1
fi
echo "[obs-websocket] Building 'obs-websocket' for macOS."
mkdir -p build && cd build
cmake .. \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 \
-DQTDIR=/tmp/obsdeps \
-DLIBOBS_INCLUDE_DIR=../../obs-studio/libobs \
-DLIBOBS_LIB=../../obs-studio/libobs \
-DOBS_FRONTEND_LIB="$(pwd)/../../obs-studio/build/UI/obs-frontend-api/libobs-frontend-api.dylib" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_PREFIX=/usr \
&& make -j4

View File

@ -1,39 +0,0 @@
#!/bin/sh
OSTYPE=$(uname)
if [ "${OSTYPE}" != "Darwin" ]; then
echo "[obs-websocket - Error] macOS obs-studio build script can be run on Darwin-type OS only."
exit 1
fi
HAS_CMAKE=$(type cmake 2>/dev/null)
HAS_GIT=$(type git 2>/dev/null)
if [ "${HAS_CMAKE}" = "" ]; then
echo "[obs-websocket - Error] CMake not installed - please run 'install-dependencies-macos.sh' first."
exit 1
fi
if [ "${HAS_GIT}" = "" ]; then
echo "[obs-websocket - Error] Git not installed - please install Xcode developer tools or via Homebrew."
exit 1
fi
# Build obs-studio
cd ..
echo "[obs-websocket] Cloning obs-studio from GitHub.."
git clone https://github.com/obsproject/obs-studio
cd obs-studio
OBSLatestTag=$(git describe --tags --abbrev=0)
git checkout $OBSLatestTag
mkdir build && cd build
echo "[obs-websocket] Building obs-studio.."
cmake .. \
-DQTDIR=/tmp/obsdeps \
-DDepsPath=/tmp/obsdeps \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 \
-DDISABLE_PLUGINS=true \
-DENABLE_SCRIPTING=0 \
-DCMAKE_PREFIX_PATH=/tmp/obsdeps/lib/cmake \
&& make -j4

View File

@ -1,57 +0,0 @@
#!/bin/sh
OSTYPE=$(uname)
if [ "${OSTYPE}" != "Darwin" ]; then
echo "[obs-websocket - Error] macOS install dependencies script can be run on Darwin-type OS only."
exit 1
fi
HAS_BREW=$(type brew 2>/dev/null)
if [ "${HAS_BREW}" = "" ]; then
echo "[obs-websocket - Error] Please install Homebrew (https://www.brew.sh/) to build obs-websocket on macOS."
exit 1
fi
# OBS Studio Brew Deps
echo "[obs-websocket] Updating Homebrew.."
brew update >/dev/null
echo "[obs-websocket] Checking installed Homebrew formulas.."
if [ -d /usr/local/opt/openssl@1.0.2t ]; then
brew uninstall openssl@1.0.2t
brew untap local/openssl
fi
if [ -d /usr/local/opt/python@2.7.17 ]; then
brew uninstall python@2.7.17
brew untap local/python2
fi
brew bundle --file ./CI/macos/Brewfile
# Fetch and install Packages app
# =!= NOTICE =!=
# Installs a LaunchDaemon under /Library/LaunchDaemons/fr.whitebox.packages.build.dispatcher.plist
# =!= NOTICE =!=
HAS_PACKAGES=$(type packagesbuild 2>/dev/null)
if [ "${HAS_PACKAGES}" = "" ]; then
echo "[obs-websocket] Installing Packaging app (might require password due to 'sudo').."
curl -L -O http://s.sudre.free.fr/Software/files/Packages.dmg
sudo hdiutil attach ./Packages.dmg
sudo installer -pkg /Volumes/Packages\ 1.2.9/Install\ Packages.pkg -target /
fi
# OBS Deps
echo "[obs-websocket] Installing obs-websocket dependency 'OBS Deps ${OBS_DEPS_VERSION}'.."
wget --quiet --retry-connrefused --waitretry=1 https://github.com/obsproject/obs-deps/releases/download/${OBS_DEPS_VERSION}/macos-deps-${OBS_DEPS_VERSION}.tar.gz
tar -xf ./macos-deps-${OBS_DEPS_VERSION}.tar.gz -C /tmp
# Qt deps
echo "[obs-websocket] Installing obs-websocket dependency 'Qt ${QT_VERSION}'.."
curl -L -O https://github.com/obsproject/obs-deps/releases/download/${OBS_DEPS_VERSION}/macos-qt-${QT_VERSION}-${OBS_DEPS_VERSION}.tar.gz
tar -xf ./macos-qt-${QT_VERSION}-${OBS_DEPS_VERSION}.tar.gz -C "/tmp"
xattr -r -d com.apple.quarantine /tmp/obsdeps

View File

@ -514,7 +514,7 @@
<key>CONCLUSION_ACTION</key>
<integer>0</integer>
<key>IDENTIFIER</key>
<string>fr.palakis.obs-websocket</string>
<string>com.obsproject.obs-websocket</string>
<key>OVERWRITE_PERMISSIONS</key>
<false/>
<key>VERSION</key>

View File

@ -1,93 +0,0 @@
#!/bin/bash
set -e
OSTYPE=$(uname)
if [ "${OSTYPE}" != "Darwin" ]; then
echo "[obs-websocket - Error] macOS package script can be run on Darwin-type OS only."
exit 1
fi
echo "[obs-websocket] Preparing package build"
GIT_HASH=$(git rev-parse --short HEAD)
GIT_BRANCH_OR_TAG=$(git name-rev --name-only HEAD | awk -F/ '{print $NF}')
VERSION="$GIT_HASH-$GIT_BRANCH_OR_TAG"
FILENAME_UNSIGNED="obs-websocket-$VERSION-Unsigned.pkg"
FILENAME="obs-websocket-$VERSION.pkg"
echo "[obs-websocket] Modifying obs-websocket.so linking"
install_name_tool \
-change /tmp/obsdeps/lib/QtWidgets.framework/Versions/5/QtWidgets \
@executable_path/../Frameworks/QtWidgets.framework/Versions/5/QtWidgets \
-change /tmp/obsdeps/lib/QtGui.framework/Versions/5/QtGui \
@executable_path/../Frameworks/QtGui.framework/Versions/5/QtGui \
-change /tmp/obsdeps/lib/QtCore.framework/Versions/5/QtCore \
@executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore \
-change /tmp/obsdeps/lib/QtNetwork.framework/Versions/5/QtNetwork \
@executable_path/../Frameworks/QtNetwork.framework/Versions/5/QtNetwork \
-change /tmp/obsdeps/lib/QtSvg.framework/Versions/5/QtSvg \
@executable_path/../Frameworks/QtSvg.framework/Versions/5/QtSvg \
./build/obs-websocket.so
# Check if replacement worked
echo "[obs-websocket] Dependencies for obs-websocket"
otool -L ./build/obs-websocket.so
if [[ "$RELEASE_MODE" == "True" ]]; then
echo "[obs-websocket] Signing plugin binary: obs-websocket.so"
codesign --sign "$CODE_SIGNING_IDENTITY" ./build/obs-websocket.so
else
echo "[obs-websocket] Skipped plugin codesigning"
fi
echo "[obs-websocket] Actual package build"
packagesbuild ./CI/macos/obs-websocket.pkgproj
echo "[obs-websocket] Renaming obs-websocket.pkg to $FILENAME"
mv ./release/obs-websocket.pkg ./release/$FILENAME_UNSIGNED
if [[ "$RELEASE_MODE" == "True" ]]; then
echo "[obs-websocket] Signing installer: $FILENAME"
productsign \
--sign "$INSTALLER_SIGNING_IDENTITY" \
./release/$FILENAME_UNSIGNED \
./release/$FILENAME
rm ./release/$FILENAME_UNSIGNED
echo "[obs-websocket] Submitting installer $FILENAME for notarization"
zip -r ./release/$FILENAME.zip ./release/$FILENAME
UPLOAD_RESULT=$(xcrun altool \
--notarize-app \
--primary-bundle-id "fr.palakis.obs-websocket" \
--username "$AC_USERNAME" \
--password "$AC_PASSWORD" \
--asc-provider "$AC_PROVIDER_SHORTNAME" \
--file "./release/$FILENAME.zip")
rm ./release/$FILENAME.zip
REQUEST_UUID=$(echo $UPLOAD_RESULT | awk -F ' = ' '/RequestUUID/ {print $2}')
echo "Request UUID: $REQUEST_UUID"
echo "[obs-websocket] Wait for notarization result"
# Pieces of code borrowed from rednoah/notarized-app
while sleep 30 && date; do
CHECK_RESULT=$(xcrun altool \
--notarization-info "$REQUEST_UUID" \
--username "$AC_USERNAME" \
--password "$AC_PASSWORD" \
--asc-provider "$AC_PROVIDER_SHORTNAME")
echo $CHECK_RESULT
if ! grep -q "Status: in progress" <<< "$CHECK_RESULT"; then
echo "[obs-websocket] Staple ticket to installer: $FILENAME"
xcrun stapler staple ./release/$FILENAME
break
fi
done
else
echo "[obs-websocket] Skipped installer codesigning and notarization"
fi

View File

@ -114,6 +114,7 @@ set(obs-websocket_SOURCES
src/requesthandler/RequestHandler_Transitions.cpp
src/requesthandler/RequestHandler_Filters.cpp
src/requesthandler/RequestHandler_SceneItems.cpp
src/requesthandler/RequestHandler_Outputs.cpp
src/requesthandler/RequestHandler_Stream.cpp
src/requesthandler/RequestHandler_Record.cpp
src/requesthandler/RequestHandler_MediaInputs.cpp

2
deps/asio vendored

Submodule deps/asio updated: 08a7029cb1...b73dc1d2c0

View File

@ -1129,6 +1129,55 @@
],
"responseFields": []
},
{
"description": "Gets the info for a specific source filter.",
"requestType": "GetSourceFilter",
"complexity": 2,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"category": "filters",
"requestFields": [
{
"valueName": "sourceName",
"valueType": "String",
"valueDescription": "Name of the source",
"valueRestrictions": null,
"valueOptional": false,
"valueOptionalBehavior": null
},
{
"valueName": "filterName",
"valueType": "String",
"valueDescription": "Name of the filter",
"valueRestrictions": null,
"valueOptional": false,
"valueOptionalBehavior": null
}
],
"responseFields": [
{
"valueName": "filterEnabled",
"valueType": "Boolean",
"valueDescription": "Whether the filter is enabled"
},
{
"valueName": "filterIndex",
"valueType": "Number",
"valueDescription": "Index of the filter in the list, beginning at 0"
},
{
"valueName": "filterKind",
"valueType": "String",
"valueDescription": "The kind of filter"
},
{
"valueName": "filterSettings",
"valueType": "Object",
"valueDescription": "Settings object associated with the filter"
}
]
},
{
"description": "Gets data about the current plugin and RPC version.",
"requestType": "GetVersion",
@ -1472,6 +1521,48 @@
}
]
},
{
"description": "Gets the names of all special inputs.",
"requestType": "GetSpecialInputs",
"complexity": 2,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"category": "inputs",
"requestFields": [],
"responseFields": [
{
"valueName": "desktop1",
"valueType": "String",
"valueDescription": "Name of the Desktop Audio input"
},
{
"valueName": "desktop2",
"valueType": "String",
"valueDescription": "Name of the Desktop Audio 2 input"
},
{
"valueName": "mic1",
"valueType": "String",
"valueDescription": "Name of the Mic/Auxiliary Audio input"
},
{
"valueName": "mic2",
"valueType": "String",
"valueDescription": "Name of the Mic/Auxiliary Audio 2 input"
},
{
"valueName": "mic3",
"valueType": "String",
"valueDescription": "Name of the Mic/Auxiliary Audio 3 input"
},
{
"valueName": "mic4",
"valueType": "String",
"valueDescription": "Name of the Mic/Auxiliary Audio 4 input"
}
]
},
{
"description": "Creates a new input, adding it as a scene item to the specified scene.",
"requestType": "CreateInput",
@ -1811,13 +1902,67 @@
"valueName": "inputVolumeDb",
"valueType": "Number",
"valueDescription": "Volume setting in dB",
"valueRestrictions": ">= -100, <= -26",
"valueRestrictions": ">= -100, <= 26",
"valueOptional": true,
"valueOptionalBehavior": "`inputVolumeMul` should be specified"
}
],
"responseFields": []
},
{
"description": "Gets the audio balance of an input.",
"requestType": "GetInputAudioBalance",
"complexity": 2,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"category": "inputs",
"requestFields": [
{
"valueName": "inputName",
"valueType": "String",
"valueDescription": "Name of the input to get the audio balance of",
"valueRestrictions": null,
"valueOptional": false,
"valueOptionalBehavior": null
}
],
"responseFields": [
{
"valueName": "inputAudioBalance",
"valueType": "Number",
"valueDescription": "Audio balance value from 0.0-1.0"
}
]
},
{
"description": "Sets the audio balance of an input.",
"requestType": "SetInputAudioBalance",
"complexity": 2,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"category": "inputs",
"requestFields": [
{
"valueName": "inputName",
"valueType": "String",
"valueDescription": "Name of the input to set the audio balance of",
"valueRestrictions": null,
"valueOptional": false,
"valueOptionalBehavior": null
},
{
"valueName": "inputAudioBalance",
"valueType": "Number",
"valueDescription": "New audio balance value",
"valueRestrictions": ">= 0.0, <= 1.0",
"valueOptional": false,
"valueOptionalBehavior": null
}
],
"responseFields": []
},
{
"description": "Gets the audio sync offset of an input.\n\nNote: The audio sync offset can be negative too!",
"requestType": "GetInputAudioSyncOffset",
@ -1926,6 +2071,60 @@
],
"responseFields": []
},
{
"description": "Gets the enable state of all audio tracks of an input.",
"requestType": "GetInputAudioTracks",
"complexity": 2,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"category": "inputs",
"requestFields": [
{
"valueName": "inputName",
"valueType": "String",
"valueDescription": "Name of the input",
"valueRestrictions": null,
"valueOptional": false,
"valueOptionalBehavior": null
}
],
"responseFields": [
{
"valueName": "inputAudioTracks",
"valueType": "Object",
"valueDescription": "Object of audio tracks and associated enable states"
}
]
},
{
"description": "Sets the enable state of audio tracks of an input.",
"requestType": "SetInputAudioTracks",
"complexity": 2,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"category": "inputs",
"requestFields": [
{
"valueName": "inputName",
"valueType": "String",
"valueDescription": "Name of the input",
"valueRestrictions": null,
"valueOptional": false,
"valueOptionalBehavior": null
},
{
"valueName": "inputAudioTracks",
"valueType": "Object",
"valueDescription": "Track settings to apply",
"valueRestrictions": null,
"valueOptional": false,
"valueOptionalBehavior": null
}
],
"responseFields": []
},
{
"description": "Gets the items of a list property from an input's properties.\n\nNote: Use this in cases where an input provides a dynamic, selectable list of items. For example, display capture, where it provides a list of available displays.",
"requestType": "GetInputPropertiesListPropertyItems",
@ -2108,6 +2307,62 @@
],
"responseFields": []
},
{
"description": "Gets the status of the virtualcam output.",
"requestType": "GetVirtualCamStatus",
"complexity": 1,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"category": "outputs",
"requestFields": [],
"responseFields": [
{
"valueName": "outputActive",
"valueType": "Boolean",
"valueDescription": "Whether the output is active"
}
]
},
{
"description": "Toggles the state of the virtualcam output.",
"requestType": "ToggleVirtualCam",
"complexity": 1,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"category": "outputs",
"requestFields": [],
"responseFields": [
{
"valueName": "outputActive",
"valueType": "Boolean",
"valueDescription": "Whether the output is active"
}
]
},
{
"description": "Starts the virtualcam output.",
"requestType": "StartVirtualCam",
"complexity": 1,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"category": "outputs",
"requestFields": [],
"responseFields": []
},
{
"description": "Stops the virtualcam output.",
"requestType": "StopVirtualCam",
"complexity": 1,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"category": "outputs",
"requestFields": [],
"responseFields": []
},
{
"description": "Gets the status of the record output.",
"requestType": "GetRecordStatus",
@ -2706,6 +2961,76 @@
],
"responseFields": []
},
{
"description": "Gets the blend mode of a scene item.\n\nBlend modes:\n\n- `OBS_BLEND_NORMAL`\n- `OBS_BLEND_ADDITIVE`\n- `OBS_BLEND_SUBTRACT`\n- `OBS_BLEND_SCREEN`\n- `OBS_BLEND_MULTIPLY`\n- `OBS_BLEND_LIGHTEN`\n- `OBS_BLEND_DARKEN`\n\nScenes and Groups",
"requestType": "GetSceneItemBlendMode",
"complexity": 2,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"category": "scene items",
"requestFields": [
{
"valueName": "sceneName",
"valueType": "String",
"valueDescription": "Name of the scene the item is in",
"valueRestrictions": null,
"valueOptional": false,
"valueOptionalBehavior": null
},
{
"valueName": "sceneItemId",
"valueType": "Number",
"valueDescription": "Numeric ID of the scene item",
"valueRestrictions": ">= 0",
"valueOptional": false,
"valueOptionalBehavior": null
}
],
"responseFields": [
{
"valueName": "sceneItemBlendMode",
"valueType": "String",
"valueDescription": "Current blend mode"
}
]
},
{
"description": "Sets the blend mode of a scene item.\n\nScenes and Groups",
"requestType": "SetSceneItemBlendMode",
"complexity": 2,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"category": "scene items",
"requestFields": [
{
"valueName": "sceneName",
"valueType": "String",
"valueDescription": "Name of the scene the item is in",
"valueRestrictions": null,
"valueOptional": false,
"valueOptionalBehavior": null
},
{
"valueName": "sceneItemId",
"valueType": "Number",
"valueDescription": "Numeric ID of the scene item",
"valueRestrictions": ">= 0",
"valueOptional": false,
"valueOptionalBehavior": null
},
{
"valueName": "sceneItemBlendMode",
"valueType": "String",
"valueDescription": "New blend mode",
"valueRestrictions": null,
"valueOptional": false,
"valueOptionalBehavior": null
}
],
"responseFields": []
},
{
"description": "Gets an array of all scenes in OBS.",
"requestType": "GetSceneList",
@ -2729,7 +3054,24 @@
{
"valueName": "scenes",
"valueType": "Array<Object>",
"valueDescription": "Array of scenes in OBS"
"valueDescription": "Array of scenes"
}
]
},
{
"description": "Gets an array of all groups in OBS.\n\nGroups in OBS are actually scenes, but renamed and modified. In obs-websocket, we treat them as scenes where we can.",
"requestType": "GetGroupList",
"complexity": 2,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"category": "scenes",
"requestFields": [],
"responseFields": [
{
"valueName": "groups",
"valueType": "Array<String>",
"valueDescription": "Array of group names"
}
]
},
@ -3317,6 +3659,66 @@
}
],
"responseFields": []
},
{
"description": "Opens the properties dialog of an input.",
"requestType": "OpenInputPropertiesDialog",
"complexity": 1,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"category": "ui",
"requestFields": [
{
"valueName": "inputName",
"valueType": "String",
"valueDescription": "Name of the input to open the dialog of",
"valueRestrictions": null,
"valueOptional": false,
"valueOptionalBehavior": null
}
],
"responseFields": []
},
{
"description": "Opens the filters dialog of an input.",
"requestType": "OpenInputFiltersDialog",
"complexity": 1,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"category": "ui",
"requestFields": [
{
"valueName": "inputName",
"valueType": "String",
"valueDescription": "Name of the input to open the dialog of",
"valueRestrictions": null,
"valueOptional": false,
"valueOptionalBehavior": null
}
],
"responseFields": []
},
{
"description": "Opens the interact dialog of an input.",
"requestType": "OpenInputInteractDialog",
"complexity": 1,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"category": "ui",
"requestFields": [
{
"valueName": "inputName",
"valueType": "String",
"valueDescription": "Name of the input to open the dialog of",
"valueRestrictions": null,
"valueOptional": false,
"valueOptionalBehavior": null
}
],
"responseFields": []
}
],
"events": [
@ -3602,6 +4004,28 @@
}
]
},
{
"description": "The audio balance value of an input has changed.",
"eventType": "InputAudioBalanceChanged",
"eventSubscription": "Inputs",
"complexity": 2,
"rpcVersion": "1",
"deprecated": false,
"initialVersion": "5.0.0",
"category": "inputs",
"dataFields": [
{
"valueName": "inputName",
"valueType": "String",
"valueDescription": "Name of the affected input"
},
{
"valueName": "inputAudioBalance",
"valueType": "Number",
"valueDescription": "New audio balance value of the input"
}
]
},
{
"description": "The sync offset of an input has changed.",
"eventType": "InputAudioSyncOffsetChanged",
@ -3641,8 +4065,8 @@
},
{
"valueName": "inputAudioTracks",
"valueType": "Array<Boolean>",
"valueDescription": "Array of audio tracks along with their associated enable states"
"valueType": "Object",
"valueDescription": "Object of audio tracks along with their associated enable states"
}
]
},

View File

@ -1263,6 +1263,7 @@ Subscription value to receive the `SceneItemTransformChanged` high-volume event.
- [InputShowStateChanged](#inputshowstatechanged)
- [InputMuteStateChanged](#inputmutestatechanged)
- [InputVolumeChanged](#inputvolumechanged)
- [InputAudioBalanceChanged](#inputaudiobalancechanged)
- [InputAudioSyncOffsetChanged](#inputaudiosyncoffsetchanged)
- [InputAudioTracksChanged](#inputaudiotrackschanged)
- [InputAudioMonitorTypeChanged](#inputaudiomonitortypechanged)
@ -1666,6 +1667,24 @@ An input's volume level has changed.
---
### InputAudioBalanceChanged
The audio balance value of an input has changed.
- Complexity Rating: `2/5`
- Latest Supported RPC Version: `1`
- Added in v5.0.0
**Data Fields:**
| Name | Type | Description |
| ---- | :---: | ----------- |
| inputName | String | Name of the affected input |
| inputAudioBalance | Number | New audio balance value of the input |
---
### InputAudioSyncOffsetChanged
The sync offset of an input has changed.
@ -1698,7 +1717,7 @@ The audio tracks of an input have changed.
| Name | Type | Description |
| ---- | :---: | ----------- |
| inputName | String | Name of the input |
| inputAudioTracks | Array&lt;Boolean&gt; | Array of audio tracks along with their associated enable states |
| inputAudioTracks | Object | Object of audio tracks along with their associated enable states |
---
@ -2045,6 +2064,7 @@ Studio mode has been enabled or disabled.
- [SaveSourceScreenshot](#savesourcescreenshot)
- [Scenes](#scenes-1)
- [GetSceneList](#getscenelist)
- [GetGroupList](#getgrouplist)
- [GetCurrentProgramScene](#getcurrentprogramscene)
- [SetCurrentProgramScene](#setcurrentprogramscene)
- [GetCurrentPreviewScene](#getcurrentpreviewscene)
@ -2055,6 +2075,7 @@ Studio mode has been enabled or disabled.
- [Inputs](#inputs-1)
- [GetInputList](#getinputlist)
- [GetInputKindList](#getinputkindlist)
- [GetSpecialInputs](#getspecialinputs)
- [CreateInput](#createinput)
- [RemoveInput](#removeinput)
- [SetInputName](#setinputname)
@ -2066,10 +2087,14 @@ Studio mode has been enabled or disabled.
- [ToggleInputMute](#toggleinputmute)
- [GetInputVolume](#getinputvolume)
- [SetInputVolume](#setinputvolume)
- [GetInputAudioBalance](#getinputaudiobalance)
- [SetInputAudioBalance](#setinputaudiobalance)
- [GetInputAudioSyncOffset](#getinputaudiosyncoffset)
- [SetInputAudioSyncOffset](#setinputaudiosyncoffset)
- [GetInputAudioMonitorType](#getinputaudiomonitortype)
- [SetInputAudioMonitorType](#setinputaudiomonitortype)
- [GetInputAudioTracks](#getinputaudiotracks)
- [SetInputAudioTracks](#setinputaudiotracks)
- [GetInputPropertiesListPropertyItems](#getinputpropertieslistpropertyitems)
- [PressInputPropertiesButton](#pressinputpropertiesbutton)
- [Transitions](#transitions)
@ -2080,6 +2105,8 @@ Studio mode has been enabled or disabled.
- [SetCurrentSceneTransitionDuration](#setcurrentscenetransitionduration)
- [SetCurrentSceneTransitionSettings](#setcurrentscenetransitionsettings)
- [TriggerStudioModeTransition](#triggerstudiomodetransition)
- [Filters](#filters)
- [GetSourceFilter](#getsourcefilter)
- [Scene Items](#scene-items-1)
- [GetSceneItemList](#getsceneitemlist)
- [GetGroupItemList](#getgroupitemlist)
@ -2095,6 +2122,13 @@ Studio mode has been enabled or disabled.
- [SetSceneItemLocked](#setsceneitemlocked)
- [GetSceneItemIndex](#getsceneitemindex)
- [SetSceneItemIndex](#setsceneitemindex)
- [GetSceneItemBlendMode](#getsceneitemblendmode)
- [SetSceneItemBlendMode](#setsceneitemblendmode)
- [Outputs](#outputs-1)
- [GetVirtualCamStatus](#getvirtualcamstatus)
- [ToggleVirtualCam](#togglevirtualcam)
- [StartVirtualCam](#startvirtualcam)
- [StopVirtualCam](#stopvirtualcam)
- [Stream](#stream)
- [GetStreamStatus](#getstreamstatus)
- [ToggleStream](#togglestream)
@ -2117,6 +2151,9 @@ Studio mode has been enabled or disabled.
- [Ui](#ui-1)
- [GetStudioModeEnabled](#getstudiomodeenabled)
- [SetStudioModeEnabled](#setstudiomodeenabled)
- [OpenInputPropertiesDialog](#openinputpropertiesdialog)
- [OpenInputFiltersDialog](#openinputfiltersdialog)
- [OpenInputInteractDialog](#openinputinteractdialog)
@ -2703,7 +2740,26 @@ Gets an array of all scenes in OBS.
| ---- | :---: | ----------- |
| currentProgramSceneName | String | Current program scene |
| currentPreviewSceneName | String | Current preview scene. `null` if not in studio mode |
| scenes | Array&lt;Object&gt; | Array of scenes in OBS |
| scenes | Array&lt;Object&gt; | Array of scenes |
---
### GetGroupList
Gets an array of all groups in OBS.
Groups in OBS are actually scenes, but renamed and modified. In obs-websocket, we treat them as scenes where we can.
- Complexity Rating: `2/5`
- Latest Supported RPC Version: `1`
- Added in v5.0.0
**Response Fields:**
| Name | Type | Description |
| ---- | :---: | ----------- |
| groups | Array&lt;String&gt; | Array of group names |
---
@ -2880,6 +2936,28 @@ Gets an array of all available input kinds in OBS.
---
### GetSpecialInputs
Gets the names of all special inputs.
- Complexity Rating: `2/5`
- Latest Supported RPC Version: `1`
- Added in v5.0.0
**Response Fields:**
| Name | Type | Description |
| ---- | :---: | ----------- |
| desktop1 | String | Name of the Desktop Audio input |
| desktop2 | String | Name of the Desktop Audio 2 input |
| mic1 | String | Name of the Mic/Auxiliary Audio input |
| mic2 | String | Name of the Mic/Auxiliary Audio 2 input |
| mic3 | String | Name of the Mic/Auxiliary Audio 3 input |
| mic4 | String | Name of the Mic/Auxiliary Audio 4 input |
---
### CreateInput
Creates a new input, adding it as a scene item to the specified scene.
@ -3121,7 +3199,49 @@ Sets the volume setting of an input.
| ---- | :---: | ----------- | :----------------: | ----------------- |
| inputName | String | Name of the input to set the volume of | None | N/A |
| ?inputVolumeMul | Number | Volume setting in mul | >= 0, <= 20 | `inputVolumeDb` should be specified |
| ?inputVolumeDb | Number | Volume setting in dB | >= -100, <= -26 | `inputVolumeMul` should be specified |
| ?inputVolumeDb | Number | Volume setting in dB | >= -100, <= 26 | `inputVolumeMul` should be specified |
---
### GetInputAudioBalance
Gets the audio balance of an input.
- Complexity Rating: `2/5`
- Latest Supported RPC Version: `1`
- Added in v5.0.0
**Request Fields:**
| Name | Type | Description | Value Restrictions | ?Default Behavior |
| ---- | :---: | ----------- | :----------------: | ----------------- |
| inputName | String | Name of the input to get the audio balance of | None | N/A |
**Response Fields:**
| Name | Type | Description |
| ---- | :---: | ----------- |
| inputAudioBalance | Number | Audio balance value from 0.0-1.0 |
---
### SetInputAudioBalance
Sets the audio balance of an input.
- Complexity Rating: `2/5`
- Latest Supported RPC Version: `1`
- Added in v5.0.0
**Request Fields:**
| Name | Type | Description | Value Restrictions | ?Default Behavior |
| ---- | :---: | ----------- | :----------------: | ----------------- |
| inputName | String | Name of the input to set the audio balance of | None | N/A |
| inputAudioBalance | Number | New audio balance value | >= 0.0, <= 1.0 | N/A |
---
@ -3216,6 +3336,48 @@ Sets the audio monitor type of an input.
---
### GetInputAudioTracks
Gets the enable state of all audio tracks of an input.
- Complexity Rating: `2/5`
- Latest Supported RPC Version: `1`
- Added in v5.0.0
**Request Fields:**
| Name | Type | Description | Value Restrictions | ?Default Behavior |
| ---- | :---: | ----------- | :----------------: | ----------------- |
| inputName | String | Name of the input | None | N/A |
**Response Fields:**
| Name | Type | Description |
| ---- | :---: | ----------- |
| inputAudioTracks | Object | Object of audio tracks and associated enable states |
---
### SetInputAudioTracks
Sets the enable state of audio tracks of an input.
- Complexity Rating: `2/5`
- Latest Supported RPC Version: `1`
- Added in v5.0.0
**Request Fields:**
| Name | Type | Description | Value Restrictions | ?Default Behavior |
| ---- | :---: | ----------- | :----------------: | ----------------- |
| inputName | String | Name of the input | None | N/A |
| inputAudioTracks | Object | Track settings to apply | None | N/A |
---
### GetInputPropertiesListPropertyItems
Gets the items of a list property from an input's properties.
@ -3387,6 +3549,35 @@ Triggers the current scene transition. Same functionality as the `Transition` bu
- Added in v5.0.0
## Filters
### GetSourceFilter
Gets the info for a specific source filter.
- Complexity Rating: `2/5`
- Latest Supported RPC Version: `1`
- Added in v5.0.0
**Request Fields:**
| Name | Type | Description | Value Restrictions | ?Default Behavior |
| ---- | :---: | ----------- | :----------------: | ----------------- |
| sourceName | String | Name of the source | None | N/A |
| filterName | String | Name of the filter | None | N/A |
**Response Fields:**
| Name | Type | Description |
| ---- | :---: | ----------- |
| filterEnabled | Boolean | Whether the filter is enabled |
| filterIndex | Number | Index of the filter in the list, beginning at 0 |
| filterKind | String | The kind of filter |
| filterSettings | Object | Settings object associated with the filter |
## Scene Items
### GetSceneItemList
@ -3736,6 +3927,119 @@ Scenes and Groups
| sceneItemId | Number | Numeric ID of the scene item | >= 0 | N/A |
| sceneItemIndex | Number | New index position of the scene item | >= 0 | N/A |
---
### GetSceneItemBlendMode
Gets the blend mode of a scene item.
Blend modes:
- `OBS_BLEND_NORMAL`
- `OBS_BLEND_ADDITIVE`
- `OBS_BLEND_SUBTRACT`
- `OBS_BLEND_SCREEN`
- `OBS_BLEND_MULTIPLY`
- `OBS_BLEND_LIGHTEN`
- `OBS_BLEND_DARKEN`
Scenes and Groups
- Complexity Rating: `2/5`
- Latest Supported RPC Version: `1`
- Added in v5.0.0
**Request Fields:**
| Name | Type | Description | Value Restrictions | ?Default Behavior |
| ---- | :---: | ----------- | :----------------: | ----------------- |
| sceneName | String | Name of the scene the item is in | None | N/A |
| sceneItemId | Number | Numeric ID of the scene item | >= 0 | N/A |
**Response Fields:**
| Name | Type | Description |
| ---- | :---: | ----------- |
| sceneItemBlendMode | String | Current blend mode |
---
### SetSceneItemBlendMode
Sets the blend mode of a scene item.
Scenes and Groups
- Complexity Rating: `2/5`
- Latest Supported RPC Version: `1`
- Added in v5.0.0
**Request Fields:**
| Name | Type | Description | Value Restrictions | ?Default Behavior |
| ---- | :---: | ----------- | :----------------: | ----------------- |
| sceneName | String | Name of the scene the item is in | None | N/A |
| sceneItemId | Number | Numeric ID of the scene item | >= 0 | N/A |
| sceneItemBlendMode | String | New blend mode | None | N/A |
## Outputs
### GetVirtualCamStatus
Gets the status of the virtualcam output.
- Complexity Rating: `1/5`
- Latest Supported RPC Version: `1`
- Added in v5.0.0
**Response Fields:**
| Name | Type | Description |
| ---- | :---: | ----------- |
| outputActive | Boolean | Whether the output is active |
---
### ToggleVirtualCam
Toggles the state of the virtualcam output.
- Complexity Rating: `1/5`
- Latest Supported RPC Version: `1`
- Added in v5.0.0
**Response Fields:**
| Name | Type | Description |
| ---- | :---: | ----------- |
| outputActive | Boolean | Whether the output is active |
---
### StartVirtualCam
Starts the virtualcam output.
- Complexity Rating: `1/5`
- Latest Supported RPC Version: `1`
- Added in v5.0.0
---
### StopVirtualCam
Stops the virtualcam output.
- Complexity Rating: `1/5`
- Latest Supported RPC Version: `1`
- Added in v5.0.0
## Stream
@ -4026,4 +4330,55 @@ Enables or disables studio mode
| ---- | :---: | ----------- | :----------------: | ----------------- |
| studioModeEnabled | Boolean | True == Enabled, False == Disabled | None | N/A |
---
### OpenInputPropertiesDialog
Opens the properties dialog of an input.
- Complexity Rating: `1/5`
- Latest Supported RPC Version: `1`
- Added in v5.0.0
**Request Fields:**
| Name | Type | Description | Value Restrictions | ?Default Behavior |
| ---- | :---: | ----------- | :----------------: | ----------------- |
| inputName | String | Name of the input to open the dialog of | None | N/A |
---
### OpenInputFiltersDialog
Opens the filters dialog of an input.
- Complexity Rating: `1/5`
- Latest Supported RPC Version: `1`
- Added in v5.0.0
**Request Fields:**
| Name | Type | Description | Value Restrictions | ?Default Behavior |
| ---- | :---: | ----------- | :----------------: | ----------------- |
| inputName | String | Name of the input to open the dialog of | None | N/A |
---
### OpenInputInteractDialog
Opens the interact dialog of an input.
- Complexity Rating: `1/5`
- Latest Supported RPC Version: `1`
- Added in v5.0.0
**Request Fields:**
| Name | Type | Description | Value Restrictions | ?Default Behavior |
| ---- | :---: | ----------- | :----------------: | ----------------- |
| inputName | String | Name of the input to open the dialog of | None | N/A |

View File

@ -22,6 +22,8 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <obs.h>
#define OBS_WEBSOCKET_API_VERSION 1
#ifdef __cplusplus
extern "C" {
#endif

View File

@ -134,9 +134,10 @@ void EventHandler::ConnectSourceSignals(obs_source_t *source) // Applies to inpu
signal_handler_connect(sh, "hide", HandleInputShowStateChanged, this);
signal_handler_connect(sh, "mute", HandleInputMuteStateChanged, this);
signal_handler_connect(sh, "volume", HandleInputVolumeChanged, this);
signal_handler_connect(sh, "audio_balance", HandleInputAudioBalanceChanged, this);
signal_handler_connect(sh, "audio_sync", HandleInputAudioSyncOffsetChanged, this);
signal_handler_connect(sh, "audio_mixers", HandleInputAudioTracksChanged, this);
//signal_handler_connect(sh, "audio_monitoring", HandleInputAudioMonitorTypeChanged, this);
signal_handler_connect(sh, "audio_monitoring", HandleInputAudioMonitorTypeChanged, this);
if (sourceType == OBS_SOURCE_TYPE_INPUT) {
signal_handler_connect(sh, "media_started", HandleMediaInputPlaybackStarted, this);
@ -174,9 +175,10 @@ void EventHandler::DisconnectSourceSignals(obs_source_t *source)
signal_handler_disconnect(sh, "hide", HandleInputShowStateChanged, this);
signal_handler_disconnect(sh, "mute", HandleInputMuteStateChanged, this);
signal_handler_disconnect(sh, "volume", HandleInputVolumeChanged, this);
signal_handler_disconnect(sh, "audio_balance", HandleInputAudioBalanceChanged, this);
signal_handler_disconnect(sh, "audio_sync", HandleInputAudioSyncOffsetChanged, this);
signal_handler_disconnect(sh, "audio_mixers", HandleInputAudioTracksChanged, this);
//signal_handler_disconnect(sh, "audio_monitoring", HandleInputAudioMonitorTypeChanged, this);
signal_handler_disconnect(sh, "audio_monitoring", HandleInputAudioMonitorTypeChanged, this);
signal_handler_disconnect(sh, "media_started", HandleMediaInputPlaybackStarted, this);
signal_handler_disconnect(sh, "media_ended", HandleMediaInputPlaybackEnded, this);
signal_handler_disconnect(sh, "media_pause", SourceMediaPauseMultiHandler, this);
@ -197,7 +199,7 @@ void EventHandler::DisconnectSourceSignals(obs_source_t *source)
void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(private_data);
auto eventHandler = static_cast<EventHandler*>(private_data);
if (!eventHandler->_obsLoaded.load()) {
if (event == OBS_FRONTEND_EVENT_FINISHED_LOADING) {
@ -208,14 +210,14 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
// In the case that plugins become hotloadable, this will have to go back into `EventHandler::EventHandler()`
// Enumerate inputs and connect each one
obs_enum_sources([](void* param, obs_source_t* source) {
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
eventHandler->ConnectSourceSignals(source);
return true;
}, private_data);
// Enumerate scenes and connect each one
obs_enum_scenes([](void* param, obs_source_t* source) {
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
eventHandler->ConnectSourceSignals(source);
return true;
}, private_data);
@ -241,14 +243,14 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
// In the case that plugins become hotloadable, this will have to go back into `EventHandler::~EventHandler()`
// Enumerate inputs and disconnect each one
obs_enum_sources([](void* param, obs_source_t* source) {
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
eventHandler->DisconnectSourceSignals(source);
return true;
}, private_data);
// Enumerate scenes and disconnect each one
obs_enum_scenes([](void* param, obs_source_t* source) {
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
eventHandler->DisconnectSourceSignals(source);
return true;
}, private_data);
@ -264,18 +266,18 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
break;
// Config
//case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING:
// eventHandler->HandleCurrentSceneCollectionChanging();
// break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGING:
eventHandler->HandleCurrentSceneCollectionChanging();
break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_CHANGED:
eventHandler->HandleCurrentSceneCollectionChanged();
break;
case OBS_FRONTEND_EVENT_SCENE_COLLECTION_LIST_CHANGED:
eventHandler->HandleSceneCollectionListChanged();
break;
//case OBS_FRONTEND_EVENT_PROFILE_CHANGING:
// eventHandler->HandleCurrentProfileChanging();
// break;
case OBS_FRONTEND_EVENT_PROFILE_CHANGING:
eventHandler->HandleCurrentProfileChanging();
break;
case OBS_FRONTEND_EVENT_PROFILE_CHANGED:
eventHandler->HandleCurrentProfileChanged();
break;
@ -363,7 +365,7 @@ void EventHandler::OnFrontendEvent(enum obs_frontend_event event, void *private_
// Only called for creation of a public source
void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
// Don't react to signals until OBS has finished loading
if (!eventHandler->_obsLoaded.load())
@ -390,7 +392,7 @@ void EventHandler::SourceCreatedMultiHandler(void *param, calldata_t *data)
// Only called for destruction of a public source
void EventHandler::SourceDestroyedMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
// We can't use any smart types here because releasing the source will cause infinite recursion
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
@ -418,7 +420,7 @@ void EventHandler::SourceDestroyedMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceRemovedMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
if (!eventHandler->_obsLoaded.load())
return;
@ -441,7 +443,7 @@ void EventHandler::SourceRemovedMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceRenamedMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
if (!eventHandler->_obsLoaded.load())
return;

View File

@ -107,6 +107,7 @@ class EventHandler
static void HandleInputShowStateChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputMuteStateChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputVolumeChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputAudioBalanceChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputAudioSyncOffsetChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputAudioTracksChanged(void *param, calldata_t *data); // Direct callback
static void HandleInputAudioMonitorTypeChanged(void *param, calldata_t *data); // Direct callback

View File

@ -111,7 +111,7 @@ void EventHandler::HandleInputNameChanged(obs_source_t *, std::string oldInputNa
*/
void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
if (!eventHandler->_inputActiveStateChangedRef.load())
return;
@ -147,7 +147,7 @@ void EventHandler::HandleInputActiveStateChanged(void *param, calldata_t *data)
*/
void EventHandler::HandleInputShowStateChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
if (!eventHandler->_inputShowStateChangedRef.load())
return;
@ -181,7 +181,7 @@ void EventHandler::HandleInputShowStateChanged(void *param, calldata_t *data)
*/
void EventHandler::HandleInputMuteStateChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -213,7 +213,7 @@ void EventHandler::HandleInputMuteStateChanged(void *param, calldata_t *data)
*/
void EventHandler::HandleInputVolumeChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -236,6 +236,39 @@ void EventHandler::HandleInputVolumeChanged(void *param, calldata_t *data)
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputVolumeChanged", eventData);
}
/**
* The audio balance value of an input has changed.
*
* @dataField inputName | String | Name of the affected input
* @dataField inputAudioBalance | Number | New audio balance value of the input
*
* @eventType InputAudioBalanceChanged
* @eventSubscription Inputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @category inputs
* @api events
*/
void EventHandler::HandleInputAudioBalanceChanged(void *param, calldata_t *data)
{
auto eventHandler = static_cast<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;
float inputAudioBalance = (float)calldata_float(data, "balance");
json eventData;
eventData["inputName"] = obs_source_get_name(source);
eventData["inputAudioBalance"] = inputAudioBalance;
eventHandler->BroadcastEvent(EventSubscription::Inputs, "InputAudioBalanceChanged", eventData);
}
/**
* The sync offset of an input has changed.
*
@ -252,7 +285,7 @@ void EventHandler::HandleInputVolumeChanged(void *param, calldata_t *data)
*/
void EventHandler::HandleInputAudioSyncOffsetChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -272,8 +305,8 @@ void EventHandler::HandleInputAudioSyncOffsetChanged(void *param, calldata_t *da
/**
* The audio tracks of an input have changed.
*
* @dataField inputName | String | Name of the input
* @dataField inputAudioTracks | Array<Boolean> | Array of audio tracks along with their associated enable states
* @dataField inputName | String | Name of the input
* @dataField inputAudioTracks | Object | Object of audio tracks along with their associated enable states
*
* @eventType InputAudioTracksChanged
* @eventSubscription Inputs
@ -285,7 +318,7 @@ void EventHandler::HandleInputAudioSyncOffsetChanged(void *param, calldata_t *da
*/
void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -328,7 +361,7 @@ void EventHandler::HandleInputAudioTracksChanged(void *param, calldata_t *data)
*/
void EventHandler::HandleInputAudioMonitorTypeChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)

View File

@ -35,7 +35,7 @@ std::string GetMediaInputActionString(ObsMediaInputAction action) {
void EventHandler::SourceMediaPauseMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -49,7 +49,7 @@ void EventHandler::SourceMediaPauseMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaPlayMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -63,7 +63,7 @@ void EventHandler::SourceMediaPlayMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaRestartMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -77,7 +77,7 @@ void EventHandler::SourceMediaRestartMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaStopMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -91,7 +91,7 @@ void EventHandler::SourceMediaStopMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaNextMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -105,7 +105,7 @@ void EventHandler::SourceMediaNextMultiHandler(void *param, calldata_t *data)
void EventHandler::SourceMediaPreviousMultiHandler(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -132,7 +132,7 @@ void EventHandler::SourceMediaPreviousMultiHandler(void *param, calldata_t *data
*/
void EventHandler::HandleMediaInputPlaybackStarted(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)
@ -161,7 +161,7 @@ void EventHandler::HandleMediaInputPlaybackStarted(void *param, calldata_t *data
*/
void EventHandler::HandleMediaInputPlaybackEnded(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_source_t *source = GetCalldataPointer<obs_source_t>(data, "source");
if (!source)

View File

@ -37,7 +37,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
*/
void EventHandler::HandleSceneItemCreated(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene)
@ -74,7 +74,7 @@ void EventHandler::HandleSceneItemCreated(void *param, calldata_t *data)
*/
void EventHandler::HandleSceneItemRemoved(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene)
@ -107,7 +107,7 @@ void EventHandler::HandleSceneItemRemoved(void *param, calldata_t *data)
*/
void EventHandler::HandleSceneItemListReindexed(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene)
@ -136,7 +136,7 @@ void EventHandler::HandleSceneItemListReindexed(void *param, calldata_t *data)
*/
void EventHandler::HandleSceneItemEnableStateChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene)
@ -172,7 +172,7 @@ void EventHandler::HandleSceneItemEnableStateChanged(void *param, calldata_t *da
*/
void EventHandler::HandleSceneItemLockStateChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
obs_scene_t *scene = GetCalldataPointer<obs_scene_t>(data, "scene");
if (!scene)
@ -208,7 +208,7 @@ void EventHandler::HandleSceneItemLockStateChanged(void *param, calldata_t *data
*/
void EventHandler::HandleSceneItemTransformChanged(void *param, calldata_t *data)
{
auto eventHandler = reinterpret_cast<EventHandler*>(param);
auto eventHandler = static_cast<EventHandler*>(param);
if (!eventHandler->_sceneItemTransformChangedRef.load())
return;

View File

@ -48,6 +48,7 @@ bool obs_module_load(void)
{
blog(LOG_INFO, "[obs_module_load] you can haz websockets (Version: %s | RPC Version: %d)", OBS_WEBSOCKET_VERSION, OBS_WEBSOCKET_RPC_VERSION);
blog(LOG_INFO, "[obs_module_load] Qt version (compile-time): %s | Qt version (run-time): %s", QT_VERSION_STR, qVersion());
blog(LOG_INFO, "[obs_module_load] Linked ASIO Version: %d", ASIO_VERSION);
// Initialize the cpu stats
_cpuUsageInfo = os_cpu_usage_info_start();
@ -68,7 +69,7 @@ bool obs_module_load(void)
// Initialize the settings dialog
obs_frontend_push_ui_translation(obs_module_get_string);
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window());
QMainWindow* mainWindow = static_cast<QMainWindow*>(obs_frontend_get_main_window());
_settingsDialog = new SettingsDialog(mainWindow);
obs_frontend_pop_ui_translation();

View File

@ -21,13 +21,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <memory>
#include <obs.hpp>
#ifdef _MSC_VER
#pragma push_macro("strtoll")
#endif
#include <util/platform.h>
#ifdef _MSC_VER
#pragma pop_macro("strtoll")
#endif
#include "utils/Obs.h"
#include "plugin-macros.generated.h"

View File

@ -109,7 +109,7 @@ static void ObsTickCallback(void *param, float)
{
ScopeProfiler prof{"obs_websocket_request_batch_frame_tick"};
auto serialFrameBatch = reinterpret_cast<SerialFrameBatch*>(param);
auto serialFrameBatch = static_cast<SerialFrameBatch*>(param);
// Increment frame count
serialFrameBatch->frameCount++;

View File

@ -17,9 +17,13 @@ You should have received a copy of the GNU General Public License along
with this program. If not, see <https://www.gnu.org/licenses/>
*/
#ifdef PLUGIN_TESTS
#include <util/profiler.hpp>
#endif
#include "RequestHandler.h"
const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
const std::unordered_map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{
// General
{"GetVersion", &RequestHandler::GetVersion},
@ -52,9 +56,12 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"GetSourceActive", &RequestHandler::GetSourceActive},
{"GetSourceScreenshot", &RequestHandler::GetSourceScreenshot},
{"SaveSourceScreenshot", &RequestHandler::SaveSourceScreenshot},
{"GetSourcePrivateSettings", &RequestHandler::GetSourcePrivateSettings},
{"SetSourcePrivateSettings", &RequestHandler::SetSourcePrivateSettings},
// Scenes
{"GetSceneList", &RequestHandler::GetSceneList},
{"GetGroupList", &RequestHandler::GetGroupList},
{"GetCurrentProgramScene", &RequestHandler::GetCurrentProgramScene},
{"SetCurrentProgramScene", &RequestHandler::SetCurrentProgramScene},
{"GetCurrentPreviewScene", &RequestHandler::GetCurrentPreviewScene},
@ -66,8 +73,9 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
// Inputs
{"GetInputList", &RequestHandler::GetInputList},
{"GetInputKindList", &RequestHandler::GetInputKindList},
{"GetSpecialInputs", &RequestHandler::GetSpecialInputs},
{"CreateInput", &RequestHandler::CreateInput},
//{"RemoveInput", &RequestHandler::RemoveInput}, // Disabled for now. Pending obs-studio#5276
{"RemoveInput", &RequestHandler::RemoveInput},
{"SetInputName", &RequestHandler::SetInputName},
{"GetInputDefaultSettings", &RequestHandler::GetInputDefaultSettings},
{"GetInputSettings", &RequestHandler::GetInputSettings},
@ -77,10 +85,14 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"ToggleInputMute", &RequestHandler::ToggleInputMute},
{"GetInputVolume", &RequestHandler::GetInputVolume},
{"SetInputVolume", &RequestHandler::SetInputVolume},
{"GetInputAudioBalance", &RequestHandler::GetInputAudioBalance},
{"SetInputAudioBalance", &RequestHandler::SetInputAudioBalance},
{"GetInputAudioSyncOffset", &RequestHandler::GetInputAudioSyncOffset},
{"SetInputAudioSyncOffset", &RequestHandler::SetInputAudioSyncOffset},
{"GetInputAudioMonitorType", &RequestHandler::GetInputAudioMonitorType},
{"SetInputAudioMonitorType", &RequestHandler::SetInputAudioMonitorType},
{"GetInputAudioTracks", &RequestHandler::GetInputAudioTracks},
{"SetInputAudioTracks", &RequestHandler::SetInputAudioTracks},
{"GetInputPropertiesListPropertyItems", &RequestHandler::GetInputPropertiesListPropertyItems},
{"PressInputPropertiesButton", &RequestHandler::PressInputPropertiesButton},
@ -93,6 +105,9 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"SetCurrentSceneTransitionSettings", &RequestHandler::SetCurrentSceneTransitionSettings},
{"TriggerStudioModeTransition", &RequestHandler::TriggerStudioModeTransition},
// Filters
{"GetSourceFilter", &RequestHandler::GetSourceFilter},
// Scene Items
{"GetSceneItemList", &RequestHandler::GetSceneItemList},
{"GetGroupSceneItemList", &RequestHandler::GetGroupSceneItemList},
@ -108,6 +123,14 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"SetSceneItemLocked", &RequestHandler::SetSceneItemLocked},
{"GetSceneItemIndex", &RequestHandler::GetSceneItemIndex},
{"SetSceneItemIndex", &RequestHandler::SetSceneItemIndex},
{"GetSceneItemBlendMode", &RequestHandler::GetSceneItemBlendMode},
{"SetSceneItemBlendMode", &RequestHandler::SetSceneItemBlendMode},
// Outputs
{"GetVirtualCamStatus", &RequestHandler::GetVirtualCamStatus},
{"ToggleVirtualCam", &RequestHandler::ToggleVirtualCam},
{"StartVirtualCam", &RequestHandler::StartVirtualCam},
{"StopVirtualCam", &RequestHandler::StopVirtualCam},
// Stream
{"GetStreamStatus", &RequestHandler::GetStreamStatus},
@ -123,7 +146,7 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
{"ToggleRecordPause", &RequestHandler::ToggleRecordPause},
{"PauseRecord", &RequestHandler::PauseRecord},
{"ResumeRecord", &RequestHandler::ResumeRecord},
//{"GetRecordDirectory", &RequestHandler::GetRecordDirectory},
{"GetRecordDirectory", &RequestHandler::GetRecordDirectory},
// Media Inputs
{"GetMediaInputStatus", &RequestHandler::GetMediaInputStatus},
@ -134,6 +157,9 @@ const std::map<std::string, RequestMethodHandler> RequestHandler::_handlerMap
// Ui
{"GetStudioModeEnabled", &RequestHandler::GetStudioModeEnabled},
{"SetStudioModeEnabled", &RequestHandler::SetStudioModeEnabled},
{"OpenInputPropertiesDialog", &RequestHandler::OpenInputPropertiesDialog},
{"OpenInputFiltersDialog", &RequestHandler::OpenInputFiltersDialog},
{"OpenInputInteractDialog", &RequestHandler::OpenInputInteractDialog},
};
RequestHandler::RequestHandler(SessionPtr session) :
@ -143,6 +169,10 @@ RequestHandler::RequestHandler(SessionPtr session) :
RequestResult RequestHandler::ProcessRequest(const Request& request)
{
#ifdef PLUGIN_TESTS
ScopeProfiler prof{"obs_websocket_request_processing"};
#endif
if (!request.RequestData.is_object() && !request.RequestData.is_null())
return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "Your request data is not an object.");

View File

@ -19,7 +19,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#pragma once
#include <map>
#include <unordered_map>
#include <obs.hpp>
#include <obs-frontend-api.h>
@ -74,9 +74,12 @@ class RequestHandler {
RequestResult GetSourceActive(const Request&);
RequestResult GetSourceScreenshot(const Request&);
RequestResult SaveSourceScreenshot(const Request&);
RequestResult GetSourcePrivateSettings(const Request&);
RequestResult SetSourcePrivateSettings(const Request&);
// Scenes
RequestResult GetSceneList(const Request&);
RequestResult GetGroupList(const Request&);
RequestResult GetCurrentProgramScene(const Request&);
RequestResult SetCurrentProgramScene(const Request&);
RequestResult GetCurrentPreviewScene(const Request&);
@ -88,6 +91,7 @@ class RequestHandler {
// Inputs
RequestResult GetInputList(const Request&);
RequestResult GetInputKindList(const Request&);
RequestResult GetSpecialInputs(const Request&);
RequestResult CreateInput(const Request&);
RequestResult RemoveInput(const Request&);
RequestResult SetInputName(const Request&);
@ -99,10 +103,14 @@ class RequestHandler {
RequestResult ToggleInputMute(const Request&);
RequestResult GetInputVolume(const Request&);
RequestResult SetInputVolume(const Request&);
RequestResult GetInputAudioBalance(const Request&);
RequestResult SetInputAudioBalance(const Request&);
RequestResult GetInputAudioSyncOffset(const Request&);
RequestResult SetInputAudioSyncOffset(const Request&);
RequestResult GetInputAudioMonitorType(const Request&);
RequestResult SetInputAudioMonitorType(const Request&);
RequestResult GetInputAudioTracks(const Request&);
RequestResult SetInputAudioTracks(const Request&);
RequestResult GetInputPropertiesListPropertyItems(const Request&);
RequestResult PressInputPropertiesButton(const Request&);
@ -115,6 +123,9 @@ class RequestHandler {
RequestResult SetCurrentSceneTransitionSettings(const Request&);
RequestResult TriggerStudioModeTransition(const Request&);
// Filters
RequestResult GetSourceFilter(const Request&);
// Scene Items
RequestResult GetSceneItemList(const Request&);
RequestResult GetGroupSceneItemList(const Request&);
@ -130,6 +141,14 @@ class RequestHandler {
RequestResult SetSceneItemLocked(const Request&);
RequestResult GetSceneItemIndex(const Request&);
RequestResult SetSceneItemIndex(const Request&);
RequestResult GetSceneItemBlendMode(const Request&);
RequestResult SetSceneItemBlendMode(const Request&);
// Outputs
RequestResult GetVirtualCamStatus(const Request&);
RequestResult ToggleVirtualCam(const Request&);
RequestResult StartVirtualCam(const Request&);
RequestResult StopVirtualCam(const Request&);
// Stream
RequestResult GetStreamStatus(const Request&);
@ -156,7 +175,10 @@ class RequestHandler {
// Ui
RequestResult GetStudioModeEnabled(const Request&);
RequestResult SetStudioModeEnabled(const Request&);
RequestResult OpenInputPropertiesDialog(const Request&);
RequestResult OpenInputFiltersDialog(const Request&);
RequestResult OpenInputInteractDialog(const Request&);
SessionPtr _session;
static const std::map<std::string, RequestMethodHandler> _handlerMap;
static const std::unordered_map<std::string, RequestMethodHandler> _handlerMap;
};

View File

@ -159,7 +159,7 @@ RequestResult RequestHandler::SetCurrentSceneCollection(const Request& request)
// Avoid queueing tasks if nothing will change
if (currentSceneCollectionName != sceneCollectionName) {
obs_queue_task(OBS_TASK_UI, [](void* param) {
obs_frontend_set_current_scene_collection(reinterpret_cast<const char*>(param));
obs_frontend_set_current_scene_collection(static_cast<const char*>(param));
}, (void*)sceneCollectionName.c_str(), true);
}
@ -193,7 +193,7 @@ RequestResult RequestHandler::CreateSceneCollection(const Request& request)
if (std::find(sceneCollections.begin(), sceneCollections.end(), sceneCollectionName) != sceneCollections.end())
return RequestResult::Error(RequestStatus::ResourceAlreadyExists);
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window());
QMainWindow* mainWindow = static_cast<QMainWindow*>(obs_frontend_get_main_window());
bool success = false;
QMetaObject::invokeMethod(mainWindow, "AddSceneCollection", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, success), Q_ARG(bool, true), Q_ARG(QString, QString::fromStdString(sceneCollectionName)));
if (!success)
@ -252,7 +252,7 @@ RequestResult RequestHandler::SetCurrentProfile(const Request& request)
// Avoid queueing tasks if nothing will change
if (currentProfileName != profileName) {
obs_queue_task(OBS_TASK_UI, [](void* param) {
obs_frontend_set_current_profile(reinterpret_cast<const char*>(param));
obs_frontend_set_current_profile(static_cast<const char*>(param));
}, (void*)profileName.c_str(), true);
}
@ -284,7 +284,7 @@ RequestResult RequestHandler::CreateProfile(const Request& request)
if (std::find(profiles.begin(), profiles.end(), profileName) != profiles.end())
return RequestResult::Error(RequestStatus::ResourceAlreadyExists);
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window());
QMainWindow* mainWindow = static_cast<QMainWindow*>(obs_frontend_get_main_window());
QMetaObject::invokeMethod(mainWindow, "NewProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName)));
return RequestResult::Success();
@ -318,7 +318,7 @@ RequestResult RequestHandler::RemoveProfile(const Request& request)
if (profiles.size() < 2)
return RequestResult::Error(RequestStatus::NotEnoughResources);
QMainWindow* mainWindow = reinterpret_cast<QMainWindow*>(obs_frontend_get_main_window());
QMainWindow* mainWindow = static_cast<QMainWindow*>(obs_frontend_get_main_window());
QMetaObject::invokeMethod(mainWindow, "DeleteProfile", Qt::BlockingQueuedConnection, Q_ARG(QString, QString::fromStdString(profileName)));
return RequestResult::Success();

View File

@ -18,3 +18,40 @@ with this program. If not, see <https://www.gnu.org/licenses/>
*/
#include "RequestHandler.h"
/**
* Gets the info for a specific source filter.
*
* @requestField sourceName | String | Name of the source
* @requestField filterName | String | Name of the filter
*
* @responseField filterEnabled | Boolean | Whether the filter is enabled
* @responseField filterIndex | Number | Index of the filter in the list, beginning at 0
* @responseField filterKind | String | The kind of filter
* @responseField filterSettings | Object | Settings object associated with the filter
*
* @requestType GetSourceFilter
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category filters
*/
RequestResult RequestHandler::GetSourceFilter(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
FilterPair pair = request.ValidateFilter("sourceName", "filterName", statusCode, comment);
if (!pair.filter)
return RequestResult::Error(statusCode, comment);
json responseData;
responseData["filterEnabled"] = obs_source_enabled(pair.filter);
responseData["filterIndex"] = Utils::Obs::NumberHelper::GetSourceFilterIndex(pair.source, pair.filter); // Todo: Use `GetSourceFilterlist` to select this filter maybe
responseData["filterKind"] = obs_source_get_id(pair.filter);
OBSDataAutoRelease filterSettings = obs_source_get_settings(pair.filter);
responseData["filterSettings"] = Utils::Json::ObsDataToJson(filterSettings);
return RequestResult::Success(responseData);
}

View File

@ -83,6 +83,43 @@ RequestResult RequestHandler::GetInputKindList(const Request& request)
return RequestResult::Success(responseData);
}
/**
* Gets the names of all special inputs.
*
* @responseField desktop1 | String | Name of the Desktop Audio input
* @responseField desktop2 | String | Name of the Desktop Audio 2 input
* @responseField mic1 | String | Name of the Mic/Auxiliary Audio input
* @responseField mic2 | String | Name of the Mic/Auxiliary Audio 2 input
* @responseField mic3 | String | Name of the Mic/Auxiliary Audio 3 input
* @responseField mic4 | String | Name of the Mic/Auxiliary Audio 4 input
*
* @requestType GetSpecialInputs
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category inputs
*/
RequestResult RequestHandler::GetSpecialInputs(const Request&)
{
json responseData;
std::vector<std::string> channels = {"desktop1", "desktop2", "mic1", "mic2", "mic3", "mic4"};
size_t channelId = 1;
for (auto &channel : channels) {
OBSSourceAutoRelease input = obs_get_output_source(channelId);
if (!input)
responseData[channel] = nullptr;
else
responseData[channel] = obs_source_get_name(input);
channelId++;
}
return RequestResult::Success(responseData);
}
/**
* Creates a new input, adding it as a scene item to the specified scene.
*
@ -346,6 +383,9 @@ RequestResult RequestHandler::GetInputMute(const Request& request)
if (!input)
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
json responseData;
responseData["inputMuted"] = obs_source_muted(input);
return RequestResult::Success(responseData);
@ -372,6 +412,9 @@ RequestResult RequestHandler::SetInputMute(const Request& request)
if (!(input && request.ValidateBoolean("inputMuted", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
obs_source_set_muted(input, request.RequestData["inputMuted"]);
return RequestResult::Success();
@ -399,6 +442,9 @@ RequestResult RequestHandler::ToggleInputMute(const Request& request)
if (!input)
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
bool inputMuted = !obs_source_muted(input);
obs_source_set_muted(input, inputMuted);
@ -430,6 +476,9 @@ RequestResult RequestHandler::GetInputVolume(const Request& request)
if (!input)
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
float inputVolumeMul = obs_source_get_volume(input);
float inputVolumeDb = obs_mul_to_db(inputVolumeMul);
if (inputVolumeDb == -INFINITY)
@ -446,7 +495,7 @@ RequestResult RequestHandler::GetInputVolume(const Request& request)
*
* @requestField inputName | String | Name of the input to set the volume of
* @requestField ?inputVolumeMul | Number | Volume setting in mul | >= 0, <= 20 | `inputVolumeDb` should be specified
* @requestField ?inputVolumeDb | Number | Volume setting in dB | >= -100, <= -26 | `inputVolumeMul` should be specified
* @requestField ?inputVolumeDb | Number | Volume setting in dB | >= -100, <= 26 | `inputVolumeMul` should be specified
*
* @requestType SetInputVolume
* @complexity 3
@ -463,6 +512,9 @@ RequestResult RequestHandler::SetInputVolume(const Request& request)
if (!input)
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
bool hasMul = request.Contains("inputVolumeMul");
if (hasMul && !request.ValidateOptionalNumber("inputVolumeMul", statusCode, comment, 0, 20))
return RequestResult::Error(statusCode, comment);
@ -488,6 +540,67 @@ RequestResult RequestHandler::SetInputVolume(const Request& request)
return RequestResult::Success();
}
/**
* Gets the audio balance of an input.
*
* @requestField inputName | String | Name of the input to get the audio balance of
*
* @responseField inputAudioBalance | Number | Audio balance value from 0.0-1.0
*
* @requestType GetInputAudioBalance
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category inputs
*/
RequestResult RequestHandler::GetInputAudioBalance(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!input)
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
json responseData;
responseData["inputAudioBalance"] = obs_source_get_balance_value(input);
return RequestResult::Success(responseData);
}
/**
* Sets the audio balance of an input.
*
* @requestField inputName | String | Name of the input to set the audio balance of
* @requestField inputAudioBalance | Number | New audio balance value | >= 0.0, <= 1.0
*
* @requestType SetInputAudioBalance
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category inputs
*/
RequestResult RequestHandler::SetInputAudioBalance(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!(input && request.ValidateNumber("inputAudioBalance", statusCode, comment, 0.0, 1.0)))
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
float inputAudioBalance = request.RequestData["inputAudioBalance"];
obs_source_set_balance_value(input, inputAudioBalance);
return RequestResult::Success();
}
/**
* Gets the audio sync offset of an input.
*
@ -512,6 +625,9 @@ RequestResult RequestHandler::GetInputAudioSyncOffset(const Request& request)
if (!input)
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
json responseData;
// Offset is stored in nanoseconds in OBS.
responseData["inputAudioSyncOffset"] = obs_source_get_sync_offset(input) / 1000000;
@ -540,6 +656,9 @@ RequestResult RequestHandler::SetInputAudioSyncOffset(const Request& request)
if (!(input && request.ValidateNumber("inputAudioSyncOffset", statusCode, comment, -950, 20000)))
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
int64_t syncOffset = request.RequestData["inputAudioSyncOffset"];
obs_source_set_sync_offset(input, syncOffset * 1000000);
@ -573,6 +692,9 @@ RequestResult RequestHandler::GetInputAudioMonitorType(const Request& request)
if (!input)
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
json responseData;
responseData["monitorType"] = Utils::Obs::StringHelper::GetInputMonitorType(input);
@ -600,6 +722,12 @@ RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request)
if (!(input && request.ValidateString("monitorType", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
if (!obs_audio_monitoring_available())
return RequestResult::Error(RequestStatus::InvalidResourceState, "Audio monitoring is not available on this platform.");
enum obs_monitoring_type monitorType;
std::string monitorTypeString = request.RequestData["monitorType"];
if (monitorTypeString == "OBS_MONITORING_TYPE_NONE")
@ -616,6 +744,95 @@ RequestResult RequestHandler::SetInputAudioMonitorType(const Request& request)
return RequestResult::Success();
}
/**
* Gets the enable state of all audio tracks of an input.
*
* @requestField inputName | String | Name of the input
*
* @responseField inputAudioTracks | Object | Object of audio tracks and associated enable states
*
* @requestType GetInputAudioTracks
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category inputs
*/
RequestResult RequestHandler::GetInputAudioTracks(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!input)
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
long long tracks = obs_source_get_audio_mixers(input);
json inputAudioTracks;
for (long long i = 0; i < MAX_AUDIO_MIXES; i++) {
inputAudioTracks[std::to_string(i + 1)] = (bool)((tracks >> i) & 1);
}
json responseData;
responseData["inputAudioTracks"] = inputAudioTracks;
return RequestResult::Success(responseData);
}
/**
* Sets the enable state of audio tracks of an input.
*
* @requestField inputName | String | Name of the input
* @requestField inputAudioTracks | Object | Track settings to apply
*
* @requestType SetInputAudioTracks
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category inputs
*/
RequestResult RequestHandler::SetInputAudioTracks(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!input || !request.ValidateObject("inputAudioTracks", statusCode, comment))
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_AUDIO))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support audio.");
json inputAudioTracks = request.RequestData["inputAudioTracks"];
long long mixers = obs_source_get_audio_mixers(input);
for (long long i = 0; i < MAX_AUDIO_MIXES; i++) {
std::string track = std::to_string(i + 1);
if (!Utils::Json::Contains(inputAudioTracks, track))
continue;
if (!inputAudioTracks[track].is_boolean())
return RequestResult::Error(RequestStatus::InvalidRequestFieldType, "The value of one of your tracks is not a boolean.");
bool enabled = inputAudioTracks[track];
if (enabled)
mixers |= (1 << i);
else
mixers &= ~(1 << i);
}
// Decided that checking if tracks have actually changed is unnecessary
obs_source_set_audio_mixers(input, mixers);
return RequestResult::Success();
}
/**
* Gets the items of a list property from an input's properties.
*

View File

@ -0,0 +1,126 @@
/*
obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.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/>
*/
#include "RequestHandler.h"
static bool VirtualCamAvailable()
{
OBSDataAutoRelease privateData = obs_get_private_data();
if (!privateData)
return false;
return obs_data_get_bool(privateData, "vcamEnabled");
}
/**
* Gets the status of the virtualcam output.
*
* @responseField outputActive | Boolean | Whether the output is active
*
* @requestType GetVirtualCamStatus
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category outputs
* @api requests
*/
RequestResult RequestHandler::GetVirtualCamStatus(const Request&)
{
if (!VirtualCamAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available.");
json responseData;
responseData["outputActive"] = obs_frontend_virtualcam_active();
return RequestResult::Success(responseData);
}
/**
* Toggles the state of the virtualcam output.
*
* @responseField outputActive | Boolean | Whether the output is active
*
* @requestType ToggleVirtualCam
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category outputs
* @api requests
*/
RequestResult RequestHandler::ToggleVirtualCam(const Request&)
{
if (!VirtualCamAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available.");
bool outputActive = obs_frontend_virtualcam_active();
if (outputActive)
obs_frontend_stop_virtualcam();
else
obs_frontend_start_virtualcam();
json responseData;
responseData["outputActive"] = !outputActive;
return RequestResult::Success(responseData);
}
/**
* Starts the virtualcam output.
*
* @requestType StartVirtualCam
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category outputs
*/
RequestResult RequestHandler::StartVirtualCam(const Request&)
{
if (!VirtualCamAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available.");
if (obs_frontend_virtualcam_active())
return RequestResult::Error(RequestStatus::OutputRunning);
obs_frontend_start_virtualcam();
return RequestResult::Success();
}
/**
* Stops the virtualcam output.
*
* @requestType StopVirtualCam
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category outputs
*/
RequestResult RequestHandler::StopVirtualCam(const Request&)
{
if (!VirtualCamAvailable())
return RequestResult::Error(RequestStatus::InvalidResourceState, "VirtualCam is not available.");
if (!obs_frontend_virtualcam_active())
return RequestResult::Error(RequestStatus::OutputNotRunning);
obs_frontend_stop_virtualcam();
return RequestResult::Success();
}

View File

@ -631,3 +631,81 @@ RequestResult RequestHandler::SetSceneItemIndex(const Request& request)
return RequestResult::Success();
}
/**
* Gets the blend mode of a scene item.
*
* Blend modes:
*
* - `OBS_BLEND_NORMAL`
* - `OBS_BLEND_ADDITIVE`
* - `OBS_BLEND_SUBTRACT`
* - `OBS_BLEND_SCREEN`
* - `OBS_BLEND_MULTIPLY`
* - `OBS_BLEND_LIGHTEN`
* - `OBS_BLEND_DARKEN`
*
* Scenes and Groups
*
* @requestField sceneName | String | Name of the scene the item is in
* @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0
*
* @responseField sceneItemBlendMode | String | Current blend mode
*
* @requestType GetSceneItemBlendMode
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::GetSceneItemBlendMode(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!sceneItem)
return RequestResult::Error(statusCode, comment);
auto blendMode = obs_sceneitem_get_blending_mode(sceneItem);
json responseData;
responseData["sceneItemBlendMode"] = Utils::Obs::StringHelper::GetSceneItemBlendMode(blendMode);
return RequestResult::Success(responseData);
}
/**
* Sets the blend mode of a scene item.
*
* Scenes and Groups
*
* @requestField sceneName | String | Name of the scene the item is in
* @requestField sceneItemId | Number | Numeric ID of the scene item | >= 0
* @requestField sceneItemBlendMode | String | New blend mode
*
* @requestType SetSceneItemBlendMode
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scene items
*/
RequestResult RequestHandler::SetSceneItemBlendMode(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSceneItemAutoRelease sceneItem = request.ValidateSceneItem("sceneName", "sceneItemId", statusCode, comment, OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP);
if (!(sceneItem && request.ValidateString("sceneItemBlendMode", statusCode, comment)))
return RequestResult::Error(statusCode, comment);
std::string blendModeString = request.RequestData["sceneItemBlendMode"];
auto blendMode = Utils::Obs::EnumHelper::GetSceneItemBlendMode(blendModeString);
if (blendMode == OBS_BLEND_NORMAL && blendModeString != "OBS_BLEND_NORMAL")
return RequestResult::Error(RequestStatus::InvalidRequestField, "The field sceneItemBlendMode has an invalid value.");
obs_sceneitem_set_blending_mode(sceneItem, blendMode);
return RequestResult::Success();
}

View File

@ -24,7 +24,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
*
* @responseField currentProgramSceneName | String | Current program scene
* @responseField currentPreviewSceneName | String | Current preview scene. `null` if not in studio mode
* @responseField scenes | Array<Object> | Array of scenes in OBS
* @responseField scenes | Array<Object> | Array of scenes
*
* @requestType GetSceneList
* @complexity 2
@ -54,6 +54,29 @@ RequestResult RequestHandler::GetSceneList(const Request&)
return RequestResult::Success(responseData);
}
/**
* Gets an array of all groups in OBS.
*
* Groups in OBS are actually scenes, but renamed and modified. In obs-websocket, we treat them as scenes where we can.
*
* @responseField groups | Array<String> | Array of group names
*
* @requestType GetGroupList
* @complexity 2
* @rpcVersion -1
* @initialVersion 5.0.0
* @api requests
* @category scenes
*/
RequestResult RequestHandler::GetGroupList(const Request&)
{
json responseData;
responseData["groups"] = Utils::Obs::ArrayHelper::GetGroupList();
return RequestResult::Success(responseData);
}
/**
* Gets the current program scene.
*

View File

@ -312,3 +312,39 @@ RequestResult RequestHandler::SaveSourceScreenshot(const Request& request)
return RequestResult::Success();
}
// Intentionally undocumented
RequestResult RequestHandler::GetSourcePrivateSettings(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment);
if (!source)
return RequestResult::Error(statusCode, comment);
OBSDataAutoRelease privateSettings = obs_source_get_private_settings(source);
json responseData;
responseData["sourceSettings"] = Utils::Json::ObsDataToJson(privateSettings);
return RequestResult::Success(responseData);
}
// Intentionally undocumented
RequestResult RequestHandler::SetSourcePrivateSettings(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease source = request.ValidateSource("sourceName", statusCode, comment);
if (!source || !request.ValidateObject("sourceSettings", statusCode, comment))
return RequestResult::Error(statusCode, comment);
OBSDataAutoRelease privateSettings = obs_source_get_private_settings(source);
OBSDataAutoRelease newSettings = Utils::Json::JsonToObsData(request.RequestData["sourceSettings"]);
// Always overlays to prevent destroying internal source data unintentionally
obs_data_apply(privateSettings, newSettings);
return RequestResult::Success();
}

View File

@ -70,3 +70,81 @@ RequestResult RequestHandler::SetStudioModeEnabled(const Request& request)
return RequestResult::Success();
}
/**
* Opens the properties dialog of an input.
*
* @requestField inputName | String | Name of the input to open the dialog of
*
* @requestType OpenInputPropertiesDialog
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category ui
* @api requests
*/
RequestResult RequestHandler::OpenInputPropertiesDialog(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!input)
return RequestResult::Error(statusCode, comment);
obs_frontend_open_source_properties(input);
return RequestResult::Success();
}
/**
* Opens the filters dialog of an input.
*
* @requestField inputName | String | Name of the input to open the dialog of
*
* @requestType OpenInputFiltersDialog
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category ui
* @api requests
*/
RequestResult RequestHandler::OpenInputFiltersDialog(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!input)
return RequestResult::Error(statusCode, comment);
obs_frontend_open_source_filters(input);
return RequestResult::Success();
}
/**
* Opens the interact dialog of an input.
*
* @requestField inputName | String | Name of the input to open the dialog of
*
* @requestType OpenInputInteractDialog
* @complexity 1
* @rpcVersion -1
* @initialVersion 5.0.0
* @category ui
* @api requests
*/
RequestResult RequestHandler::OpenInputInteractDialog(const Request& request)
{
RequestStatus::RequestStatus statusCode;
std::string comment;
OBSSourceAutoRelease input = request.ValidateInput("inputName", statusCode, comment);
if (!input)
return RequestResult::Error(statusCode, comment);
if (!(obs_source_get_output_flags(input) & OBS_SOURCE_INTERACTION))
return RequestResult::Error(RequestStatus::InvalidResourceState, "The specified input does not support interaction.");
obs_frontend_open_source_interaction(input);
return RequestResult::Success();
}

View File

@ -295,6 +295,27 @@ obs_source_t *Request::ValidateInput(const std::string &keyName, RequestStatus::
return ret;
}
FilterPair Request::ValidateFilter(const std::string &sourceKeyName, const std::string &filterKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const
{
obs_source_t *source = ValidateSource(sourceKeyName, statusCode, comment);
if (!source)
return FilterPair{source, nullptr};
if (!ValidateString(filterKeyName, statusCode, comment))
return FilterPair{source, nullptr};
std::string filterName = RequestData[filterKeyName];
obs_source_t *filter = obs_source_get_filter_by_name(source, filterName.c_str());
if (!filter) {
statusCode = RequestStatus::ResourceNotFound;
comment = std::string("No filter was found in the source `") + RequestData[sourceKeyName].get<std::string>() + "` with the name `" + filterName + "`.";
return FilterPair{source, nullptr};
}
return FilterPair{source, filter};
}
obs_sceneitem_t *Request::ValidateSceneItem(const std::string &sceneKeyName, const std::string &sceneItemIdKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter) const
{
OBSSceneAutoRelease scene = ValidateScene2(sceneKeyName, statusCode, comment, filter);

View File

@ -29,6 +29,12 @@ enum ObsWebSocketSceneFilter {
OBS_WEBSOCKET_SCENE_FILTER_SCENE_OR_GROUP,
};
// We return filters as a pair because `obs_filter_get_parent()` is apparently volatile
struct FilterPair {
OBSSourceAutoRelease source;
OBSSourceAutoRelease filter;
};
struct Request
{
Request(const std::string &requestType, const json &requestData = nullptr, const RequestBatchExecutionType::RequestBatchExecutionType executionType = RequestBatchExecutionType::None);
@ -53,6 +59,7 @@ struct Request
obs_source_t *ValidateScene(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
obs_scene_t *ValidateScene2(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
obs_source_t *ValidateInput(const std::string &keyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const;
FilterPair ValidateFilter(const std::string &sourceKeyName, const std::string &filterKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment) const;
obs_sceneitem_t *ValidateSceneItem(const std::string &sceneKeyName, const std::string &sceneItemIdKeyName, RequestStatus::RequestStatus &statusCode, std::string &comment, const ObsWebSocketSceneFilter filter = OBS_WEBSOCKET_SCENE_FILTER_SCENE_ONLY) const;
std::string RequestType;

View File

@ -21,7 +21,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include "Platform.h"
#include "../plugin-macros.generated.h"
bool Utils::Json::JsonArrayIsValidObsArray(json j)
bool Utils::Json::JsonArrayIsValidObsArray(const json &j)
{
for (auto it : j) {
if (!it.is_object())
@ -191,7 +191,7 @@ bool Utils::Json::GetJsonFileContent(std::string fileName, json &content)
return true;
}
bool Utils::Json::SetJsonFileContent(std::string fileName, json content, bool createNew)
bool Utils::Json::SetJsonFileContent(std::string fileName, const json &content, bool createNew)
{
std::string textContent = content.dump(2);
return Utils::Platform::SetTextFileContent(fileName, textContent, createNew);

View File

@ -27,10 +27,11 @@ using json = nlohmann::json;
namespace Utils {
namespace Json {
bool JsonArrayIsValidObsArray(json j);
bool JsonArrayIsValidObsArray(const json &j);
obs_data_t *JsonToObsData(json j);
json ObsDataToJson(obs_data_t *d, bool includeDefault = false);
bool GetJsonFileContent(std::string fileName, json &content);
bool SetJsonFileContent(std::string fileName, json content, bool createNew = true);
bool SetJsonFileContent(std::string fileName, const json &content, bool createNew = true);
static inline bool Contains(const json &j, std::string key) { return j.contains(key) && !j[key].is_null(); }
}
}

View File

@ -62,7 +62,7 @@ using OBSWeakServiceAutoRelease = OBSRef<obs_weak_service_t *, ___weak_service_d
template <typename T> T* GetCalldataPointer(const calldata_t *data, const char* name) {
void *ptr = nullptr;
calldata_get_ptr(data, name, &ptr);
return reinterpret_cast<T*>(ptr);
return static_cast<T*>(ptr);
}
enum ObsOutputState {
@ -163,6 +163,7 @@ namespace Utils {
std::string GetMediaInputState(obs_source_t *input);
std::string GetLastReplayBufferFilePath();
std::string GetSceneItemBoundsType(enum obs_bounds_type type);
std::string GetSceneItemBlendMode(enum obs_blending_type mode);
std::string DurationToTimecode(uint64_t);
std::string GetOutputState(ObsOutputState state);
}
@ -170,11 +171,13 @@ namespace Utils {
namespace EnumHelper {
enum obs_bounds_type GetSceneItemBoundsType(std::string boundsType);
enum ObsMediaInputAction GetMediaInputAction(std::string mediaAction);
enum obs_blending_type GetSceneItemBlendMode(std::string mode);
}
namespace NumberHelper {
uint64_t GetOutputDuration(obs_output_t *output);
size_t GetSceneCount();
size_t GetSourceFilterIndex(obs_source_t *source, obs_source_t *filter);
}
namespace ArrayHelper {
@ -183,6 +186,7 @@ namespace Utils {
std::vector<obs_hotkey_t *> GetHotkeyList();
std::vector<std::string> GetHotkeyNameList();
std::vector<json> GetSceneList();
std::vector<std::string> GetGroupList();
std::vector<json> GetSceneItemList(obs_scene_t *scene, bool basic = false);
std::vector<json> GetInputList(std::string inputKind = "");
std::vector<std::string> GetInputKindList(bool unversioned = false, bool includeDisabled = false);

View File

@ -29,7 +29,7 @@ struct CreateSceneItemData {
void CreateSceneItemHelper(void *_data, obs_scene_t *scene)
{
auto *data = reinterpret_cast<CreateSceneItemData*>(_data);
auto *data = static_cast<CreateSceneItemData*>(_data);
data->sceneItem = obs_scene_add(scene, data->source);
if (data->sceneItemTransform)

View File

@ -61,7 +61,7 @@ std::vector<obs_hotkey_t *> Utils::Obs::ArrayHelper::GetHotkeyList()
std::vector<obs_hotkey_t *> ret;
obs_enum_hotkeys([](void* data, obs_hotkey_id, obs_hotkey_t* hotkey) {
auto ret = reinterpret_cast<std::vector<obs_hotkey_t *> *>(data);
auto ret = static_cast<std::vector<obs_hotkey_t *> *>(data);
ret->push_back(hotkey);
@ -91,9 +91,6 @@ std::vector<json> Utils::Obs::ArrayHelper::GetSceneList()
for (size_t i = 0; i < sceneList.sources.num; i++) {
obs_source_t *scene = sceneList.sources.array[i];
if (obs_source_is_group(scene))
continue;
json sceneJson;
sceneJson["sceneName"] = obs_source_get_name(scene);
sceneJson["sceneIndex"] = sceneList.sources.num - i - 1;
@ -109,13 +106,33 @@ std::vector<json> Utils::Obs::ArrayHelper::GetSceneList()
return ret;
}
std::vector<std::string> Utils::Obs::ArrayHelper::GetGroupList()
{
std::vector<std::string> ret;
auto cb = [](void *priv_data, obs_source_t *scene) {
auto ret = static_cast<std::vector<std::string>*>(priv_data);
if (!obs_source_is_group(scene))
return true;
ret->emplace_back(obs_source_get_name(scene));
return true;
};
obs_enum_scenes(cb, &ret);
return ret;
}
std::vector<json> Utils::Obs::ArrayHelper::GetSceneItemList(obs_scene_t *scene, bool basic)
{
std::pair<std::vector<json>, bool> enumData;
enumData.second = basic;
obs_scene_enum_items(scene, [](obs_scene_t*, obs_sceneitem_t* sceneItem, void* param) {
auto enumData = reinterpret_cast<std::pair<std::vector<json>, bool>*>(param);
auto enumData = static_cast<std::pair<std::vector<json>, bool>*>(param);
json item;
item["sceneItemId"] = obs_sceneitem_get_id(sceneItem);
@ -158,7 +175,7 @@ std::vector<json> Utils::Obs::ArrayHelper::GetInputList(std::string inputKind)
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
return true;
auto inputInfo = reinterpret_cast<EnumInputInfo*>(param);
auto inputInfo = static_cast<EnumInputInfo*>(param);
std::string inputKind = obs_source_get_id(input);

View File

@ -23,25 +23,38 @@ with this program. If not, see <https://www.gnu.org/licenses/>
enum obs_bounds_type Utils::Obs::EnumHelper::GetSceneItemBoundsType(std::string boundsType)
{
RET_COMPARE(boundsType, OBS_BOUNDS_NONE);
RET_COMPARE(boundsType, OBS_BOUNDS_STRETCH);
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_INNER);
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_OUTER);
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_TO_WIDTH);
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_TO_HEIGHT);
RET_COMPARE(boundsType, OBS_BOUNDS_MAX_ONLY);
RET_COMPARE(boundsType, OBS_BOUNDS_NONE)
RET_COMPARE(boundsType, OBS_BOUNDS_STRETCH)
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_INNER)
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_OUTER)
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_TO_WIDTH)
RET_COMPARE(boundsType, OBS_BOUNDS_SCALE_TO_HEIGHT)
RET_COMPARE(boundsType, OBS_BOUNDS_MAX_ONLY)
return OBS_BOUNDS_NONE;
}
enum ObsMediaInputAction Utils::Obs::EnumHelper::GetMediaInputAction(std::string mediaAction)
{
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY);
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE);
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP);
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART);
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT);
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS);
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PLAY)
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PAUSE)
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_STOP)
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_RESTART)
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NEXT)
RET_COMPARE(mediaAction, OBS_WEBSOCKET_MEDIA_INPUT_ACTION_PREVIOUS)
return OBS_WEBSOCKET_MEDIA_INPUT_ACTION_NONE;
}
enum obs_blending_type Utils::Obs::EnumHelper::GetSceneItemBlendMode(std::string mode)
{
RET_COMPARE(mode, OBS_BLEND_NORMAL)
RET_COMPARE(mode, OBS_BLEND_ADDITIVE)
RET_COMPARE(mode, OBS_BLEND_SUBTRACT)
RET_COMPARE(mode, OBS_BLEND_SCREEN)
RET_COMPARE(mode, OBS_BLEND_MULTIPLY)
RET_COMPARE(mode, OBS_BLEND_LIGHTEN)
RET_COMPARE(mode, OBS_BLEND_DARKEN)
return OBS_BLEND_NORMAL;
}

View File

@ -39,7 +39,7 @@ size_t Utils::Obs::NumberHelper::GetSceneCount()
{
size_t ret;
auto sceneEnumProc = [](void *param, obs_source_t *scene) {
auto ret = reinterpret_cast<size_t*>(param);
auto ret = static_cast<size_t*>(param);
if (obs_source_is_group(scene))
return true;
@ -52,3 +52,28 @@ size_t Utils::Obs::NumberHelper::GetSceneCount()
return ret;
}
size_t Utils::Obs::NumberHelper::GetSourceFilterIndex(obs_source_t *source, obs_source_t *filter)
{
struct FilterSearch {
obs_source_t *filter;
bool found;
size_t index;
};
auto search = [](obs_source_t *, obs_source_t *filter, void *priv_data) {
auto filterSearch = static_cast<FilterSearch*>(priv_data);
if (filter == filterSearch->filter)
filterSearch->found = true;
if (!filterSearch->found)
filterSearch->index++;
};
FilterSearch filterSearch = {filter, 0, 0};
obs_source_enum_filters(source, search, &filterSearch);
return filterSearch.index;
}

View File

@ -64,12 +64,10 @@ std::string Utils::Obs::StringHelper::GetCurrentProfilePath()
std::string Utils::Obs::StringHelper::GetCurrentRecordOutputPath()
{
//char *recordOutputPath = obs_frontend_get_current_record_output_path();
//std::string ret = recordOutputPath;
//bfree(recordOutputPath);
//return ret;
return "";
char *recordOutputPath = obs_frontend_get_current_record_output_path();
std::string ret = recordOutputPath;
bfree(recordOutputPath);
return ret;
}
std::string Utils::Obs::StringHelper::GetSourceType(obs_source_t *source)
@ -144,6 +142,20 @@ std::string Utils::Obs::StringHelper::GetSceneItemBoundsType(enum obs_bounds_typ
}
}
std::string Utils::Obs::StringHelper::GetSceneItemBlendMode(enum obs_blending_type mode)
{
switch (mode) {
default:
CASE(OBS_BLEND_NORMAL)
CASE(OBS_BLEND_ADDITIVE)
CASE(OBS_BLEND_SUBTRACT)
CASE(OBS_BLEND_SCREEN)
CASE(OBS_BLEND_MULTIPLY)
CASE(OBS_BLEND_LIGHTEN)
CASE(OBS_BLEND_DARKEN)
}
}
std::string Utils::Obs::StringHelper::DurationToTimecode(uint64_t ms)
{
uint64_t secs = ms / 1000ULL;

View File

@ -1,354 +1,353 @@
/*
obs-websocket
Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de>
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.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/>
*/
#include <cmath>
#include <algorithm>
#include "Obs.h"
#include "Obs_VolumeMeter.h"
#include "Obs_VolumeMeter_Helpers.h"
#include "../obs-websocket.h"
Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input) :
PeakMeterType(SAMPLE_PEAK_METER),
_input(obs_source_get_weak_source(input)),
_channels(0),
_lastUpdate(0),
_volume(obs_source_get_volume(input))
{
signal_handler_t *sh = obs_source_get_signal_handler(input);
signal_handler_connect(sh, "volume", Meter::InputVolumeCallback, this);
obs_source_add_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this);
blog_debug("[Utils::Obs::VolumeMeter::Meter::Meter] Meter created for input: %s", obs_source_get_name(input));
}
Utils::Obs::VolumeMeter::Meter::~Meter()
{
OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
if (!input) {
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::~Meter] Failed to get strong reference to input. Has it been destroyed?");
return;
}
signal_handler_t *sh = obs_source_get_signal_handler(input);
signal_handler_disconnect(sh, "volume", Meter::InputVolumeCallback, this);
obs_source_remove_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this);
blog_debug("[Utils::Obs::VolumeMeter::Meter::~Meter] Meter destroyed for input: %s", obs_source_get_name(input));
}
bool Utils::Obs::VolumeMeter::Meter::InputValid()
{
// return !obs_weak_source_expired(_input);
return true;
}
json Utils::Obs::VolumeMeter::Meter::GetMeterData()
{
json ret;
OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
if (!input) {
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::GetMeterData] Failed to get strong reference to input. Has it been destroyed?");
return ret;
}
std::vector<std::vector<float>> levels;
const float volume = _muted ? 0.0f : _volume.load();
std::unique_lock<std::mutex> l(_mutex);
if (_lastUpdate != 0 && (os_gettime_ns() - _lastUpdate) * 0.000000001 > 0.3)
ResetAudioLevels();
for (int channel = 0; channel < _channels; channel++) {
std::vector<float> level;
level.push_back(_magnitude[channel] * volume);
level.push_back(_peak[channel] * volume);
level.push_back(_peak[channel]);
levels.push_back(level);
}
l.unlock();
ret["inputName"] = obs_source_get_name(input);
ret["inputLevelsMul"] = levels;
return ret;
}
// MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ResetAudioLevels()
{
_lastUpdate = 0;
for (int channelNumber = 0; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++) {
_magnitude[channelNumber] = 0;
_peak[channelNumber] = 0;
}
}
// MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ProcessAudioChannels(const struct audio_data *data)
{
int channels = 0;
for (int i = 0; i < MAX_AV_PLANES; i++) {
if (data->data[i])
channels++;
}
bool channelsChanged = _channels != channels;
_channels = std::clamp(channels, 0, MAX_AUDIO_CHANNELS);
if (channelsChanged)
ResetAudioLevels();
}
// MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data)
{
size_t sampleCount = data->frames;
int channelNumber = 0;
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
float *samples = (float*)data->data[planeNumber];
if (!samples)
continue;
if (((uintptr_t)samples & 0xf) > 0) {
_peak[channelNumber] = 1.0f;
channelNumber++;
continue;
}
__m128 previousSamples = _mm_loadu_ps(_previousSamples[channelNumber]);
float peak;
switch (PeakMeterType) {
default:
case SAMPLE_PEAK_METER:
peak = GetSamplePeak(previousSamples, samples, sampleCount);
break;
case TRUE_PEAK_METER:
peak = GetTruePeak(previousSamples, samples, sampleCount);
break;
}
switch (sampleCount) {
case 0:
break;
case 1:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][1];
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][2];
_previousSamples[channelNumber][2] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
break;
case 2:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][2];
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
break;
case 3:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][1] = samples[sampleCount - 3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
break;
default:
_previousSamples[channelNumber][0] = samples[sampleCount - 4];
_previousSamples[channelNumber][1] = samples[sampleCount - 3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
}
_peak[channelNumber] = peak;
channelNumber++;
}
for (; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++)
_peak[channelNumber] = 0.0;
}
// MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(const struct audio_data *data)
{
size_t sampleCount = data->frames;
int channelNumber = 0;
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
float *samples = (float*)data->data[planeNumber];
if (!samples)
continue;
float sum = 0.0;
for (size_t i = 0; i < sampleCount; i++) {
float sample = samples[i];
sum += sample * sample;
}
_magnitude[channelNumber] = std::sqrt(sum / sampleCount);
channelNumber++;
}
}
void Utils::Obs::VolumeMeter::Meter::InputAudioCaptureCallback(void *priv_data, obs_source_t *, const struct audio_data *data, bool muted)
{
auto c = static_cast<Meter*>(priv_data);
std::unique_lock<std::mutex> l(c->_mutex);
c->_muted = muted;
c->ProcessAudioChannels(data);
c->ProcessPeak(data);
c->ProcessMagnitude(data);
c->_lastUpdate = os_gettime_ns();
}
void Utils::Obs::VolumeMeter::Meter::InputVolumeCallback(void *priv_data, calldata_t *cd)
{
auto c = static_cast<Meter*>(priv_data);
c->_volume = (float)calldata_float(cd, "volume");
}
Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod) :
_updateCallback(cb),
_updatePeriod(updatePeriod),
_running(false)
{
signal_handler_t *sh = obs_get_signal_handler();
if (!sh)
return;
auto enumProc = [](void *priv_data, obs_source_t *input) {
auto c = static_cast<Handler*>(priv_data);
if (!obs_source_active(input))
return true;
uint32_t flags = obs_source_get_output_flags(input);
if ((flags & OBS_SOURCE_AUDIO) == 0)
return true;
c->_meters.emplace_back(std::move(new Meter(input)));
return true;
};
obs_enum_sources(enumProc, this);
signal_handler_connect(sh, "source_activate", Handler::InputActivateCallback, this);
signal_handler_connect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
_running = true;
_updateThread = std::thread(&Handler::UpdateThread, this);
blog_debug("[Utils::Obs::VolumeMeter::Handler::Handler] Handler created.");
}
Utils::Obs::VolumeMeter::Handler::~Handler()
{
signal_handler_t *sh = obs_get_signal_handler();
if (!sh)
return;
signal_handler_disconnect(sh, "source_activate", Handler::InputActivateCallback, this);
signal_handler_disconnect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
if (_running) {
_running = false;
_cond.notify_all();
}
if (_updateThread.joinable())
_updateThread.join();
blog_debug("[Utils::Obs::VolumeMeter::Handler::~Handler] Handler destroyed.");
}
void Utils::Obs::VolumeMeter::Handler::UpdateThread()
{
blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread started.");
while (_running) {
{
std::unique_lock<std::mutex> l(_mutex);
if (_cond.wait_for(l, std::chrono::milliseconds(_updatePeriod), [this]{ return !_running; }))
break;
}
std::vector<json> inputs;
std::unique_lock<std::mutex> l(_meterMutex);
for (auto &meter : _meters) {
if (meter->InputValid())
inputs.push_back(meter->GetMeterData());
}
l.unlock();
if (_updateCallback)
_updateCallback(inputs);
}
blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread stopped.");
}
void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data, calldata_t *cd)
{
auto c = static_cast<Handler*>(priv_data);
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
if (!input)
return;
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
return;
uint32_t flags = obs_source_get_output_flags(input);
if ((flags & OBS_SOURCE_AUDIO) == 0)
return;
std::unique_lock<std::mutex> l(c->_meterMutex);
c->_meters.emplace_back(std::move(new Meter(input)));
}
void Utils::Obs::VolumeMeter::Handler::InputDeactivateCallback(void *priv_data, calldata_t *cd)
{
auto c = static_cast<Handler*>(priv_data);
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
if (!input)
return;
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
return;
// Don't ask me why, but using std::remove_if segfaults trying this.
std::unique_lock<std::mutex> l(c->_meterMutex);
std::vector<MeterPtr>::iterator iter;
for (iter = c->_meters.begin(); iter != c->_meters.end();) {
if (obs_weak_source_references_source(iter->get()->GetWeakInput(), input))
iter = c->_meters.erase(iter);
else
++iter;
}
}
/*
obs-websocket
Copyright (C) 2014 by Leonhard Oelke <leonhard@in-verted.de>
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.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/>
*/
#include <cmath>
#include <algorithm>
#include "Obs.h"
#include "Obs_VolumeMeter.h"
#include "Obs_VolumeMeter_Helpers.h"
#include "../obs-websocket.h"
Utils::Obs::VolumeMeter::Meter::Meter(obs_source_t *input) :
PeakMeterType(SAMPLE_PEAK_METER),
_input(obs_source_get_weak_source(input)),
_channels(0),
_lastUpdate(0),
_volume(obs_source_get_volume(input))
{
signal_handler_t *sh = obs_source_get_signal_handler(input);
signal_handler_connect(sh, "volume", Meter::InputVolumeCallback, this);
obs_source_add_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this);
blog_debug("[Utils::Obs::VolumeMeter::Meter::Meter] Meter created for input: %s", obs_source_get_name(input));
}
Utils::Obs::VolumeMeter::Meter::~Meter()
{
OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
if (!input) {
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::~Meter] Failed to get strong reference to input. Has it been destroyed?");
return;
}
signal_handler_t *sh = obs_source_get_signal_handler(input);
signal_handler_disconnect(sh, "volume", Meter::InputVolumeCallback, this);
obs_source_remove_audio_capture_callback(input, Meter::InputAudioCaptureCallback, this);
blog_debug("[Utils::Obs::VolumeMeter::Meter::~Meter] Meter destroyed for input: %s", obs_source_get_name(input));
}
bool Utils::Obs::VolumeMeter::Meter::InputValid()
{
return !obs_weak_source_expired(_input);
}
json Utils::Obs::VolumeMeter::Meter::GetMeterData()
{
json ret;
OBSSourceAutoRelease input = obs_weak_source_get_source(_input);
if (!input) {
blog(LOG_WARNING, "[Utils::Obs::VolumeMeter::Meter::GetMeterData] Failed to get strong reference to input. Has it been destroyed?");
return ret;
}
std::vector<std::vector<float>> levels;
const float volume = _muted ? 0.0f : _volume.load();
std::unique_lock<std::mutex> l(_mutex);
if (_lastUpdate != 0 && (os_gettime_ns() - _lastUpdate) * 0.000000001 > 0.3)
ResetAudioLevels();
for (int channel = 0; channel < _channels; channel++) {
std::vector<float> level;
level.push_back(_magnitude[channel] * volume);
level.push_back(_peak[channel] * volume);
level.push_back(_peak[channel]);
levels.push_back(level);
}
l.unlock();
ret["inputName"] = obs_source_get_name(input);
ret["inputLevelsMul"] = levels;
return ret;
}
// MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ResetAudioLevels()
{
_lastUpdate = 0;
for (int channelNumber = 0; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++) {
_magnitude[channelNumber] = 0;
_peak[channelNumber] = 0;
}
}
// MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ProcessAudioChannels(const struct audio_data *data)
{
int channels = 0;
for (int i = 0; i < MAX_AV_PLANES; i++) {
if (data->data[i])
channels++;
}
bool channelsChanged = _channels != channels;
_channels = std::clamp(channels, 0, MAX_AUDIO_CHANNELS);
if (channelsChanged)
ResetAudioLevels();
}
// MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ProcessPeak(const struct audio_data *data)
{
size_t sampleCount = data->frames;
int channelNumber = 0;
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
float *samples = (float*)data->data[planeNumber];
if (!samples)
continue;
if (((uintptr_t)samples & 0xf) > 0) {
_peak[channelNumber] = 1.0f;
channelNumber++;
continue;
}
__m128 previousSamples = _mm_loadu_ps(_previousSamples[channelNumber]);
float peak;
switch (PeakMeterType) {
default:
case SAMPLE_PEAK_METER:
peak = GetSamplePeak(previousSamples, samples, sampleCount);
break;
case TRUE_PEAK_METER:
peak = GetTruePeak(previousSamples, samples, sampleCount);
break;
}
switch (sampleCount) {
case 0:
break;
case 1:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][1];
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][2];
_previousSamples[channelNumber][2] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
break;
case 2:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][2];
_previousSamples[channelNumber][1] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
break;
case 3:
_previousSamples[channelNumber][0] = _previousSamples[channelNumber][3];
_previousSamples[channelNumber][1] = samples[sampleCount - 3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
break;
default:
_previousSamples[channelNumber][0] = samples[sampleCount - 4];
_previousSamples[channelNumber][1] = samples[sampleCount - 3];
_previousSamples[channelNumber][2] = samples[sampleCount - 2];
_previousSamples[channelNumber][3] = samples[sampleCount - 1];
}
_peak[channelNumber] = peak;
channelNumber++;
}
for (; channelNumber < MAX_AUDIO_CHANNELS; channelNumber++)
_peak[channelNumber] = 0.0;
}
// MUST HOLD LOCK
void Utils::Obs::VolumeMeter::Meter::ProcessMagnitude(const struct audio_data *data)
{
size_t sampleCount = data->frames;
int channelNumber = 0;
for (int planeNumber = 0; channelNumber < _channels; planeNumber++) {
float *samples = (float*)data->data[planeNumber];
if (!samples)
continue;
float sum = 0.0;
for (size_t i = 0; i < sampleCount; i++) {
float sample = samples[i];
sum += sample * sample;
}
_magnitude[channelNumber] = std::sqrt(sum / sampleCount);
channelNumber++;
}
}
void Utils::Obs::VolumeMeter::Meter::InputAudioCaptureCallback(void *priv_data, obs_source_t *, const struct audio_data *data, bool muted)
{
auto c = static_cast<Meter*>(priv_data);
std::unique_lock<std::mutex> l(c->_mutex);
c->_muted = muted;
c->ProcessAudioChannels(data);
c->ProcessPeak(data);
c->ProcessMagnitude(data);
c->_lastUpdate = os_gettime_ns();
}
void Utils::Obs::VolumeMeter::Meter::InputVolumeCallback(void *priv_data, calldata_t *cd)
{
auto c = static_cast<Meter*>(priv_data);
c->_volume = (float)calldata_float(cd, "volume");
}
Utils::Obs::VolumeMeter::Handler::Handler(UpdateCallback cb, uint64_t updatePeriod) :
_updateCallback(cb),
_updatePeriod(updatePeriod),
_running(false)
{
signal_handler_t *sh = obs_get_signal_handler();
if (!sh)
return;
auto enumProc = [](void *priv_data, obs_source_t *input) {
auto c = static_cast<Handler*>(priv_data);
if (!obs_source_active(input))
return true;
uint32_t flags = obs_source_get_output_flags(input);
if ((flags & OBS_SOURCE_AUDIO) == 0)
return true;
c->_meters.emplace_back(std::move(new Meter(input)));
return true;
};
obs_enum_sources(enumProc, this);
signal_handler_connect(sh, "source_activate", Handler::InputActivateCallback, this);
signal_handler_connect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
_running = true;
_updateThread = std::thread(&Handler::UpdateThread, this);
blog_debug("[Utils::Obs::VolumeMeter::Handler::Handler] Handler created.");
}
Utils::Obs::VolumeMeter::Handler::~Handler()
{
signal_handler_t *sh = obs_get_signal_handler();
if (!sh)
return;
signal_handler_disconnect(sh, "source_activate", Handler::InputActivateCallback, this);
signal_handler_disconnect(sh, "source_deactivate", Handler::InputDeactivateCallback, this);
if (_running) {
_running = false;
_cond.notify_all();
}
if (_updateThread.joinable())
_updateThread.join();
blog_debug("[Utils::Obs::VolumeMeter::Handler::~Handler] Handler destroyed.");
}
void Utils::Obs::VolumeMeter::Handler::UpdateThread()
{
blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread started.");
while (_running) {
{
std::unique_lock<std::mutex> l(_mutex);
if (_cond.wait_for(l, std::chrono::milliseconds(_updatePeriod), [this]{ return !_running; }))
break;
}
std::vector<json> inputs;
std::unique_lock<std::mutex> l(_meterMutex);
for (auto &meter : _meters) {
if (meter->InputValid())
inputs.push_back(meter->GetMeterData());
}
l.unlock();
if (_updateCallback)
_updateCallback(inputs);
}
blog_debug("[Utils::Obs::VolumeMeter::Handler::UpdateThread] Thread stopped.");
}
void Utils::Obs::VolumeMeter::Handler::InputActivateCallback(void *priv_data, calldata_t *cd)
{
auto c = static_cast<Handler*>(priv_data);
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
if (!input)
return;
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
return;
uint32_t flags = obs_source_get_output_flags(input);
if ((flags & OBS_SOURCE_AUDIO) == 0)
return;
std::unique_lock<std::mutex> l(c->_meterMutex);
c->_meters.emplace_back(std::move(new Meter(input)));
}
void Utils::Obs::VolumeMeter::Handler::InputDeactivateCallback(void *priv_data, calldata_t *cd)
{
auto c = static_cast<Handler*>(priv_data);
obs_source_t *input = GetCalldataPointer<obs_source_t>(cd, "source");
if (!input)
return;
if (obs_source_get_type(input) != OBS_SOURCE_TYPE_INPUT)
return;
// Don't ask me why, but using std::remove_if segfaults trying this.
std::unique_lock<std::mutex> l(c->_meterMutex);
std::vector<MeterPtr>::iterator iter;
for (iter = c->_meters.begin(); iter != c->_meters.end();) {
if (obs_weak_source_references_source(iter->get()->GetWeakInput(), input))
iter = c->_meters.erase(iter);
else
++iter;
}
}

View File

@ -1,99 +1,99 @@
/*
obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.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 <string>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <memory>
#include <thread>
#include <obs.hpp>
#include "Obs.h"
#include "Json.h"
namespace Utils {
namespace Obs {
namespace VolumeMeter {
// Some code copied from https://github.com/obsproject/obs-studio/blob/master/libobs/obs-audio-controls.c
// Keeps a running tally of the current audio levels, for a specific input
class Meter {
public:
Meter(obs_source_t *input);
~Meter();
bool InputValid();
obs_weak_source_t *GetWeakInput() { return _input; }
json GetMeterData();
std::atomic<enum obs_peak_meter_type> PeakMeterType;
private:
OBSWeakSourceAutoRelease _input;
// All values in mul
std::mutex _mutex;
bool _muted;
int _channels;
float _magnitude[MAX_AUDIO_CHANNELS];
float _peak[MAX_AUDIO_CHANNELS];
float _previousSamples[MAX_AUDIO_CHANNELS][4];
std::atomic<uint64_t> _lastUpdate;
std::atomic<float> _volume;
void ResetAudioLevels();
void ProcessAudioChannels(const struct audio_data *data);
void ProcessPeak(const struct audio_data *data);
void ProcessMagnitude(const struct audio_data *data);
static void InputAudioCaptureCallback(void *priv_data, obs_source_t *source, const struct audio_data *data, bool muted);
static void InputVolumeCallback(void *priv_data, calldata_t *cd);
};
// Maintains an array of active inputs
class Handler {
typedef std::function<void(std::vector<json>)> UpdateCallback;
typedef std::unique_ptr<Meter> MeterPtr;
public:
Handler(UpdateCallback cb, uint64_t updatePeriod = 50);
~Handler();
private:
UpdateCallback _updateCallback;
std::mutex _meterMutex;
std::vector<MeterPtr> _meters;
uint64_t _updatePeriod;
std::mutex _mutex;
std::condition_variable _cond;
std::atomic<bool> _running;
std::thread _updateThread;
void UpdateThread();
static void InputActivateCallback(void *priv_data, calldata_t *cd);
static void InputDeactivateCallback(void *priv_data, calldata_t *cd);
};
}
}
}
/*
obs-websocket
Copyright (C) 2016-2021 Stephane Lepin <stephane.lepin@gmail.com>
Copyright (C) 2020-2021 Kyle Manning <tt2468@gmail.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 <string>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <memory>
#include <thread>
#include <obs.hpp>
#include "Obs.h"
#include "Json.h"
namespace Utils {
namespace Obs {
namespace VolumeMeter {
// Some code copied from https://github.com/obsproject/obs-studio/blob/master/libobs/obs-audio-controls.c
// Keeps a running tally of the current audio levels, for a specific input
class Meter {
public:
Meter(obs_source_t *input);
~Meter();
bool InputValid();
obs_weak_source_t *GetWeakInput() { return _input; }
json GetMeterData();
std::atomic<enum obs_peak_meter_type> PeakMeterType;
private:
OBSWeakSourceAutoRelease _input;
// All values in mul
std::mutex _mutex;
bool _muted;
int _channels;
float _magnitude[MAX_AUDIO_CHANNELS];
float _peak[MAX_AUDIO_CHANNELS];
float _previousSamples[MAX_AUDIO_CHANNELS][4];
std::atomic<uint64_t> _lastUpdate;
std::atomic<float> _volume;
void ResetAudioLevels();
void ProcessAudioChannels(const struct audio_data *data);
void ProcessPeak(const struct audio_data *data);
void ProcessMagnitude(const struct audio_data *data);
static void InputAudioCaptureCallback(void *priv_data, obs_source_t *source, const struct audio_data *data, bool muted);
static void InputVolumeCallback(void *priv_data, calldata_t *cd);
};
// Maintains an array of active inputs
class Handler {
typedef std::function<void(std::vector<json>)> UpdateCallback;
typedef std::unique_ptr<Meter> MeterPtr;
public:
Handler(UpdateCallback cb, uint64_t updatePeriod = 50);
~Handler();
private:
UpdateCallback _updateCallback;
std::mutex _meterMutex;
std::vector<MeterPtr> _meters;
uint64_t _updatePeriod;
std::mutex _mutex;
std::condition_variable _cond;
std::atomic<bool> _running;
std::thread _updateThread;
void UpdateThread();
static void InputActivateCallback(void *priv_data, calldata_t *cd);
static void InputDeactivateCallback(void *priv_data, calldata_t *cd);
};
}
}
}

View File

@ -56,7 +56,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
r = fmaxf(r, x4_mem[3]); \
} while (false)
float GetSamplePeak(__m128 previousSamples, const float *samples, size_t sampleCount)
static float GetSamplePeak(__m128 previousSamples, const float *samples, size_t sampleCount)
{
__m128 peak = previousSamples;
for (size_t i = 0; (i + 3) < sampleCount; i += 4) {
@ -69,7 +69,7 @@ float GetSamplePeak(__m128 previousSamples, const float *samples, size_t sampleC
return ret;
}
float GetTruePeak(__m128 previousSamples, const float *samples, size_t sampleCount)
static float GetTruePeak(__m128 previousSamples, const float *samples, size_t sampleCount)
{
const __m128 m3 = _mm_set_ps(-0.155915f, 0.935489f, 0.233872f, -0.103943f);
const __m128 m1 = _mm_set_ps(-0.216236f, 0.756827f, 0.504551f, -0.189207f);

View File

@ -111,9 +111,9 @@ void Utils::Platform::SendTrayNotification(QSystemTrayIcon::MessageIcon icon, QS
obs_queue_task(OBS_TASK_UI, [](void* param) {
void *systemTrayPtr = obs_frontend_get_system_tray();
auto systemTray = reinterpret_cast<QSystemTrayIcon*>(systemTrayPtr);
auto systemTray = static_cast<QSystemTrayIcon*>(systemTrayPtr);
auto notification = reinterpret_cast<SystemTrayNotification*>(param);
auto notification = static_cast<SystemTrayNotification*>(param);
systemTray->showMessage(notification->title, notification->body, notification->icon);
delete notification;
}, (void*)notification, false);

View File

@ -23,6 +23,7 @@ with this program. If not, see <https://www.gnu.org/licenses/>
#include <QObject>
#include <QThreadPool>
#include <QString>
#include <asio.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>