Compare commits

..

21 Commits
1.10 ... 1.11.2

Author SHA1 Message Date
WarmUpTill
e4bf5fa749 Add option to use transition overrides for transition changes (#164)
* Adjust switchScene() signature to support adjustActiveTransitionType and verbose parameters and only change the transition type if adjustActiveTransitionType, otherwise rely on transition overrides

* Add new adjustActiveTransitionType checkbox

* save and load adjustActiveTransitionType

* Add check to either enable transition type change or use of transition
overrides

* Add locale

* Adjust layout and locale
2021-04-10 19:58:17 +02:00
WarmUpTill
eb346da6f4 CI: Fix macOS build by disabling browser build (#163) 2021-04-09 20:57:35 +02:00
WarmUpTill
866480cde6 Fix crash on Linux if no ewmh is available (#159) 2021-04-07 21:03:54 +02:00
WarmUpTill
10e1c7b3a3 Resolve asio network crashes on macOS (#157)
* Reenable network tab functionality for macOS

* Set asio to b73dc1d and  websocketpp to c6d7e29
2021-04-03 22:02:42 +02:00
WarmUpTill
beb482bd5a Disable Network tab functionality on macOS (#155)
The asio library ran into issues when the obs-websocket plugin was also running leading to a crash in the obs-websocket's listen() calls.

As the use obs-websocket plugin is more widespread than the use of the advanced scene switcher the network tab functionality will be disabled for macOS for the time being.
2021-04-03 18:52:16 +02:00
WarmUpTill
e561d378dd CI: Disable PipeWire on Ubuntu (#156) 2021-04-03 12:43:52 +02:00
WarmUpTill
73ce88e2f0 Add option to save window size and position (#154) 2021-04-03 11:55:42 +02:00
WarmUpTill
dfccc4b16d Add network tab (#149)
The network tab allows you to open a web-socket or connect to web-socket to synchronize the active scene between two or more instances of OBS.
The functionality is mostly based on https://github.com/Palakis/obs-websocket.

An OBS instance running a server will send its currently active scene to all connected clients on scene change.
By default all scene switches will trigger a message to be sent to all clients, but you also have the option to limit sending these messages only when an automated scene change is triggered by the advanced scene switcher.
2021-03-24 20:51:22 +01:00
WarmUpTill
b0ef1fee71 Fix window check always testing for focus (#152) 2021-03-22 23:06:44 +01:00
WarmUpTill
1ef8ce4b4a Resolve Qt runtime warning by renaming function. (#151) 2021-03-22 22:31:29 +01:00
WarmUpTill
1bf4bff014 Merge pull request #150 from WarmUpTill/CI
CI: Improve format check and init submodules for build
2021-03-21 21:48:21 +01:00
WarmUpTill
4b6e00b2b2 CI: Init submodules for build 2021-03-21 21:39:11 +01:00
WarmUpTill
c99cbb0c0f CI: Improve format check.
The format check used to check too many files, which would be worsened
when introducing additional submodules.
2021-03-21 21:37:10 +01:00
WarmUpTill
2a87334d38 Init noMatchDelay to zero. (#148)
A non-zero value might be confusing for users which set up the plugin
for the first time.
2021-03-21 14:44:42 +01:00
WarmUpTill
1a4724ff21 Fix audio tab not supporting mono audio output. (#145) 2021-03-19 20:01:15 +01:00
WarmUpTill
59d8fb5d28 Fix MacOS build. (#146) 2021-03-19 19:43:58 +01:00
WarmUpTill
8823c05aeb Fix scene groups not being selected when using more than one entry on the Random tab. (#143) 2021-03-18 21:22:33 +01:00
WarmUpTill
2d0a52f8d1 Enable use of Scene Groups for Random tab (#141) 2021-03-17 20:00:06 +01:00
WarmUpTill
1273446ce5 Add option to configure default transition delay. (#140) 2021-03-17 18:21:42 +01:00
WarmUpTill
177ca4aea2 CI: Build on Ubuntu 18.04, use newer clang-format. (#139) 2021-03-13 17:02:04 +01:00
WarmUpTill
7526cbe1a3 Add option to check volume of 'inactive' audio sources. (#137) 2021-03-12 18:48:06 +01:00
24 changed files with 1375 additions and 122 deletions

View File

@@ -27,6 +27,7 @@ jobs:
uses: actions/checkout@v2.3.4
with:
path: UI/frontend-plugins/${{ env.PLUGIN_NAME }}
submodules: 'recursive'
- name: Fetch Git Tags
run: |
cd UI/frontend-plugins/${{ env.PLUGIN_NAME }}
@@ -62,7 +63,7 @@ jobs:
echo "add_subdirectory(${{ env.PLUGIN_NAME }})" >> UI/frontend-plugins/CMakeLists.txt
mkdir ./build
cd ./build
cmake -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 -DDISABLE_PYTHON=ON -DDepsPath="/tmp/obsdeps" -DQTDIR="/tmp/obsdeps" ..
cmake -DBUILD_BROWSER=OFF -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 -DDISABLE_PYTHON=ON -DDepsPath="/tmp/obsdeps" -DQTDIR="/tmp/obsdeps" ..
cd -
- name: Build
shell: bash
@@ -75,8 +76,9 @@ jobs:
if: success()
shell: bash
run: |
curl -L -O https://s3-us-west-2.amazonaws.com/obs-nightly/Packages.pkg
sudo installer -pkg ./Packages.pkg -target /
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 /
- name: Package
if: success()
shell: bash
@@ -102,7 +104,7 @@ jobs:
path: ./nightly/*.pkg
ubuntu64:
name: 'Linux/Ubuntu 64-bit'
runs-on: [ubuntu-latest]
runs-on: [ubuntu-18.04]
steps:
- name: Checkout
uses: actions/checkout@v2.3.4
@@ -113,6 +115,7 @@ jobs:
uses: actions/checkout@v2.3.4
with:
path: UI/frontend-plugins/${{ env.PLUGIN_NAME }}
submodules: 'recursive'
- name: Add plugin to obs cmake
shell: bash
run: echo "add_subdirectory(${{ env.PLUGIN_NAME }})" >> UI/frontend-plugins/CMakeLists.txt
@@ -170,7 +173,7 @@ jobs:
run: |
mkdir ./build
cd ./build
cmake -DUNIX_STRUCTURE=0 -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/obs-studio-portable" -DWITH_RTMPS=OFF -DBUILD_BROWSER=OFF ..
cmake -DENABLE_PIPEWIRE=OFF -DUNIX_STRUCTURE=0 -DCMAKE_INSTALL_PREFIX="${{ github.workspace }}/obs-studio-portable" -DWITH_RTMPS=OFF -DBUILD_BROWSER=OFF ..
- name: 'Build'
shell: bash
working-directory: ${{ github.workspace }}/build
@@ -210,6 +213,7 @@ jobs:
uses: actions/checkout@v2.3.4
with:
path: UI/frontend-plugins/${{ env.PLUGIN_NAME}}
submodules: 'recursive'
- name: Add plugin to obs cmake
shell: cmd
run: echo add_subdirectory(${{ env.PLUGIN_NAME }}) >> UI/frontend-plugins/CMakeLists.txt
@@ -271,7 +275,7 @@ jobs:
release:
needs: [macos64, ubuntu64, windows]
name: 'Create Release'
runs-on: [ubuntu-latest]
runs-on: [ubuntu-18.04]
steps:
- name: "Checkout plugin"
uses: actions/checkout@v2.3.4

View File

@@ -8,23 +8,10 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
with:
submodules: 'recursive'
- name: Install clang format
run: |
# gets us newer clang
sudo bash -c "cat >> /etc/apt/sources.list" << LLVMAPT
# 3.8
deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-8 main
deb-src http://apt.llvm.org/xenial/ llvm-toolchain-xenial-8 main
LLVMAPT
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
sudo apt-get -qq update
sudo apt-get install -y clang-format-8
sudo apt-get install -y clang-format-10
- name: Check the Formatting
run: |

6
.gitmodules vendored Normal file
View File

@@ -0,0 +1,6 @@
[submodule "deps/asio"]
path = deps/asio
url = https://github.com/chriskohlhoff/asio.git
[submodule "deps/websocketpp"]
path = deps/websocketpp
url = https://github.com/zaphoyd/websocketpp.git

View File

@@ -6,6 +6,8 @@ set -o errexit
set -o pipefail
set -o nounset
SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
# Runs the Clang Formatter in parallel on the code base.
# Return codes:
# - 1 there are files to be formatted
@@ -19,13 +21,15 @@ if [[ $OS = "Linux" || $OS = "Darwin" ]] ; then
fi
# Discover clang-format
if type clang-format-8 2> /dev/null ; then
if type clang-format-10 2> /dev/null ; then
CLANG_FORMAT=clang-format-10
elif type clang-format-8 2> /dev/null ; then
CLANG_FORMAT=clang-format-8
else
CLANG_FORMAT=clang-format
fi
find .. -type d \( -path ./deps \
-o -path ./cmake \
-o -path ./build \) -prune -type f -o -name '*.h' -or -name '*.hpp' -or -name '*.m' -or -name '*.mm' -or -name '*.c' -or -name '*.cpp' \
find $SCRIPTPATH/.. -type d \( -path $SCRIPTPATH/../deps \
-o -path $SCRIPTPATH/../cmake \
-o -path $SCRIPTPATH/../build \) -prune -type f -o -name '*.h' -or -name '*.hpp' -or -name '*.m' -or -name '*.mm' -or -name '*.c' -or -name '*.cpp' \
| xargs -L100 -P${NPROC} ${CLANG_FORMAT} -i -style=file -fallback-style=none

View File

@@ -1,6 +1,7 @@
cmake_minimum_required(VERSION 3.5)
project(advanced-scene-switcher)
add_definitions(-DASIO_STANDALONE)
# generate version info
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/")
@@ -52,6 +53,11 @@ if(UNIX AND NOT APPLE)
include_directories(${X11_INCLUDE_DIR})
endif()
include_directories(
"${CMAKE_CURRENT_SOURCE_DIR}/deps/asio/asio/include"
"${CMAKE_CURRENT_SOURCE_DIR}/deps/websocketpp"
)
set(advanced-scene-switcher_HEADERS
${advanced-scene-switcher_HEADERS}
src/headers/advanced-scene-switcher.hpp
@@ -66,6 +72,7 @@ set(advanced-scene-switcher_HEADERS
src/headers/switch-file.hpp
src/headers/switch-idle.hpp
src/headers/switch-media.hpp
src/headers/switch-network.hpp
src/headers/switch-pause.hpp
src/headers/switch-random.hpp
src/headers/switch-screen-region.hpp
@@ -94,6 +101,7 @@ set(advanced-scene-switcher_SOURCES
src/switch-file.cpp
src/switch-window.cpp
src/switch-media.cpp
src/switch-network.cpp
src/hotkey.cpp
src/general.cpp
src/switch-pause.cpp
@@ -113,6 +121,8 @@ set(advanced-scene-switcher_UI
)
if(WIN32)
add_definitions(-D_WEBSOCKETPP_CPP11_STL_)
set(advanced-scene-switcher_PLATFORM_SOURCES
src/win/advanced-scene-switcher-win.cpp)
elseif(APPLE)

View File

@@ -27,6 +27,7 @@ AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo="Wechsle zu:"
AdvSceneSwitcher.generalTab.generalBehavior.cooldown="Nach einem automatisierten Szenenwechsel wechsle nicht mehr für"
AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="In diesem Zeitraum werden potentielle erfüllte Bedingungen ignoriert!"
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Ausführliches Logging"
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Fensterposition und -größe speichern"
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Deaktiviere UI Tipps"
AdvSceneSwitcher.generalTab.priority="Priorität"
AdvSceneSwitcher.generalTab.priority.description="Szenenwechselmethoden sortiert nach Priorität"
@@ -53,13 +54,18 @@ AdvSceneSwitcher.generalTab.priority.video="Video"
; Transition Tab
AdvSceneSwitcher.transitionTab.title="Szenenübergänge"
AdvSceneSwitcher.transitionTab.transitionOverride="Szenenübergänge, die im Szenenwechsler konfiguriert werden, haben Vorrang vor Übergangsüberschreibung"
AdvSceneSwitcher.transitionTab.setTransitionBy="Beim Ändern des Szenenübergangs:"
AdvSceneSwitcher.transitionTab.transitionOverride="Verwende Übergangsüberschreibungen"
AdvSceneSwitcher.transitionTab.adjustActiveTransitionType="Wechsle den aktiven Szenenübergangtyp"
AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError="Mindestens eine Option muss aktiv sein:\n\n - Verwende Übergangsüberschreibungen\n\n - Wechsle den aktiven Szenenübergangtyp"
AdvSceneSwitcher.transitionTab.transitionForAToB="Szenenübergänge für automatisierte Szenenwechsel von Szene A zu Szene B"
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body><p>Diese Einstellungen beeinflussen <span style=\"font-style:italic;\">nur</span> vom Szenenwechsler ausgelöste Szenenübergänge - Siehe <a href=\"https://obsproject.com/forum/resources/transition-table.1174/\"><span style=\" text-decoration: underline; color:#268bd2;\">Transition Table</span></a> um auch manuelle Szenenübergänge zu konfigurieren.<br/>Einstellungen auf diesem Tab haben Vorrang, vor denen welche auf den übrigen Tabs konfiguriert wurden.<br/><br/>Klicke auf das Plus Symbol, um einen neuen Eintrag hinzuzufügen.</p></body></html>"
AdvSceneSwitcher.transitionTab.defaultTransition="Ändere den Szenenübergang wenn eine Szene aktiv ist"
AdvSceneSwitcher.transitionTab.entry="Wechsle von {{scenes}} zu {{scenes2}} mit {{transitions}}"
AdvSceneSwitcher.transitionTab.defaultTransitionEntry="Wenn {{scenes}} aktiv ist ändere den Szenenübergang zu {{transitions}}"
AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="Klicke auf das Plus Symbol, um einen neuen Eintrag hinzuzufügen."
AdvSceneSwitcher.transitionTab.defaultTransition.delay="Wechsle Szenenübergang {{defTransitionDelay}} nach Szenenwechsel."
AdvSceneSwitcher.transitionTab.defaultTransition.delay.help="Diese Verzögerung dient dazu Szenenwechsel zu unterbrechen.\nDies kann passieren, wenn der Szenenübergangtyp gewechselt wird während ein Szenenübergang grade noch im Gang ist."
; Pause Scenes Tab
AdvSceneSwitcher.pauseTab.title="Pause"
@@ -195,7 +201,8 @@ AdvSceneSwitcher.sceneSequenceTab.help="Dieser Tab ermöglicht es automatisiert
AdvSceneSwitcher.audioTab.title="Audio"
AdvSceneSwitcher.audioTab.condition.above="über"
AdvSceneSwitcher.audioTab.condition.below="unter"
AdvSceneSwitcher.audioTab.entry="Wenn die Lautstärke von {{audioSources}} {{condition}} {{volumeWidget}} ist für {{duration}} wechsle zu {{scenes}} mit {{transitions}}"
AdvSceneSwitcher.audioTab.ignoreInactiveSource="außer Audio Quelle ist inaktiv"
AdvSceneSwitcher.audioTab.entry="Wenn die Lautstärke von {{audioSources}} {{condition}} {{volumeWidget}} ist für {{duration}} wechsle zu {{scenes}} mit {{transitions}} {{ignoreInactiveSource}}"
AdvSceneSwitcher.audioTab.multiMatchfallbackCondition="Wenn mehrere Einträge zutreffen ..."
AdvSceneSwitcher.audioTab.multiMatchfallback="... für {{duration}} wechsle zu {{scenes}} mit {{transitions}}"
AdvSceneSwitcher.audioTab.help="Dieser Tab ermöglicht es basierend auf der Lautstärke von Audioquellen Szenen zu wechseln.\nSo kann zum Beispiel automatisch zu einer Szene gewechselt werden, wenn die Lautstärke eines Mikrofons eine konfigurierte Schwelle überschreitet.\n\nKlicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen."
@@ -212,6 +219,29 @@ AdvSceneSwitcher.videoTab.ignoreInactiveSource="außer Video Quelle ist inaktiv"
AdvSceneSwitcher.videoTab.entry="Wenn {{videoSources}} {{condition}} {{filePath}} {{browseButton}} für {{duration}} wechsle zu {{scenes}} mit {{transitions}} {{ignoreInactiveSource}}"
AdvSceneSwitcher.videoTab.help="<html><head/><body><p>Dieser Tab ermöglicht es basierend auf der Videoausgabe von Quellen scenen zu wechseln.<br/>Für eine noch bessere Implementierung dieser Funktionalität siehe <a href=\"https://obsproject.com/forum/resources/pixel-match-switcher.1202/\"><span style=\" text-decoration: underline; color:#268bd2;\">Pixel Match Switcher</span></a>.<br/><br/>Klicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen..</p></body></html>"
; Network Tab
AdvSceneSwitcher.networkTab.title="Netzwerk"
AdvSceneSwitcher.networkTab.warning="Die Verwendung des Servers außerhalb eines lokalen Netzwerks kann dazu führen, dass die aktive Szene von dritten Personen ausgelesen werden kann."
AdvSceneSwitcher.networkTab.Disabledwarning="Diese Funktionalität musste unter macOS leider aufgrund von Bibliotheksinkompatibilitäten mit dem obs-websocket plugin deaktiviert werden."
AdvSceneSwitcher.networkTab.server="Server starten (Sendet Szenenwechselnachrichten zu allen verbundenen Clients)"
AdvSceneSwitcher.networkTab.server.port="Port"
AdvSceneSwitcher.networkTab.server.lockToIPv4="Nur IPv4 verwenden (deaktiviert IPv6)"
AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Sende nur Szenenwechselnachrichten für automatisierte Szenenwechsel"
AdvSceneSwitcher.networkTab.startFailed.message="Der WebSocket-Server konnte nicht gestartet werden, mögliche Gründe:\n - Der TCP-Port %1 wird möglicherweise gerade von einem anderen Programm verwendet. Versuchen Sie einen anderen Port in den Websocket-Servereinstellungen zu setzen oder alle Programme zu beenden, die den Port möglicherweise verwenden.\n - Fehler: %2"
AdvSceneSwitcher.networkTab.server.status.currentStatus="Aktueller Status"
AdvSceneSwitcher.networkTab.server.status.notRunning="Nicht gestartet"
AdvSceneSwitcher.networkTab.server.status.starting="Startet"
AdvSceneSwitcher.networkTab.server.status.running="Server läuft"
AdvSceneSwitcher.networkTab.server.restart="Server neu starten"
AdvSceneSwitcher.networkTab.client="Client starten (Empfängt Szenenwechselnachrichten )"
AdvSceneSwitcher.networkTab.client.address="Hostname oder IP-Adresse"
AdvSceneSwitcher.networkTab.client.port="Port"
AdvSceneSwitcher.networkTab.client.status.currentStatus="Aktueller status"
AdvSceneSwitcher.networkTab.client.status.disconnected="Verbindung getrennt"
AdvSceneSwitcher.networkTab.client.status.connecting="Verbinde"
AdvSceneSwitcher.networkTab.client.status.connected="Verbunden"
AdvSceneSwitcher.networkTab.client.reconnect="Erneutes Verbinden erzwingen"
; Scene Group Tab
AdvSceneSwitcher.sceneGroupTab.title="Szenengruppe"
AdvSceneSwitcher.sceneGroupTab.list="Szenengruppen"

View File

@@ -27,6 +27,7 @@ AdvSceneSwitcher.generalTab.generalBehavior.onNoMet.switchTo="Switch to:"
AdvSceneSwitcher.generalTab.generalBehavior.cooldown="After a match do not switch scenes for"
AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint="During this time potential matches will be ignored!"
AdvSceneSwitcher.generalTab.generalBehavior.verboseLogging="Enable verbose logging"
AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo="Save window position and size"
AdvSceneSwitcher.generalTab.generalBehavior.disableUIHints="Disable UI hints"
AdvSceneSwitcher.generalTab.priority="Priority"
AdvSceneSwitcher.generalTab.priority.description="Switching methods priority (Highest priority is at the top)"
@@ -53,13 +54,18 @@ AdvSceneSwitcher.generalTab.priority.video="Video"
; Transition Tab
AdvSceneSwitcher.transitionTab.title="Transition"
AdvSceneSwitcher.transitionTab.transitionOverride="Scene transitions defined in the scene switcher take priority over Transition Overrides defined per scene"
AdvSceneSwitcher.transitionTab.setTransitionBy="When changing transitions:"
AdvSceneSwitcher.transitionTab.transitionOverride="Set transition overrides"
AdvSceneSwitcher.transitionTab.adjustActiveTransitionType="Change active transition type"
AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError="At least one option must be enabled:\n\n - Use transition overrides\n\n - Change active transition type"
AdvSceneSwitcher.transitionTab.transitionForAToB="Use transition for automated scene switch from scene A to scene B"
AdvSceneSwitcher.transitionTab.transitionsHelp="<html><head/><body><p>These settings <span style=\"font-style:italic;\">only</span> affect transitions caused by the scene switcher - Check out <a href=\"https://obsproject.com/forum/resources/transition-table.1174/\"><span style=\" text-decoration: underline; color:#268bd2;\">Transition Table</span></a> if you want to configure this for manual scene changes.<br/>Settings defined here take priority over transition settings configured elsewhere in the scene switcher.<br/><br/>Click the plus symbol below to add a new entry.</p></body></html>"
AdvSceneSwitcher.transitionTab.defaultTransition="Change transition if scene is active"
AdvSceneSwitcher.transitionTab.entry="Switch from {{scenes}} to {{scenes2}} using {{transitions}}"
AdvSceneSwitcher.transitionTab.defaultTransitionEntry="When scene {{scenes}} is active change default scene transition to {{transitions}}"
AdvSceneSwitcher.transitionTab.defaultTransitionsHelp="Click on the plus symbol to add an entry."
AdvSceneSwitcher.transitionTab.defaultTransition.delay="Switch transition {{defTransitionDelay}} after scene change."
AdvSceneSwitcher.transitionTab.defaultTransition.delay.help="The delay is used to avoid cancelled scene switches, which can happen if the transition type is changed while a transition is still ongoing."
; Pause Scenes Tab
AdvSceneSwitcher.pauseTab.title="Pause"
@@ -195,7 +201,8 @@ AdvSceneSwitcher.sceneSequenceTab.help="This tab will allow you to automatically
AdvSceneSwitcher.audioTab.title="Audio"
AdvSceneSwitcher.audioTab.condition.above="above"
AdvSceneSwitcher.audioTab.condition.below="below"
AdvSceneSwitcher.audioTab.entry="When the volume of {{audioSources}} is {{condition}} {{volumeWidget}} for {{duration}} switch to {{scenes}} using {{transitions}}"
AdvSceneSwitcher.audioTab.ignoreInactiveSource="unless source is inactive"
AdvSceneSwitcher.audioTab.entry="When the volume of {{audioSources}} is {{condition}} {{volumeWidget}} for {{duration}} switch to {{scenes}} using {{transitions}} {{ignoreInactiveSource}}"
AdvSceneSwitcher.audioTab.multiMatchfallbackCondition="If multiple entries match ..."
AdvSceneSwitcher.audioTab.multiMatchfallback="... for {{duration}} switch to {{scenes}} using {{transitions}}"
AdvSceneSwitcher.audioTab.help="This tab will allow you to switch scenes based on the volume of sources.\nFor example, you could automatically switch to a different scene if the volume of your microphone reaches a certain threshold.\n\nClick on the highlighted plus symbol to continue."
@@ -212,6 +219,29 @@ AdvSceneSwitcher.videoTab.ignoreInactiveSource="unless source is inactive"
AdvSceneSwitcher.videoTab.entry="When {{videoSources}} {{condition}} {{filePath}} {{browseButton}} for {{duration}} switch to {{scenes}} using {{transitions}} {{ignoreInactiveSource}}"
AdvSceneSwitcher.videoTab.help="<html><head/><body><p>This tab will allow you to switch scenes based on the current video output of selected sources.<br/>Make sure to check out <a href=\"https://obsproject.com/forum/resources/pixel-match-switcher.1202/\"><span style=\" text-decoration: underline; color:#268bd2;\">Pixel Match Switcher</span></a> for an even better implementation of this functionality.<br/><br/> Click on the highlighted plus symbol to continue.</p></body></html>"
; Network Tab
AdvSceneSwitcher.networkTab.title="Network"
AdvSceneSwitcher.networkTab.warning="Running the server outside of a local network will allow third parties to read the active scene."
AdvSceneSwitcher.networkTab.DisabledWarning="This functionality unfortunately had to be disabled on macOS due to library incompatibilities when running the obs-websocket plugin in parallel."
AdvSceneSwitcher.networkTab.server="Start server (Sends scene switch messages to all connected clients)"
AdvSceneSwitcher.networkTab.server.port="Port"
AdvSceneSwitcher.networkTab.server.lockToIPv4="Lock server to only using IPv4"
AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches="Only send messages for automated scene switches"
AdvSceneSwitcher.networkTab.startFailed.message="The WebSockets server failed to start, maybe because:\n - TCP port %1 may currently be in use elsewhere on this system, possibly by another application. Try setting a different TCP port in the WebSocket server settings, or stop any application that could be using this port.\n - Error message: %2"
AdvSceneSwitcher.networkTab.server.status.currentStatus="Current status"
AdvSceneSwitcher.networkTab.server.status.notRunning="Not running"
AdvSceneSwitcher.networkTab.server.status.starting="Starting"
AdvSceneSwitcher.networkTab.server.status.running="Running"
AdvSceneSwitcher.networkTab.server.restart="Restart server"
AdvSceneSwitcher.networkTab.client="Start client (Receives scene switches messages)"
AdvSceneSwitcher.networkTab.client.address="Hostname or IP address"
AdvSceneSwitcher.networkTab.client.port="Port"
AdvSceneSwitcher.networkTab.client.status.currentStatus="Current status"
AdvSceneSwitcher.networkTab.client.status.disconnected="Disconnected"
AdvSceneSwitcher.networkTab.client.status.connecting="Connecting"
AdvSceneSwitcher.networkTab.client.status.connected="Connected"
AdvSceneSwitcher.networkTab.client.reconnect="Force reconnect"
; Scene Group Tab
AdvSceneSwitcher.sceneGroupTab.title="Scene Group"
AdvSceneSwitcher.sceneGroupTab.list="Scene Groups"

1
deps/asio vendored Submodule

Submodule deps/asio added at b73dc1d2c0

1
deps/websocketpp vendored Submodule

Submodule deps/websocketpp added at c6d7e295bf

View File

@@ -161,46 +161,8 @@
<property name="title">
<string>AdvSceneSwitcher.generalTab.generalBehavior</string>
</property>
<layout class="QGridLayout" name="gridLayout_5">
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.cooldown</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="cooldownTime">
<property name="suffix">
<string>s</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_32">
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<layout class="QHBoxLayout" name="horizontalLayout_43">
@@ -295,7 +257,45 @@
</item>
</layout>
</item>
<item row="4" column="0">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.cooldown</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="cooldownTime">
<property name="suffix">
<string>s</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_52">
<item>
<widget class="QCheckBox" name="verboseLogging">
@@ -319,7 +319,31 @@
</item>
</layout>
</item>
<item row="5" column="0">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_50">
<item>
<widget class="QCheckBox" name="saveWindowGeo">
<property name="text">
<string>AdvSceneSwitcher.generalTab.generalBehavior.saveWindowGeo</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_55">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_48">
<item>
<widget class="QCheckBox" name="uiHintsDisable">
@@ -542,18 +566,42 @@
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_15">
<item>
<widget class="QCheckBox" name="transitionOverridecheckBox">
<property name="text">
<string>AdvSceneSwitcher.transitionTab.transitionOverride</string>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line_17">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
<layout class="QGridLayout" name="gridLayout_5">
<item row="1" column="1">
<widget class="QCheckBox" name="adjustActiveTransitionType">
<property name="text">
<string>AdvSceneSwitcher.transitionTab.adjustActiveTransitionType</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>AdvSceneSwitcher.transitionTab.setTransitionBy</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="transitionOverridecheckBox">
<property name="text">
<string>AdvSceneSwitcher.transitionTab.transitionOverride</string>
</property>
</widget>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer_9">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QGroupBox" name="transitionsGroup">
@@ -737,6 +785,9 @@
<string>AdvSceneSwitcher.transitionTab.defaultTransition</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_12">
<item>
<layout class="QHBoxLayout" name="defTransitionDelayLayout"/>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_10">
<item row="0" column="0">
@@ -2825,7 +2876,7 @@
</widget>
</item>
<item>
<widget class="Line" name="line">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
@@ -3269,6 +3320,184 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="networkTab">
<attribute name="title">
<string>AdvSceneSwitcher.networkTab.title</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_30">
<item>
<widget class="QLabel" name="networkTabWarningLabel">
<property name="text">
<string>AdvSceneSwitcher.networkTab.warning</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="serverSettings">
<property name="title">
<string>AdvSceneSwitcher.networkTab.server</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_31">
<item>
<layout class="QGridLayout" name="gridLayout_24">
<item row="0" column="1">
<widget class="QSpinBox" name="serverPort">
<property name="minimum">
<number>1024</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>55555</number>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_17">
<property name="text">
<string>AdvSceneSwitcher.networkTab.server.port</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="lockToIPv4">
<property name="text">
<string>AdvSceneSwitcher.networkTab.server.lockToIPv4</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_19">
<property name="text">
<string>AdvSceneSwitcher.networkTab.server.status.currentStatus</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="serverStatus">
<property name="text">
<string>AdvSceneSwitcher.networkTab.server.status.notRunning</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="restrictSend">
<property name="text">
<string>AdvSceneSwitcher.networkTab.server.restrictSendToAutomatedSwitches</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="serverRestart">
<property name="text">
<string>AdvSceneSwitcher.networkTab.server.restart</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="clientSettings">
<property name="title">
<string>AdvSceneSwitcher.networkTab.client</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QGridLayout" name="gridLayout_23">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout_21">
<item row="1" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>AdvSceneSwitcher.networkTab.client.port</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>AdvSceneSwitcher.networkTab.client.address</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="clientHostname">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="clientPort">
<property name="minimum">
<number>1024</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>55555</number>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="clientStatus">
<property name="text">
<string>AdvSceneSwitcher.networkTab.client.status.disconnected</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>AdvSceneSwitcher.networkTab.client.status.currentStatus</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="clientReconnect">
<property name="text">
<string>AdvSceneSwitcher.networkTab.client.reconnect</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="sceneGroupTab">
<attribute name="title">
<string>AdvSceneSwitcher.sceneGroupTab.title</string>

View File

@@ -60,10 +60,12 @@ void AdvSceneSwitcher::loadUI()
setupTimeTab();
setupAudioTab();
setupVideoTab();
setupNetworkTab();
setupSceneGroupTab();
setupTriggerTab();
setTabOrder();
restoreWindowGeo();
loading = false;
}
@@ -546,8 +548,12 @@ void SwitcherData::Thread()
lock.unlock();
if (match) {
if (networkConfig.ServerEnabled) {
server.sendMessage(scene, transition);
}
switchScene(scene, transition,
tansitionOverrideOverride);
tansitionOverrideOverride,
adjustActiveTransitionType, verbose);
}
writeSceneInfoToFile();
@@ -614,9 +620,10 @@ bool SwitcherData::checkForMatch(OBSWeakSource &scene,
}
void switchScene(OBSWeakSource &scene, OBSWeakSource &transition,
bool &transitionOverrideOverride)
bool transitionOverrideOverride,
bool adjustActiveTransitionType, bool verbose)
{
if (!scene && switcher->verbose) {
if (!scene && verbose) {
blog(LOG_INFO, "nothing to switch to");
return;
}
@@ -627,13 +634,14 @@ void switchScene(OBSWeakSource &scene, OBSWeakSource &transition,
if (source && source != currentSource) {
transitionData td;
setNextTransition(scene, currentSource, transition,
transitionOverrideOverride, td);
transitionOverrideOverride,
adjustActiveTransitionType, td);
obs_frontend_set_current_scene(source);
if (transitionOverrideOverride) {
restoreTransitionOverride(source, td);
}
if (switcher->verbose) {
if (verbose) {
blog(LOG_INFO, "switched scene");
}
}
@@ -652,6 +660,15 @@ void SwitcherData::Start()
// Will be overwritten quickly but might be useful
writeToStatusFile("Advanced Scene Switcher running");
}
if (networkConfig.ServerEnabled) {
server.start(networkConfig.ServerPort,
networkConfig.LockToIPv4);
}
if (networkConfig.ClientEnabled) {
client.connect(networkConfig.GetClientUri());
}
}
void SwitcherData::Stop()
@@ -666,6 +683,9 @@ void SwitcherData::Stop()
writeToStatusFile("Advanced Scene Switcher stopped");
}
server.stop();
client.disconnect();
}
bool SwitcherData::sceneChangedDuringWait()
@@ -714,6 +734,11 @@ void handleSceneChange(SwitcherData *s)
s->checkTriggers();
s->checkDefaultSceneTransitions();
if (switcher->networkConfig.ServerEnabled &&
switcher->networkConfig.SendAll) {
switcher->server.sendMessage(ws, nullptr);
}
}
void setLiveTime(SwitcherData *s)

View File

@@ -4,7 +4,7 @@
#include "headers/utility.hpp"
#include "headers/version.h"
constexpr auto tab_count = 16;
constexpr auto tab_count = 17;
QMetaObject::Connection inactivePluse;
@@ -156,6 +156,9 @@ void AdvSceneSwitcher::on_toggleStartButton_clicked()
void AdvSceneSwitcher::closeEvent(QCloseEvent *)
{
switcher->windowPos = this->pos();
switcher->windowSize = this->size();
obs_frontend_save();
}
@@ -168,6 +171,15 @@ void AdvSceneSwitcher::on_verboseLogging_stateChanged(int state)
switcher->verbose = state;
}
void AdvSceneSwitcher::on_saveWindowGeo_stateChanged(int state)
{
if (loading) {
return;
}
switcher->saveWindowGeo = state;
}
void AdvSceneSwitcher::on_uiHintsDisable_stateChanged(int state)
{
if (loading) {
@@ -329,9 +341,12 @@ int findTabIndex(QTabWidget *tabWidget, int pos)
tabName = "videoTab";
break;
case 14:
tabName = "sceneGroupTab";
tabName = "networkTab";
break;
case 15:
tabName = "sceneGroupTab";
break;
case 16:
tabName = "sceneTriggerTab";
break;
}
@@ -362,6 +377,14 @@ void AdvSceneSwitcher::setTabOrder()
connect(bar, &QTabBar::tabMoved, this, &AdvSceneSwitcher::on_tabMoved);
}
void AdvSceneSwitcher::restoreWindowGeo()
{
if (switcher->saveWindowGeo) {
this->resize(switcher->windowSize);
this->move(switcher->windowPos);
}
}
void AdvSceneSwitcher::on_tabMoved(int from, int to)
{
if (loading) {
@@ -400,6 +423,7 @@ void SwitcherData::loadSettings(obs_data_t *obj)
loadTimeSwitches(obj);
loadAudioSwitches(obj);
loadVideoSwitches(obj);
loadNetworkSettings(obj);
loadSceneTriggers(obj);
loadGeneralSettings(obj);
loadHotkeys(obj);
@@ -425,6 +449,7 @@ void SwitcherData::saveSettings(obs_data_t *obj)
saveTimeSwitches(obj);
saveAudioSwitches(obj);
saveVideoSwitches(obj);
saveNetworkSwitches(obj);
saveSceneTriggers(obj);
saveGeneralSettings(obj);
saveHotkeys(obj);
@@ -497,8 +522,15 @@ void SwitcherData::saveGeneralSettings(obs_data_t *obj)
obs_data_set_int(obj, "sequenceTabPos", switcher->tabOrder[11]);
obs_data_set_int(obj, "audioTabPos", switcher->tabOrder[12]);
obs_data_set_int(obj, "videoTabPos", switcher->tabOrder[13]);
obs_data_set_int(obj, "sceneGroupTabPos", switcher->tabOrder[14]);
obs_data_set_int(obj, "triggerTabPos", switcher->tabOrder[15]);
obs_data_set_int(obj, "networkTabPos", switcher->tabOrder[14]);
obs_data_set_int(obj, "sceneGroupTabPos", switcher->tabOrder[15]);
obs_data_set_int(obj, "triggerTabPos", switcher->tabOrder[16]);
obs_data_set_bool(obj, "saveWindowGeo", saveWindowGeo);
obs_data_set_int(obj, "windowPosX", switcher->windowPos.x());
obs_data_set_int(obj, "windowPosY", switcher->windowPos.y());
obs_data_set_int(obj, "windowWidth", switcher->windowSize.width());
obs_data_set_int(obj, "windowHeight", switcher->windowSize.height());
}
void SwitcherData::loadGeneralSettings(obs_data_t *obj)
@@ -594,8 +626,9 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
obs_data_set_default_int(obj, "sequenceTabPos", 11);
obs_data_set_default_int(obj, "audioTabPos", 12);
obs_data_set_default_int(obj, "videoTabPos", 13);
obs_data_set_default_int(obj, "sceneGroupTabPos", 14);
obs_data_set_default_int(obj, "triggerTabPos", 15);
obs_data_set_default_int(obj, "networkTabPos", 14);
obs_data_set_default_int(obj, "sceneGroupTabPos", 15);
obs_data_set_default_int(obj, "triggerTabPos", 16);
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "generalTabPos")));
@@ -625,6 +658,8 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
(int)(obs_data_get_int(obj, "audioTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "videoTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "networkTabPos")));
switcher->tabOrder.emplace_back(
(int)(obs_data_get_int(obj, "sceneGroupTabPos")));
switcher->tabOrder.emplace_back(
@@ -633,6 +668,12 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
if (!tabOrderValid()) {
resetTabOrder();
}
saveWindowGeo = obs_data_get_bool(obj, "saveWindowGeo");
windowPos = {(int)obs_data_get_int(obj, "windowPosX"),
(int)obs_data_get_int(obj, "windowPosY")};
windowSize = {(int)obs_data_get_int(obj, "windowWidth"),
(int)obs_data_get_int(obj, "windowHeight")};
}
bool SwitcherData::tabOrderValid()
@@ -754,6 +795,7 @@ void AdvSceneSwitcher::setupGeneralTab()
"AdvSceneSwitcher.generalTab.generalBehavior.cooldownHint"));
ui->verboseLogging->setChecked(switcher->verbose);
ui->saveWindowGeo->setChecked(switcher->saveWindowGeo);
ui->uiHintsDisable->setChecked(switcher->disableHints);
for (int p : switcher->functionNamesByPriority) {

View File

@@ -60,7 +60,9 @@ public:
void setupSceneGroupTab();
void setupTriggerTab();
void setupVideoTab();
void setupNetworkTab();
void setTabOrder();
void restoreWindowGeo();
static bool DisplayMessage(QString msg, bool question = false);
static void AskBackup(obs_data_t *obj);
@@ -149,6 +151,7 @@ public slots:
void on_sceneSequenceSwitches_itemDoubleClicked(QListWidgetItem *item);
void on_verboseLogging_stateChanged(int state);
void on_saveWindowGeo_stateChanged(int state);
void on_uiHintsDisable_stateChanged(int state);
void on_exportSettings_clicked();
@@ -163,6 +166,8 @@ public slots:
void on_defaultTransitionsUp_clicked();
void on_defaultTransitionsDown_clicked();
void on_transitionOverridecheckBox_stateChanged(int state);
void on_adjustActiveTransitionType_stateChanged(int state);
void defTransitionDelayValueChanged(int value);
void on_browseButton_clicked();
void on_readFileCheckBox_stateChanged(int state);
@@ -211,6 +216,18 @@ public slots:
void on_videoDown_clicked();
void on_getScreenshot_clicked();
void on_serverSettings_toggled(bool on);
void on_serverPort_valueChanged(int value);
void on_lockToIPv4_stateChanged(int state);
void on_serverRestart_clicked();
void updateServerStatus();
void on_clientSettings_toggled(bool on);
void on_clientHostname_textChanged(const QString &text);
void on_clientPort_valueChanged(int value);
void on_restrictSend_stateChanged(int state);
void on_clientReconnect_clicked();
void updateClientStatus();
void on_sceneGroupAdd_clicked();
void on_sceneGroupRemove_clicked();
void on_sceneGroupUp_clicked();
@@ -271,12 +288,14 @@ bool isInFocus(const QString &executable);
void setNextTransition(OBSWeakSource &targetScene, obs_source_t *currentSource,
OBSWeakSource &transition,
bool &transitionOverrideOverride, transitionData &td);
bool transitionOverrideOverride,
bool adjustActiveTransitionType, transitionData &td);
void overwriteTransitionOverride(obs_weak_source_t *sceneWs,
obs_source_t *transition, transitionData &td);
void restoreTransitionOverride(obs_source_t *scene, transitionData td);
void switchScene(OBSWeakSource &scene, OBSWeakSource &transition,
bool &transitionOverrideOverride);
bool transitionOverrideOverride,
bool adjustActiveTransitionType, bool verbose);
/******************************************************************************
* Main SwitcherData

View File

@@ -1,5 +1,6 @@
#pragma once
#include <QSpinBox>
#include <limits>
#include "switch-generic.hpp"
#include "volume-control.hpp"
@@ -18,8 +19,9 @@ struct AudioSwitch : virtual SceneSwitcherEntry {
int volumeThreshold = 0;
audioCondition condition = ABOVE;
double duration = 0;
bool ignoreInactiveSource = true;
unsigned int matchCount = 0;
float peak = -1;
float peak = -std::numeric_limits<float>::infinity();
obs_volmeter_t *volmeter = nullptr;
const char *getType() { return "audio"; }
@@ -69,12 +71,14 @@ private slots:
void VolumeThresholdChanged(int vol);
void ConditionChanged(int cond);
void DurationChanged(double dur);
void IgnoreInactiveChanged(int state);
private:
QComboBox *audioSources;
QComboBox *condition;
QSpinBox *audioVolumeThreshold;
QDoubleSpinBox *duration;
QCheckBox *ignoreInactiveSource;
VolControl *volMeter;
AudioSwitch *switchData;

View File

@@ -0,0 +1,111 @@
/*
Most of this code is based on https://github.com/Palakis/obs-websocket
*/
#pragma once
#include <set>
#include <QtCore/QObject>
#include <QtCore/QMutex>
#include <QtCore/QSharedPointer>
#include <QtCore/QVariantHash>
#include <QtCore/QThreadPool>
#include <mutex>
#include <condition_variable>
#include <websocketpp/config/asio_no_tls_client.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include <websocketpp/client.hpp>
using websocketpp::connection_hdl;
typedef websocketpp::server<websocketpp::config::asio> server;
typedef websocketpp::client<websocketpp::config::asio_client> client;
class NetworkConfig {
public:
NetworkConfig();
void Load(obs_data_t *obj);
void Save(obs_data_t *obj);
void SetDefaults(obs_data_t *obj);
std::string GetClientUri();
// Server
bool ServerEnabled;
uint64_t ServerPort;
bool LockToIPv4;
// Client
bool ClientEnabled;
std::string Address;
uint64_t ClientPort;
bool SendAll;
};
class WSServer : public QObject {
Q_OBJECT
public:
explicit WSServer();
virtual ~WSServer();
void start(quint16 port, bool lockToIPv4);
void stop();
void sendMessage(OBSWeakSource scene, OBSWeakSource transition);
QThreadPool *threadPool() { return &_threadPool; }
private:
void onOpen(connection_hdl hdl);
void onMessage(connection_hdl hdl, server::message_ptr message);
void onClose(connection_hdl hdl);
QString getRemoteEndpoint(connection_hdl hdl);
server _server;
quint16 _serverPort;
bool _lockToIPv4;
std::set<connection_hdl, std::owner_less<connection_hdl>> _connections;
QMutex _clMutex;
QThreadPool _threadPool;
};
enum class ServerStatus {
NOT_RUNNING,
STARTING,
RUNNING,
};
class WSClient : public QObject {
Q_OBJECT
public:
explicit WSClient();
virtual ~WSClient();
void connect(std::string uri);
void disconnect();
std::string getFail() { return _failMsg; }
private:
void onOpen(connection_hdl hdl);
void onFail(connection_hdl hdl);
void onMessage(connection_hdl hdl, client::message_ptr message);
void onClose(connection_hdl hdl);
void connectThread();
client _client;
std::string _uri;
connection_hdl _connection;
std::thread _thread;
bool _retry = false;
std::atomic_bool _connected = {false};
std::mutex _waitMtx;
std::condition_variable _cv;
std::string _failMsg;
};
enum class ClientStatus {
DISCONNECTED,
CONNECTING,
CONNECTED,
FAIL,
};

View File

@@ -18,6 +18,7 @@ struct SceneTransition : SceneSwitcherEntry {
struct DefaultSceneTransition : SceneSwitcherEntry {
static bool pause;
static unsigned int delay;
const char *getType() { return "def_transition"; }
bool checkMatch(OBSWeakSource currentScene);

View File

@@ -22,6 +22,7 @@
#include "switch-window.hpp"
#include "switch-sequence.hpp"
#include "switch-video.hpp"
#include "switch-network.hpp"
constexpr auto default_interval = 300;
constexpr auto previous_scene_name = "Previous Scene";
@@ -61,6 +62,7 @@ struct SwitcherData {
bool disableHints = false;
bool showFrame = false;
bool tansitionOverrideOverride = false;
bool adjustActiveTransitionType = true;
int interval = default_interval;
@@ -68,9 +70,10 @@ struct SwitcherData {
OBSWeakSource previousScene = nullptr;
OBSWeakSource previousSceneHelper = nullptr;
OBSWeakSource lastRandomScene;
SceneGroup *lastRandomSceneGroup;
OBSWeakSource nonMatchingScene;
NoMatch switchIfNotMatching = NO_SWITCH;
double noMatchDelay;
double noMatchDelay = 0;
double noMatchCount = 0;
StartupBehavior startupBehavior = PERSIST;
AutoStartEvent autoStartEvent = AutoStartEvent::NEVER;
@@ -114,6 +117,12 @@ struct SwitcherData {
std::deque<AudioSwitch> audioSwitches;
AudioSwitchFallback audioFallback;
WSServer server;
ServerStatus serverStatus = ServerStatus::NOT_RUNNING;
WSClient client;
ClientStatus clientStatus = ClientStatus::DISCONNECTED;
NetworkConfig networkConfig;
std::deque<VideoSwitch> videoSwitches;
std::deque<SceneGroup> sceneGroups;
@@ -158,6 +167,10 @@ struct SwitcherData {
obs_hotkey_id stopHotkey = 0;
obs_hotkey_id toggleHotkey = 0;
bool saveWindowGeo = false;
QPoint windowPos = {};
QSize windowSize = {};
bool versionChanged(obs_data_t *obj, std::string currentVersion);
void Thread();
@@ -225,6 +238,7 @@ struct SwitcherData {
void saveSceneGroups(obs_data_t *obj);
void saveSceneTriggers(obs_data_t *obj);
void saveVideoSwitches(obs_data_t *obj);
void saveNetworkSwitches(obs_data_t *obj);
void saveGeneralSettings(obs_data_t *obj);
void saveHotkeys(obs_data_t *obj);
void saveVersion(obs_data_t *obj, std::string currentVersion);
@@ -246,6 +260,7 @@ struct SwitcherData {
void loadSceneGroups(obs_data_t *obj);
void loadSceneTriggers(obs_data_t *obj);
void loadVideoSwitches(obs_data_t *obj);
void loadNetworkSettings(obs_data_t *obj);
void loadGeneralSettings(obs_data_t *obj);
void loadHotkeys(obs_data_t *obj);

View File

@@ -203,6 +203,10 @@ void GetCurrentWindowTitle(std::string &title)
&format, &num, &bytes,
(uint8_t **)&data);
if (data == nullptr) {
return;
}
int status = 0;
XTextProperty text;
if (xstatus == Success) {
@@ -386,4 +390,4 @@ int secondsSinceLastInput()
XCloseDisplay(display);
return idle_time;
}
}

View File

@@ -125,9 +125,16 @@ void SwitcherData::checkAudioSwitch(bool &match, OBSWeakSource &scene,
continue;
}
obs_source_t *as = obs_weak_source_get_source(s.audioSource);
bool audioActive = obs_source_active(as);
obs_source_release(as);
if (s.ignoreInactiveSource) {
obs_source_t *as =
obs_weak_source_get_source(s.audioSource);
bool audioActive = obs_source_active(as);
obs_source_release(as);
if (!audioActive) {
continue;
}
}
// peak will have a value from -60 db to 0 db
bool volumeThresholdreached = false;
@@ -154,7 +161,7 @@ void SwitcherData::checkAudioSwitch(bool &match, OBSWeakSource &scene,
1000.0 >=
s.duration;
if (volumeThresholdreached && durationReached && audioActive) {
if (volumeThresholdreached && durationReached) {
if (match) {
checkAudioSwitchFallback(scene, transition);
fallbackChecked = true;
@@ -250,7 +257,7 @@ void AudioSwitch::setVolumeLevel(void *data,
UNUSED_PARAMETER(inputPeak);
AudioSwitch *s = static_cast<AudioSwitch *>(data);
for (int i = 1; i < MAX_AUDIO_CHANNELS; i++) {
for (int i = 0; i < MAX_AUDIO_CHANNELS; i++) {
if (peak[i] > s->peak) {
s->peak = peak[i];
}
@@ -301,6 +308,7 @@ void AudioSwitch::save(obs_data_t *obj)
obs_data_set_int(obj, "volume", volumeThreshold);
obs_data_set_int(obj, "condition", condition);
obs_data_set_double(obj, "duration", duration);
obs_data_set_bool(obj, "ignoreInactiveSource", ignoreInactiveSource);
}
// To be removed in future version
@@ -346,6 +354,7 @@ void AudioSwitch::load(obs_data_t *obj)
volumeThreshold = obs_data_get_int(obj, "volume");
condition = (audioCondition)obs_data_get_int(obj, "condition");
duration = obs_data_get_double(obj, "duration");
ignoreInactiveSource = obs_data_get_bool(obj, "ignoreInactiveSource");
volmeter = AddVolmeterToSource(this, audioSource);
}
@@ -453,6 +462,8 @@ AudioSwitchWidget::AudioSwitchWidget(QWidget *parent, AudioSwitch *s)
condition = new QComboBox();
audioVolumeThreshold = new QSpinBox();
duration = new QDoubleSpinBox();
ignoreInactiveSource = new QCheckBox(obs_module_text(
"AdvSceneSwitcher.audioTab.ignoreInactiveSource"));
obs_source_t *source = nullptr;
if (s) {
@@ -482,6 +493,8 @@ AudioSwitchWidget::AudioSwitchWidget(QWidget *parent, AudioSwitch *s)
QWidget::connect(audioSources,
SIGNAL(currentTextChanged(const QString &)), this,
SLOT(SourceChanged(const QString &)));
QWidget::connect(ignoreInactiveSource, SIGNAL(stateChanged(int)), this,
SLOT(IgnoreInactiveChanged(int)));
AdvSceneSwitcher::populateAudioSelection(audioSources);
populateConditionSelection(condition);
@@ -492,6 +505,7 @@ AudioSwitchWidget::AudioSwitchWidget(QWidget *parent, AudioSwitch *s)
audioVolumeThreshold->setValue(s->volumeThreshold);
condition->setCurrentIndex(s->condition);
duration->setValue(s->duration);
ignoreInactiveSource->setChecked(s->ignoreInactiveSource);
}
QHBoxLayout *switchLayout = new QHBoxLayout;
@@ -500,6 +514,7 @@ AudioSwitchWidget::AudioSwitchWidget(QWidget *parent, AudioSwitch *s)
{"{{volumeWidget}}", audioVolumeThreshold},
{"{{condition}}", condition},
{"{{duration}}", duration},
{"{{ignoreInactiveSource}}", ignoreInactiveSource},
{"{{scenes}}", scenes},
{"{{transitions}}", transitions}};
placeWidgets(obs_module_text("AdvSceneSwitcher.audioTab.entry"),
@@ -599,6 +614,16 @@ void AudioSwitchWidget::DurationChanged(double dur)
switchData->duration = dur;
}
void AudioSwitchWidget::IgnoreInactiveChanged(int state)
{
if (loading || !switchData) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switchData->ignoreInactiveSource = state;
}
AudioSwitchFallbackWidget::AudioSwitchFallbackWidget(QWidget *parent,
AudioSwitchFallback *s)
: SwitchWidget(parent, s, true, true)

634
src/switch-network.cpp Normal file
View File

@@ -0,0 +1,634 @@
/*
Most of this code is based on https://github.com/Palakis/obs-websocket
*/
#include <QtWidgets/QMainWindow>
#include <QtConcurrent/QtConcurrent>
#include <QTime>
#include <QMessageBox>
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
#define PARAM_SERVER_ENABLE "ServerEnabled"
#define PARAM_SERVER_PORT "ServerPort"
#define PARAM_LOCKTOIPV4 "LockToIPv4"
#define PARAM_CLIENT_ENABLE "ClientEnabled"
#define PARAM_CLIENT_PORT "ClientPort"
#define PARAM_ADDRESS "Address"
#define PARAM_CLIENT_SENDALL "SendAll"
#define RECONNECT_DELAY 10
#define SCENE_ENTRY "scene"
#define TRANSITION_ENTRY "transition"
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;
NetworkConfig::NetworkConfig()
: ServerEnabled(false),
ServerPort(55555),
LockToIPv4(false),
ClientEnabled(false),
Address(""),
ClientPort(55555),
SendAll(true)
{
}
void NetworkConfig::Load(obs_data_t *obj)
{
SetDefaults(obj);
ServerEnabled = obs_data_get_bool(obj, PARAM_SERVER_ENABLE);
ServerPort = obs_data_get_int(obj, PARAM_SERVER_PORT);
LockToIPv4 = obs_data_get_bool(obj, PARAM_LOCKTOIPV4);
ClientEnabled = obs_data_get_bool(obj, PARAM_CLIENT_ENABLE);
Address = obs_data_get_string(obj, PARAM_ADDRESS);
ClientPort = obs_data_get_int(obj, PARAM_CLIENT_PORT);
SendAll = obs_data_get_bool(obj, PARAM_CLIENT_SENDALL);
}
void NetworkConfig::Save(obs_data_t *obj)
{
obs_data_set_bool(obj, PARAM_SERVER_ENABLE, ServerEnabled);
obs_data_set_int(obj, PARAM_SERVER_PORT, ServerPort);
obs_data_set_bool(obj, PARAM_LOCKTOIPV4, LockToIPv4);
obs_data_set_bool(obj, PARAM_CLIENT_ENABLE, ClientEnabled);
obs_data_set_string(obj, PARAM_ADDRESS, Address.c_str());
obs_data_set_int(obj, PARAM_CLIENT_PORT, ClientPort);
obs_data_set_bool(obj, PARAM_CLIENT_SENDALL, SendAll);
}
void NetworkConfig::SetDefaults(obs_data_t *obj)
{
obs_data_set_default_bool(obj, PARAM_SERVER_ENABLE, ServerEnabled);
obs_data_set_default_int(obj, PARAM_SERVER_PORT, ServerPort);
obs_data_set_default_bool(obj, PARAM_LOCKTOIPV4, LockToIPv4);
obs_data_set_default_bool(obj, PARAM_CLIENT_ENABLE, ClientEnabled);
obs_data_set_default_string(obj, PARAM_ADDRESS, Address.c_str());
obs_data_set_default_int(obj, PARAM_CLIENT_PORT, ClientPort);
obs_data_set_default_bool(obj, PARAM_CLIENT_SENDALL, SendAll);
}
std::string NetworkConfig::GetClientUri()
{
return "ws://" + Address + ":" + std::to_string(ClientPort);
}
WSServer::WSServer()
: QObject(nullptr), _connections(), _clMutex(QMutex::Recursive)
{
_server.get_alog().clear_channels(
websocketpp::log::alevel::frame_header |
websocketpp::log::alevel::frame_payload |
websocketpp::log::alevel::control);
_server.init_asio();
#ifndef _WIN32
_server.set_reuse_addr(true);
#endif
_server.set_open_handler(bind(&WSServer::onOpen, this, ::_1));
_server.set_close_handler(bind(&WSServer::onClose, this, ::_1));
_server.set_message_handler(
bind(&WSServer::onMessage, this, ::_1, ::_2));
}
WSServer::~WSServer()
{
stop();
}
void WSServer::start(quint16 port, bool lockToIPv4)
{
if (_server.is_listening() &&
(port == _serverPort && _lockToIPv4 == lockToIPv4)) {
blog(LOG_INFO,
"WSServer::start: server already on this port and protocol mode. no restart needed");
return;
}
if (_server.is_listening()) {
stop();
}
_server.reset();
_serverPort = port;
_lockToIPv4 = lockToIPv4;
websocketpp::lib::error_code errorCode;
if (lockToIPv4) {
blog(LOG_INFO, "WSServer::start: Locked to IPv4 bindings");
_server.listen(websocketpp::lib::asio::ip::tcp::v4(),
_serverPort, errorCode);
} else {
blog(LOG_INFO, "WSServer::start: Not locked to IPv4 bindings");
_server.listen(_serverPort, errorCode);
}
if (errorCode) {
std::string errorCodeMessage = errorCode.message();
blog(LOG_INFO, "server: listen failed: %s",
errorCodeMessage.c_str());
obs_frontend_push_ui_translation(obs_module_get_string);
QString errorTitle = tr("AdvSceneSwitcher.windowTitle");
QString errorMessage =
tr("AdvSceneSwitcher.networkTab.startFailed.message")
.arg(_serverPort)
.arg(errorCodeMessage.c_str());
obs_frontend_pop_ui_translation();
QMainWindow *mainWindow = reinterpret_cast<QMainWindow *>(
obs_frontend_get_main_window());
QMessageBox::warning(mainWindow, errorTitle, errorMessage);
return;
}
switcher->serverStatus = ServerStatus::STARTING;
_server.start_accept();
QtConcurrent::run([=]() {
blog(LOG_INFO, "WSServer::start: io thread started");
_server.run();
blog(LOG_INFO, "WSServer::start: io thread exited");
});
switcher->serverStatus = ServerStatus::RUNNING;
blog(LOG_INFO,
"WSServer::start: server started successfully on port %d",
_serverPort);
}
void WSServer::stop()
{
if (!_server.is_listening()) {
return;
}
_server.stop_listening();
for (connection_hdl hdl : _connections) {
websocketpp::lib::error_code ec;
_server.close(hdl, websocketpp::close::status::going_away,
"Server stopping", ec);
}
_threadPool.waitForDone();
while (_connections.size() > 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
switcher->serverStatus = ServerStatus::NOT_RUNNING;
blog(LOG_INFO, "server stopped successfully");
}
void WSServer::sendMessage(OBSWeakSource scene, OBSWeakSource transition)
{
if (!scene) {
return;
}
OBSData data = obs_data_create();
obs_data_set_string(data, SCENE_ENTRY,
GetWeakSourceName(scene).c_str());
obs_data_set_string(data, TRANSITION_ENTRY,
GetWeakSourceName(transition).c_str());
std::string message = obs_data_get_json(data);
obs_data_release(data);
for (connection_hdl hdl : _connections) {
websocketpp::lib::error_code ec;
_server.send(hdl, message, websocketpp::frame::opcode::text,
ec);
if (ec) {
std::string errorCodeMessage = ec.message();
blog(LOG_INFO, "server: send failed: %s",
errorCodeMessage.c_str());
}
}
if (switcher->verbose) {
blog(LOG_INFO, "server sent message:\n%s", message.c_str());
}
}
void WSServer::onOpen(connection_hdl hdl)
{
QMutexLocker locker(&_clMutex);
_connections.insert(hdl);
locker.unlock();
QString clientIp = getRemoteEndpoint(hdl);
blog(LOG_INFO, "new client connection from %s",
clientIp.toUtf8().constData());
}
std::string processMessage(std::string payload)
{
auto config = switcher->networkConfig;
std::string msgContainer(payload);
const char *msg = msgContainer.c_str();
OBSData data = obs_data_create_from_json(msg);
if (!data) {
blog(LOG_ERROR, "invalid JSON payload received for '%s'", msg);
return "invalid JSON payload";
}
if (!obs_data_has_user_value(data, SCENE_ENTRY) ||
!obs_data_has_user_value(data, TRANSITION_ENTRY)) {
return "missing request parameters";
}
std::string sceneName = obs_data_get_string(data, SCENE_ENTRY);
std::string transitionName =
obs_data_get_string(data, TRANSITION_ENTRY);
obs_data_release(data);
auto scene = GetWeakSourceByName(sceneName.c_str());
if (!scene) {
return "ignoring request - unknown scene '" + sceneName + "'";
}
std::string ret = "message ok";
auto transition = GetWeakTransitionByName(transitionName.c_str());
if (switcher->verbose && !transition) {
ret += " - ignoring invalid transition: '" + transitionName +
"'";
}
switchScene(scene, transition, switcher->tansitionOverrideOverride,
switcher->adjustActiveTransitionType, switcher->verbose);
return ret;
}
void WSServer::onMessage(connection_hdl hdl, server::message_ptr message)
{
UNUSED_PARAMETER(hdl);
auto opcode = message->get_opcode();
if (opcode != websocketpp::frame::opcode::text) {
return;
}
QtConcurrent::run(&_threadPool, [=]() {
if (message->get_payload() != "message ok") {
blog(LOG_WARNING, "received response: %s",
message->get_payload().c_str());
}
});
}
void WSServer::onClose(connection_hdl hdl)
{
QMutexLocker locker(&_clMutex);
_connections.erase(hdl);
locker.unlock();
auto conn = _server.get_con_from_hdl(hdl);
auto localCloseCode = conn->get_local_close_code();
if (localCloseCode != websocketpp::close::status::going_away) {
QString clientIp = getRemoteEndpoint(hdl);
blog(LOG_INFO, "client %s disconnected",
clientIp.toUtf8().constData());
}
}
QString WSServer::getRemoteEndpoint(connection_hdl hdl)
{
auto conn = _server.get_con_from_hdl(hdl);
return QString::fromStdString(conn->get_remote_endpoint());
}
WSClient::WSClient() : QObject(nullptr)
{
_client.get_alog().clear_channels(
websocketpp::log::alevel::frame_header |
websocketpp::log::alevel::frame_payload |
websocketpp::log::alevel::control);
_client.init_asio();
#ifndef _WIN32
_client.set_reuse_addr(true);
#endif
_client.set_open_handler(bind(&WSClient::onOpen, this, ::_1));
_client.set_fail_handler(bind(&WSClient::onFail, this, ::_1));
_client.set_message_handler(
bind(&WSClient::onMessage, this, ::_1, ::_2));
_client.set_close_handler(bind(&WSClient::onClose, this, ::_1));
}
WSClient::~WSClient()
{
disconnect();
}
void WSClient::connectThread()
{
while (_retry) {
_client.reset();
switcher->clientStatus = ClientStatus::CONNECTING;
// Create a connection to the given URI and queue it for connection once
// the event loop starts
websocketpp::lib::error_code ec;
client::connection_ptr con = _client.get_connection(_uri, ec);
if (ec) {
_failMsg = ec.message();
blog(LOG_INFO, "client: connect failed: %s",
_failMsg.c_str());
switcher->clientStatus = ClientStatus::FAIL;
} else {
_client.connect(con);
_connection = connection_hdl(con);
// Start the ASIO io_service run loop
blog(LOG_INFO, "WSClient::connect: io thread started");
_connected = true;
_client.run();
_connected = false;
blog(LOG_INFO, "WSClient::connect: io thread exited");
}
if (_retry) {
std::unique_lock<std::mutex> lck(_waitMtx);
blog(LOG_INFO,
"trying to reconnect to %s in %d seconds.",
_uri.c_str(), RECONNECT_DELAY);
_cv.wait_for(lck,
std::chrono::seconds(RECONNECT_DELAY));
}
}
}
void WSClient::connect(std::string uri)
{
disconnect();
_uri = uri;
_retry = true;
_thread = std::thread(&WSClient::connectThread, this);
switcher->clientStatus = ClientStatus::DISCONNECTED;
blog(LOG_INFO, "WSClient::connect: exited");
}
void WSClient::disconnect()
{
_retry = false;
websocketpp::lib::error_code ec;
_client.close(_connection, websocketpp::close::status::normal,
"Client stopping", ec);
{
std::unique_lock<std::mutex> waitLck(_waitMtx);
blog(LOG_INFO, "trying to reconnect to %s in %d seconds.",
_uri.c_str(), RECONNECT_DELAY);
_cv.notify_all();
}
while (_connected) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
_client.close(_connection, websocketpp::close::status::normal,
"Client stopping", ec);
}
if (_thread.joinable()) {
_thread.join();
}
}
void WSClient::onOpen(connection_hdl hdl)
{
UNUSED_PARAMETER(hdl);
blog(LOG_INFO, "connection to %s opened", _uri.c_str());
switcher->clientStatus = ClientStatus::CONNECTED;
}
void WSClient::onFail(connection_hdl hdl)
{
UNUSED_PARAMETER(hdl);
blog(LOG_INFO, "connection to %s failed", _uri.c_str());
}
void WSClient::onMessage(connection_hdl hdl, client::message_ptr message)
{
auto opcode = message->get_opcode();
if (opcode != websocketpp::frame::opcode::text) {
return;
}
std::string payload = message->get_payload();
std::string response = processMessage(payload);
websocketpp::lib::error_code errorCode;
_client.send(hdl, response, websocketpp::frame::opcode::text,
errorCode);
if (errorCode) {
std::string errorCodeMessage = errorCode.message();
blog(LOG_INFO, "client(response): send failed: %s",
errorCodeMessage.c_str());
}
if (switcher->verbose) {
blog(LOG_INFO, "client sent message:\n%s", response.c_str());
}
}
void WSClient::onClose(connection_hdl hdl)
{
UNUSED_PARAMETER(hdl);
blog(LOG_INFO, "client-connection to %s closed.", _uri.c_str());
switcher->clientStatus = ClientStatus::DISCONNECTED;
}
void SwitcherData::loadNetworkSettings(obs_data_t *obj)
{
networkConfig.Load(obj);
}
void SwitcherData::saveNetworkSwitches(obs_data_t *obj)
{
networkConfig.Save(obj);
if (!networkConfig.ServerEnabled) {
switcher->server.stop();
}
}
void AdvSceneSwitcher::setupNetworkTab()
{
ui->serverSettings->setChecked(switcher->networkConfig.ServerEnabled);
ui->serverPort->setValue(switcher->networkConfig.ServerPort);
ui->lockToIPv4->setChecked(switcher->networkConfig.LockToIPv4);
ui->clientSettings->setChecked(switcher->networkConfig.ClientEnabled);
ui->clientHostname->setText(switcher->networkConfig.Address.c_str());
ui->clientPort->setValue(switcher->networkConfig.ClientPort);
ui->restrictSend->setChecked(!switcher->networkConfig.SendAll);
QTimer *statusTimer = new QTimer(this);
connect(statusTimer, SIGNAL(timeout()), this,
SLOT(updateClientStatus()));
connect(statusTimer, SIGNAL(timeout()), this,
SLOT(updateServerStatus()));
statusTimer->start(500);
}
void AdvSceneSwitcher::on_serverSettings_toggled(bool on)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->networkConfig.ServerEnabled = on;
if (on) {
switcher->server.start(switcher->networkConfig.ServerPort,
switcher->networkConfig.LockToIPv4);
} else {
switcher->server.stop();
}
}
void AdvSceneSwitcher::on_serverPort_valueChanged(int value)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->networkConfig.ServerPort = value;
}
void AdvSceneSwitcher::on_lockToIPv4_stateChanged(int state)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->networkConfig.LockToIPv4 = state;
}
void AdvSceneSwitcher::on_serverRestart_clicked()
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->server.start(switcher->networkConfig.ServerPort,
switcher->networkConfig.LockToIPv4);
}
void AdvSceneSwitcher::updateServerStatus()
{
switch (switcher->serverStatus) {
case ServerStatus::NOT_RUNNING:
ui->serverStatus->setText(obs_module_text(
"AdvSceneSwitcher.networkTab.server.status.notRunning"));
break;
case ServerStatus::STARTING:
ui->serverStatus->setText(obs_module_text(
"AdvSceneSwitcher.networkTab.server.status.starting"));
break;
case ServerStatus::RUNNING:
ui->serverStatus->setText(obs_module_text(
"AdvSceneSwitcher.networkTab.server.status.running"));
break;
default:
break;
}
}
void AdvSceneSwitcher::on_clientSettings_toggled(bool on)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->networkConfig.ClientEnabled = on;
if (on) {
switcher->client.connect(
switcher->networkConfig.GetClientUri());
} else {
switcher->client.disconnect();
}
}
void AdvSceneSwitcher::on_clientHostname_textChanged(const QString &text)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->networkConfig.Address = text.toUtf8().constData();
}
void AdvSceneSwitcher::on_clientPort_valueChanged(int value)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->networkConfig.ClientPort = value;
}
void AdvSceneSwitcher::on_restrictSend_stateChanged(int state)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->networkConfig.SendAll = !state;
}
void AdvSceneSwitcher::on_clientReconnect_clicked()
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->client.connect(switcher->networkConfig.GetClientUri());
}
void AdvSceneSwitcher::updateClientStatus()
{
switch (switcher->clientStatus) {
case ClientStatus::DISCONNECTED:
ui->clientStatus->setText(obs_module_text(
"AdvSceneSwitcher.networkTab.client.status.disconnected"));
break;
case ClientStatus::CONNECTING:
ui->clientStatus->setText(obs_module_text(
"AdvSceneSwitcher.networkTab.client.status.connecting"));
break;
case ClientStatus::CONNECTED:
ui->clientStatus->setText(obs_module_text(
"AdvSceneSwitcher.networkTab.client.status.connected"));
break;
case ClientStatus::FAIL:
ui->clientStatus->setText(QString("Error: ") +
switcher->client.getFail().c_str());
break;
default:
break;
}
}

View File

@@ -52,8 +52,15 @@ void SwitcherData::checkRandom(bool &match, OBSWeakSource &scene,
continue;
}
if (r.scene == lastRandomScene && randomSwitches.size() != 1) {
continue;
if (randomSwitches.size() != 1) {
if (r.targetType == SwitchTargetType::Scene &&
r.scene == lastRandomScene) {
continue;
} else if (r.targetType ==
SwitchTargetType::SceneGroup &&
r.group && r.group == lastRandomSceneGroup) {
continue;
}
}
scene = r.getScene();
@@ -61,6 +68,7 @@ void SwitcherData::checkRandom(bool &match, OBSWeakSource &scene,
delay = (int)r.delay * 1000;
match = true;
lastRandomScene = r.scene;
lastRandomSceneGroup = r.group;
if (verbose) {
r.logMatch();
@@ -142,7 +150,7 @@ void RandomSwitch::load(obs_data_t *obj)
}
RandomSwitchWidget::RandomSwitchWidget(QWidget *parent, RandomSwitch *s)
: SwitchWidget(parent, s, false, false)
: SwitchWidget(parent, s, false, true)
{
delay = new QDoubleSpinBox();

View File

@@ -3,7 +3,9 @@
#include "headers/advanced-scene-switcher.hpp"
#include "headers/utility.hpp"
constexpr auto default_def_transition_dealy = 50;
bool DefaultSceneTransition::pause = false;
unsigned int DefaultSceneTransition::delay = default_def_transition_dealy;
void AdvSceneSwitcher::on_transitionsAdd_clicked()
{
@@ -180,12 +182,41 @@ void AdvSceneSwitcher::on_transitionOverridecheckBox_stateChanged(int state)
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
if (!state) {
switcher->tansitionOverrideOverride = false;
} else {
switcher->tansitionOverrideOverride = true;
if (!state && !switcher->adjustActiveTransitionType) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError"));
ui->adjustActiveTransitionType->setChecked(true);
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->tansitionOverrideOverride = state;
}
void AdvSceneSwitcher::on_adjustActiveTransitionType_stateChanged(int state)
{
if (loading) {
return;
}
// This option only makes sense if we are allowed to use transition overrides
if (!state && !switcher->tansitionOverrideOverride) {
DisplayMessage(obs_module_text(
"AdvSceneSwitcher.transitionTab.transitionBehaviorSelectionError"));
ui->transitionOverridecheckBox->setChecked(true);
}
std::lock_guard<std::mutex> lock(switcher->m);
switcher->adjustActiveTransitionType = state;
}
void AdvSceneSwitcher::defTransitionDelayValueChanged(int value)
{
if (loading) {
return;
}
std::lock_guard<std::mutex> lock(switcher->m);
DefaultSceneTransition::delay = value;
}
obs_weak_source_t *getNextTransition(obs_weak_source_t *scene1,
@@ -239,7 +270,8 @@ void restoreTransitionOverride(obs_source_t *scene, transitionData td)
void setNextTransition(OBSWeakSource &targetScene, obs_source_t *currentSource,
OBSWeakSource &transition,
bool &transitionOverrideOverride, transitionData &td)
bool transitionOverrideOverride,
bool adjustActiveTransitionType, transitionData &td)
{
obs_weak_source_t *currentScene =
obs_source_get_weak_source(currentSource);
@@ -254,7 +286,7 @@ void setNextTransition(OBSWeakSource &targetScene, obs_source_t *currentSource,
nextTransition = obs_weak_source_get_source(transition);
}
if (nextTransition) {
if (nextTransition && adjustActiveTransitionType) {
obs_frontend_set_current_transition(nextTransition);
}
@@ -322,6 +354,12 @@ void SwitcherData::saveSceneTransitions(obs_data_t *obj)
obs_data_set_bool(obj, "tansitionOverrideOverride",
switcher->tansitionOverrideOverride);
obs_data_set_bool(obj, "adjustActiveTransitionType",
switcher->adjustActiveTransitionType);
obs_data_set_default_int(obj, "defTransitionDelay",
default_def_transition_dealy);
obs_data_set_int(obj, "defTransitionDelay",
DefaultSceneTransition::delay);
}
void SwitcherData::loadSceneTransitions(obs_data_t *obj)
@@ -374,6 +412,10 @@ void SwitcherData::loadSceneTransitions(obs_data_t *obj)
switcher->tansitionOverrideOverride =
obs_data_get_bool(obj, "tansitionOverrideOverride");
switcher->adjustActiveTransitionType =
obs_data_get_bool(obj, "adjustActiveTransitionType");
DefaultSceneTransition::delay =
obs_data_get_int(obj, "defTransitionDelay");
}
void AdvSceneSwitcher::setupTransitionsTab()
@@ -412,6 +454,26 @@ void AdvSceneSwitcher::setupTransitionsTab()
ui->transitionOverridecheckBox->setChecked(
switcher->tansitionOverrideOverride);
ui->adjustActiveTransitionType->setChecked(
switcher->adjustActiveTransitionType);
QSpinBox *defTransitionDelay = new QSpinBox();
defTransitionDelay->setSuffix("ms");
defTransitionDelay->setMinimum(50);
defTransitionDelay->setMaximum(10000);
defTransitionDelay->setValue(DefaultSceneTransition::delay);
defTransitionDelay->setToolTip(obs_module_text(
"AdvSceneSwitcher.transitionTab.defaultTransition.delay.help"));
QWidget::connect(defTransitionDelay, SIGNAL(valueChanged(int)), this,
SLOT(defTransitionDelayValueChanged(int)));
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
{"{{defTransitionDelay}}", defTransitionDelay}};
placeWidgets(
obs_module_text(
"AdvSceneSwitcher.transitionTab.defaultTransition.delay"),
ui->defTransitionDelayLayout, widgetPlaceholders);
}
bool SceneTransition::initialized()
@@ -527,7 +589,7 @@ bool DefaultSceneTransition::checkMatch(OBSWeakSource currentScene)
return scene == currentScene;
}
void setTransitionDelayed(OBSWeakSource transition)
void setTransitionDelayed(OBSWeakSource transition, unsigned int delay)
{
// A hardcoded delay of 50 ms before switching transition type is
// necessary due to OBS_FRONTEND_EVENT_SCENE_CHANGED seemingly firing a
@@ -535,14 +597,14 @@ void setTransitionDelayed(OBSWeakSource transition)
//
// The same is to be the case for OBS_FRONTEND_EVENT_TRANSITION_STOPPED.
//
// 50 ms was chosen as it seems to avoid the problem mentioned above and
// becuase that is the minimum value which can be chosen for the scene
// switcher's check interval.
// 50 ms was chosen as a default value as it seems to avoid the problem
// mentioned above and becuase that is the minimum value which can be
// chosen for the scene switcher's check interval.
// Thus it can be made sure that the delayed setting of the transition
// does not interfere with any new scene changes triggered by the scene
// switcher
std::this_thread::sleep_for(std::chrono::milliseconds(50));
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
obs_source_t *transitionSource = obs_weak_source_get_source(transition);
obs_frontend_set_current_transition(transitionSource);
@@ -552,6 +614,6 @@ void setTransitionDelayed(OBSWeakSource transition)
void DefaultSceneTransition::setTransition()
{
std::thread t;
t = std::thread(setTransitionDelayed, transition);
t = std::thread(setTransitionDelayed, transition, delay);
t.detach();
}

View File

@@ -176,7 +176,7 @@ void checkWindowTitleSwitchDirect(WindowSwitch &s,
OBSWeakSource &scene,
OBSWeakSource &transition)
{
bool focus = s.window == currentWindowTitle;
bool focus = (!s.focus || s.window == currentWindowTitle);
bool fullscreen = (!s.fullscreen || isFullscreen(s.window));
bool max = (!s.maximized || isMaximized(s.window));
@@ -202,7 +202,7 @@ void checkWindowTitleSwitchRegex(WindowSwitch &s,
} catch (const std::regex_error &) {
}
bool focus = window == currentWindowTitle;
bool focus = (!s.focus || window == currentWindowTitle);
bool fullscreen = (!s.fullscreen || isFullscreen(window));
bool max = (!s.maximized || isMaximized(window));

View File

@@ -1,3 +1,4 @@
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <util/platform.h>
#include "../headers/advanced-scene-switcher.hpp"