mirror of
https://github.com/WarmUpTill/SceneSwitcher.git
synced 2026-08-29 04:36:21 -05:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
beb482bd5a | ||
|
|
e561d378dd | ||
|
|
73ce88e2f0 | ||
|
|
dfccc4b16d | ||
|
|
b0ef1fee71 | ||
|
|
1ef8ce4b4a | ||
|
|
1bf4bff014 | ||
|
|
4b6e00b2b2 | ||
|
|
c99cbb0c0f | ||
|
|
2a87334d38 | ||
|
|
1a4724ff21 | ||
|
|
59d8fb5d28 | ||
|
|
8823c05aeb | ||
|
|
2d0a52f8d1 | ||
|
|
1273446ce5 | ||
|
|
177ca4aea2 | ||
|
|
7526cbe1a3 | ||
|
|
e4bb034303 | ||
|
|
78e14df620 | ||
|
|
34f07bfb07 | ||
|
|
ee74d39424 |
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
@@ -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 }}
|
||||
@@ -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
|
||||
|
||||
15
.github/workflows/clang-format.yml
vendored
15
.github/workflows/clang-format.yml
vendored
@@ -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
6
.gitmodules
vendored
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -73,6 +80,7 @@ set(advanced-scene-switcher_HEADERS
|
||||
src/headers/switch-transitions.hpp
|
||||
src/headers/switch-window.hpp
|
||||
src/headers/switch-sequence.hpp
|
||||
src/headers/switch-video.hpp
|
||||
src/headers/switch-generic.hpp
|
||||
src/headers/version.h
|
||||
)
|
||||
@@ -93,12 +101,14 @@ 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
|
||||
src/switch-random.cpp
|
||||
src/switch-time.cpp
|
||||
src/switch-audio.cpp
|
||||
src/switch-video.cpp
|
||||
src/switch-generic.cpp
|
||||
src/curl-helper.cpp
|
||||
src/volume-control.cpp
|
||||
@@ -111,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)
|
||||
|
||||
@@ -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"
|
||||
@@ -49,6 +50,7 @@ AdvSceneSwitcher.generalTab.priority.windowTitle="Fenstername"
|
||||
AdvSceneSwitcher.generalTab.priority.media="Medien"
|
||||
AdvSceneSwitcher.generalTab.priority.time="Zeit"
|
||||
AdvSceneSwitcher.generalTab.priority.audio="Audio"
|
||||
AdvSceneSwitcher.generalTab.priority.video="Video"
|
||||
|
||||
; Transition Tab
|
||||
AdvSceneSwitcher.transitionTab.title="Szenenübergänge"
|
||||
@@ -59,6 +61,8 @@ AdvSceneSwitcher.transitionTab.defaultTransition="Ändere den Szenenübergang we
|
||||
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"
|
||||
@@ -185,6 +189,8 @@ AdvSceneSwitcher.sceneSequenceTab.loadSuccess="Laden der Einstellungen war erfol
|
||||
AdvSceneSwitcher.sceneSequenceTab.fileType="Text Dateien (*.txt)"
|
||||
AdvSceneSwitcher.sceneSequenceTab.interruptible="kann unterbrochen werden"
|
||||
AdvSceneSwitcher.sceneSequenceTab.interruptibleHint="Andere Szenenwechselmethoden können diese Sequenz unterbrechen"
|
||||
AdvSceneSwitcher.sceneSequenceTab.extendEdit="Szenenfolge erweitern"
|
||||
AdvSceneSwitcher.sceneSequenceTab.extendEntry="Nach {{delay}} {{delayUnits}} wechsle zu {{scenes}} mit {{transitions}}"
|
||||
AdvSceneSwitcher.sceneSequenceTab.entry="Wenn {{startScenes}} aktiv ist wechsle zu {{scenes}} nach {{delay}} {{delayUnits}} mit {{transitions}} {{interruptible}}"
|
||||
AdvSceneSwitcher.sceneSequenceTab.help="Dieser Tab ermöglicht es automatisiert zu einer anderen Szene zu wechseln, nachdem eine Szene für die konfigurierte Zeit aktiv war.\nZum Beispiel kann so automatisch zwischen zwei Szenen hin und her gewechselt werden.\n\nKlicke auf das markierte Plus Symbol, um einen neuen Eintrag hinzuzufügen."
|
||||
|
||||
@@ -192,11 +198,47 @@ 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."
|
||||
|
||||
; Video Tab
|
||||
AdvSceneSwitcher.videoTab.title="Video"
|
||||
AdvSceneSwitcher.videoTab.getScreenshot="Screenshot für ausgewählten Eintrag erstellen"
|
||||
AdvSceneSwitcher.videoTab.getScreenshotHelp="Erstellt einen Screenshot für die Video Quelle des ausgewählten Eintrag und setzt diesen als Ziel Bild"
|
||||
AdvSceneSwitcher.videoTab.condition.match="genau übereinstimmt mit"
|
||||
AdvSceneSwitcher.videoTab.condition.match.tooltip="Eine genaue Übereinstimmung setzt voraus, dass sowohl Zielquelle als auch das Bild die gleiche Auflösung besitzen.\nZusätzlich muss jeder einzelne Pixel zwischen beiden Bildquellen übereinstimmen weshalb Bildformate, welche das Bild komprimieren (z. B. .JPG) nicht zu empfehlen sind."
|
||||
AdvSceneSwitcher.videoTab.condition.differ="nicht übereinstimmt mit"
|
||||
AdvSceneSwitcher.videoTab.condition.hasNotChanged="sich nicht verändert"
|
||||
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"
|
||||
@@ -256,6 +298,7 @@ AdvSceneSwitcher.currentTransition="Aktueller Szenenübergang"
|
||||
AdvSceneSwitcher.selectTransition="--Szenenübergang auswählen--"
|
||||
AdvSceneSwitcher.selectWindow="--Fenster auswählen--"
|
||||
AdvSceneSwitcher.selectAudioSource="--Audio Quelle auswählen--"
|
||||
AdvSceneSwitcher.selectVideoSource="--Video Quelle auswählen--"
|
||||
AdvSceneSwitcher.selectMediaSource="--Medien Quelle auswählen--"
|
||||
AdvSceneSwitcher.selectProcess="--Prozess auswählen--"
|
||||
AdvSceneSwitcher.enterPath="--Pfad eingeben--"
|
||||
|
||||
@@ -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)"
|
||||
@@ -49,6 +50,7 @@ AdvSceneSwitcher.generalTab.priority.windowTitle="Window Title"
|
||||
AdvSceneSwitcher.generalTab.priority.media="Media"
|
||||
AdvSceneSwitcher.generalTab.priority.time="Time"
|
||||
AdvSceneSwitcher.generalTab.priority.audio="Audio"
|
||||
AdvSceneSwitcher.generalTab.priority.video="Video"
|
||||
|
||||
; Transition Tab
|
||||
AdvSceneSwitcher.transitionTab.title="Transition"
|
||||
@@ -59,6 +61,8 @@ AdvSceneSwitcher.transitionTab.defaultTransition="Change transition if scene is
|
||||
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"
|
||||
@@ -194,11 +198,47 @@ 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."
|
||||
|
||||
; Video Tab
|
||||
AdvSceneSwitcher.videoTab.title="Video"
|
||||
AdvSceneSwitcher.videoTab.getScreenshot="Get screenshot for selected entry"
|
||||
AdvSceneSwitcher.videoTab.getScreenshotHelp="Get Screenshot of the currently selected entry's video source and automatically set it as the target image"
|
||||
AdvSceneSwitcher.videoTab.condition.match="exactly matches"
|
||||
AdvSceneSwitcher.videoTab.condition.match.tooltip="An exact match requires the target and the source image to be of the same resolution.\nAdditionally every single pixel needs to match, which is why use of image formats which use compression (e.g. .JPG) is not recommended!"
|
||||
AdvSceneSwitcher.videoTab.condition.differ="does not match"
|
||||
AdvSceneSwitcher.videoTab.condition.hasNotChanged="has not changed"
|
||||
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"
|
||||
@@ -257,6 +297,7 @@ AdvSceneSwitcher.currentTransition="Current Transition"
|
||||
AdvSceneSwitcher.selectTransition="--select transition--"
|
||||
AdvSceneSwitcher.selectWindow="--select window--"
|
||||
AdvSceneSwitcher.selectAudioSource="--select audio source--"
|
||||
AdvSceneSwitcher.selectVideoSource="--select video source--"
|
||||
AdvSceneSwitcher.selectMediaSource="--select media source--"
|
||||
AdvSceneSwitcher.selectProcess="--select process--"
|
||||
AdvSceneSwitcher.enterPath="--enter path--"
|
||||
|
||||
1
deps/asio
vendored
Submodule
1
deps/asio
vendored
Submodule
Submodule deps/asio added at 93f87fbdbf
1
deps/websocketpp
vendored
Submodule
1
deps/websocketpp
vendored
Submodule
Submodule deps/websocketpp added at 1b11fd3015
@@ -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">
|
||||
@@ -737,6 +761,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 +2852,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
@@ -3084,6 +3111,369 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="videoTab">
|
||||
<attribute name="title">
|
||||
<string>AdvSceneSwitcher.videoTab.title</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_29">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_22">
|
||||
<item row="0" column="0">
|
||||
<widget class="QListWidget" name="videoSwitches">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="videoHelp">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.videoTab.help</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_58">
|
||||
<item>
|
||||
<widget class="QPushButton" name="videoAdd">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string notr="true">addIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="videoRemove">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string notr="true">removeIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_124">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>5</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_43">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_125">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>5</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="videoUp">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>../../../forms/images/up.svg</normaloff>../../../forms/images/up.svg</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string>upArrowIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="videoDown">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>22</width>
|
||||
<height>22</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>../../../forms/images/down.svg</normaloff>../../../forms/images/down.svg</iconset>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="themeID" stdset="0">
|
||||
<string>downArrowIconSmall</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="getScreenshot">
|
||||
<property name="text">
|
||||
<string>AdvSceneSwitcher.videoTab.getScreenshot</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_62">
|
||||
<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>
|
||||
</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>
|
||||
|
||||
@@ -59,10 +59,13 @@ void AdvSceneSwitcher::loadUI()
|
||||
setupFileTab();
|
||||
setupTimeTab();
|
||||
setupAudioTab();
|
||||
setupVideoTab();
|
||||
setupNetworkTab();
|
||||
setupSceneGroupTab();
|
||||
setupTriggerTab();
|
||||
|
||||
setTabOrder();
|
||||
restoreWindowGeo();
|
||||
|
||||
loading = false;
|
||||
}
|
||||
@@ -119,8 +122,6 @@ void AdvSceneSwitcher::populateSceneSelection(QComboBox *sel, bool addPrevious,
|
||||
std::string selectText,
|
||||
bool selectable)
|
||||
{
|
||||
sel->clear();
|
||||
|
||||
if (addSelect) {
|
||||
if (selectText.empty()) {
|
||||
addSelectionEntry(
|
||||
@@ -235,6 +236,42 @@ void AdvSceneSwitcher::populateAudioSelection(QComboBox *sel, bool addSelect)
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::populateVideoSelection(QComboBox *sel, bool addScenes,
|
||||
bool addSelect)
|
||||
{
|
||||
if (addSelect) {
|
||||
addSelectionEntry(
|
||||
sel,
|
||||
obs_module_text("AdvSceneSwitcher.selectVideoSource"),
|
||||
false,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.invaildEntriesWillNotBeSaved"));
|
||||
}
|
||||
|
||||
auto sourceEnum = [](void *data, obs_source_t *source) -> bool /* -- */
|
||||
{
|
||||
std::vector<std::string> *list =
|
||||
reinterpret_cast<std::vector<std::string> *>(data);
|
||||
uint32_t flags = obs_source_get_output_flags(source);
|
||||
std::string test = obs_source_get_name(source);
|
||||
if ((flags & (OBS_SOURCE_VIDEO | OBS_SOURCE_ASYNC)) != 0) {
|
||||
list->push_back(obs_source_get_name(source));
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
std::vector<std::string> videoSources;
|
||||
obs_enum_sources(sourceEnum, &videoSources);
|
||||
sort(videoSources.begin(), videoSources.end());
|
||||
for (std::string &source : videoSources) {
|
||||
sel->addItem(source.c_str());
|
||||
}
|
||||
|
||||
if (addScenes) {
|
||||
populateSceneSelection(sel, false, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::populateMediaSelection(QComboBox *sel, bool addSelect)
|
||||
{
|
||||
if (addSelect) {
|
||||
@@ -511,6 +548,9 @@ void SwitcherData::Thread()
|
||||
lock.unlock();
|
||||
|
||||
if (match) {
|
||||
if (networkConfig.ServerEnabled) {
|
||||
server.sendMessage(scene, transition);
|
||||
}
|
||||
switchScene(scene, transition,
|
||||
tansitionOverrideOverride);
|
||||
}
|
||||
@@ -563,6 +603,9 @@ bool SwitcherData::checkForMatch(OBSWeakSource &scene,
|
||||
case audio_func:
|
||||
checkAudioSwitch(match, scene, transition);
|
||||
break;
|
||||
case video_func:
|
||||
checkVideoSwitch(match, scene, transition);
|
||||
break;
|
||||
}
|
||||
|
||||
if (switcher->stop) {
|
||||
@@ -614,6 +657,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()
|
||||
@@ -628,6 +680,9 @@ void SwitcherData::Stop()
|
||||
|
||||
writeToStatusFile("Advanced Scene Switcher stopped");
|
||||
}
|
||||
|
||||
server.stop();
|
||||
client.disconnect();
|
||||
}
|
||||
|
||||
bool SwitcherData::sceneChangedDuringWait()
|
||||
@@ -676,6 +731,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)
|
||||
|
||||
128
src/general.cpp
128
src/general.cpp
@@ -4,7 +4,7 @@
|
||||
#include "headers/utility.hpp"
|
||||
#include "headers/version.h"
|
||||
|
||||
constexpr auto tab_count = 15;
|
||||
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) {
|
||||
@@ -326,9 +338,15 @@ int findTabIndex(QTabWidget *tabWidget, int pos)
|
||||
tabName = "audioTab";
|
||||
break;
|
||||
case 13:
|
||||
tabName = "sceneGroupTab";
|
||||
tabName = "videoTab";
|
||||
break;
|
||||
case 14:
|
||||
tabName = "networkTab";
|
||||
break;
|
||||
case 15:
|
||||
tabName = "sceneGroupTab";
|
||||
break;
|
||||
case 16:
|
||||
tabName = "sceneTriggerTab";
|
||||
break;
|
||||
}
|
||||
@@ -359,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) {
|
||||
@@ -383,22 +409,24 @@ void SwitcherData::loadSettings(obs_data_t *obj)
|
||||
return;
|
||||
}
|
||||
|
||||
switcher->loadSceneGroups(obj);
|
||||
switcher->loadWindowTitleSwitches(obj);
|
||||
switcher->loadScreenRegionSwitches(obj);
|
||||
switcher->loadPauseSwitches(obj);
|
||||
switcher->loadSceneSequenceSwitches(obj);
|
||||
switcher->loadSceneTransitions(obj);
|
||||
switcher->loadIdleSwitches(obj);
|
||||
switcher->loadExecutableSwitches(obj);
|
||||
switcher->loadRandomSwitches(obj);
|
||||
switcher->loadFileSwitches(obj);
|
||||
switcher->loadMediaSwitches(obj);
|
||||
switcher->loadTimeSwitches(obj);
|
||||
switcher->loadAudioSwitches(obj);
|
||||
switcher->loadSceneTriggers(obj);
|
||||
switcher->loadGeneralSettings(obj);
|
||||
switcher->loadHotkeys(obj);
|
||||
loadSceneGroups(obj);
|
||||
loadWindowTitleSwitches(obj);
|
||||
loadScreenRegionSwitches(obj);
|
||||
loadPauseSwitches(obj);
|
||||
loadSceneSequenceSwitches(obj);
|
||||
loadSceneTransitions(obj);
|
||||
loadIdleSwitches(obj);
|
||||
loadExecutableSwitches(obj);
|
||||
loadRandomSwitches(obj);
|
||||
loadFileSwitches(obj);
|
||||
loadMediaSwitches(obj);
|
||||
loadTimeSwitches(obj);
|
||||
loadAudioSwitches(obj);
|
||||
loadVideoSwitches(obj);
|
||||
loadNetworkSettings(obj);
|
||||
loadSceneTriggers(obj);
|
||||
loadGeneralSettings(obj);
|
||||
loadHotkeys(obj);
|
||||
}
|
||||
|
||||
void SwitcherData::saveSettings(obs_data_t *obj)
|
||||
@@ -420,6 +448,8 @@ void SwitcherData::saveSettings(obs_data_t *obj)
|
||||
saveMediaSwitches(obj);
|
||||
saveTimeSwitches(obj);
|
||||
saveAudioSwitches(obj);
|
||||
saveVideoSwitches(obj);
|
||||
saveNetworkSwitches(obj);
|
||||
saveSceneTriggers(obj);
|
||||
saveGeneralSettings(obj);
|
||||
saveHotkeys(obj);
|
||||
@@ -467,15 +497,15 @@ void SwitcherData::saveGeneralSettings(obs_data_t *obj)
|
||||
switcher->functionNamesByPriority[7]);
|
||||
obs_data_set_int(obj, "priority8",
|
||||
switcher->functionNamesByPriority[8]);
|
||||
obs_data_set_int(obj, "priority9",
|
||||
switcher->functionNamesByPriority[9]);
|
||||
|
||||
obs_data_set_int(obj, "threadPriority", switcher->threadPriority);
|
||||
|
||||
// After fresh install of OBS the vector can be empty
|
||||
// as save() might be called before first load()
|
||||
if (switcher->tabOrder.size() < tab_count) {
|
||||
switcher->tabOrder = std::vector<int>(tab_count);
|
||||
std::iota(switcher->tabOrder.begin(), switcher->tabOrder.end(),
|
||||
0);
|
||||
if (tabOrder.size() < tab_count) {
|
||||
resetTabOrder();
|
||||
}
|
||||
|
||||
obs_data_set_int(obj, "generalTabPos", switcher->tabOrder[0]);
|
||||
@@ -491,8 +521,16 @@ void SwitcherData::saveGeneralSettings(obs_data_t *obj)
|
||||
obs_data_set_int(obj, "idleTabPos", switcher->tabOrder[10]);
|
||||
obs_data_set_int(obj, "sequenceTabPos", switcher->tabOrder[11]);
|
||||
obs_data_set_int(obj, "audioTabPos", switcher->tabOrder[12]);
|
||||
obs_data_set_int(obj, "sceneGroupTabPos", switcher->tabOrder[13]);
|
||||
obs_data_set_int(obj, "triggerTabPos", switcher->tabOrder[14]);
|
||||
obs_data_set_int(obj, "videoTabPos", switcher->tabOrder[13]);
|
||||
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)
|
||||
@@ -555,6 +593,8 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
|
||||
(obs_data_get_int(obj, "priority7"));
|
||||
switcher->functionNamesByPriority[8] =
|
||||
(obs_data_get_int(obj, "priority8"));
|
||||
switcher->functionNamesByPriority[9] =
|
||||
(obs_data_get_int(obj, "priority9"));
|
||||
if (!switcher->prioFuncsValid()) {
|
||||
switcher->functionNamesByPriority[0] = (default_priority_0);
|
||||
switcher->functionNamesByPriority[1] = (default_priority_1);
|
||||
@@ -565,6 +605,7 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
|
||||
switcher->functionNamesByPriority[6] = (default_priority_6);
|
||||
switcher->functionNamesByPriority[7] = (default_priority_7);
|
||||
switcher->functionNamesByPriority[8] = (default_priority_8);
|
||||
switcher->functionNamesByPriority[9] = (default_priority_9);
|
||||
}
|
||||
|
||||
obs_data_set_default_int(obj, "threadPriority",
|
||||
@@ -584,8 +625,10 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
|
||||
obs_data_set_default_int(obj, "idleTabPos", 10);
|
||||
obs_data_set_default_int(obj, "sequenceTabPos", 11);
|
||||
obs_data_set_default_int(obj, "audioTabPos", 12);
|
||||
obs_data_set_default_int(obj, "sceneGroupTabPos", 13);
|
||||
obs_data_set_default_int(obj, "triggerTabPos", 14);
|
||||
obs_data_set_default_int(obj, "videoTabPos", 13);
|
||||
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")));
|
||||
@@ -613,10 +656,38 @@ void SwitcherData::loadGeneralSettings(obs_data_t *obj)
|
||||
(int)(obs_data_get_int(obj, "sequenceTabPos")));
|
||||
switcher->tabOrder.emplace_back(
|
||||
(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(
|
||||
(int)(obs_data_get_int(obj, "triggerTabPos")));
|
||||
|
||||
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()
|
||||
{
|
||||
auto tmp = tabOrder;
|
||||
std::sort(tmp.begin(), tmp.end());
|
||||
auto it = std::unique(tmp.begin(), tmp.end());
|
||||
return it == tmp.end();
|
||||
}
|
||||
|
||||
void SwitcherData::resetTabOrder()
|
||||
{
|
||||
tabOrder = std::vector<int>(tab_count);
|
||||
std::iota(switcher->tabOrder.begin(), switcher->tabOrder.end(), 0);
|
||||
}
|
||||
|
||||
void SwitcherData::checkNoMatchSwitch(bool &match, OBSWeakSource &scene,
|
||||
@@ -724,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) {
|
||||
@@ -765,6 +837,10 @@ void AdvSceneSwitcher::setupGeneralTab()
|
||||
s = obs_module_text(
|
||||
"AdvSceneSwitcher.generalTab.priority.audio");
|
||||
break;
|
||||
case video_func:
|
||||
s = obs_module_text(
|
||||
"AdvSceneSwitcher.generalTab.priority.video");
|
||||
break;
|
||||
}
|
||||
QString text(s.c_str());
|
||||
QListWidgetItem *item =
|
||||
|
||||
@@ -59,7 +59,10 @@ public:
|
||||
void setupAudioTab();
|
||||
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);
|
||||
@@ -82,6 +85,9 @@ public:
|
||||
bool addSelect = true);
|
||||
static void populateAudioSelection(QComboBox *sel,
|
||||
bool addSelect = true);
|
||||
static void populateVideoSelection(QComboBox *sel,
|
||||
bool addScenes = true,
|
||||
bool addSelect = true);
|
||||
static void populateMediaSelection(QComboBox *sel,
|
||||
bool addSelect = true);
|
||||
static void populateProcessSelection(QComboBox *sel,
|
||||
@@ -145,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();
|
||||
@@ -159,6 +166,7 @@ public slots:
|
||||
void on_defaultTransitionsUp_clicked();
|
||||
void on_defaultTransitionsDown_clicked();
|
||||
void on_transitionOverridecheckBox_stateChanged(int state);
|
||||
void defTransitionDelayValueChanged(int value);
|
||||
|
||||
void on_browseButton_clicked();
|
||||
void on_readFileCheckBox_stateChanged(int state);
|
||||
@@ -201,6 +209,24 @@ public slots:
|
||||
void on_audioDown_clicked();
|
||||
void on_audioFallback_toggled(bool on);
|
||||
|
||||
void on_videoAdd_clicked();
|
||||
void on_videoRemove_clicked();
|
||||
void on_videoUp_clicked();
|
||||
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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
144
src/headers/switch-network.hpp
Normal file
144
src/headers/switch-network.hpp
Normal file
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
Most of this code is based on https://github.com/Palakis/obs-websocket
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
// Due to iussues with asio the network functionality will be disabled on macOS
|
||||
#if !(__APPLE__)
|
||||
|
||||
#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.hpp>
|
||||
#include <websocketpp/server.hpp>
|
||||
|
||||
#include <websocketpp/config/asio_no_tls_client.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 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;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
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){};
|
||||
};
|
||||
|
||||
class WSClient : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WSClient(){};
|
||||
virtual ~WSClient(){};
|
||||
void connect(std::string uri){};
|
||||
void disconnect(){};
|
||||
std::string getFail() { return _failMsg; }
|
||||
|
||||
private:
|
||||
std::string _failMsg;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
enum class ServerStatus {
|
||||
NOT_RUNNING,
|
||||
STARTING,
|
||||
RUNNING,
|
||||
};
|
||||
|
||||
enum class ClientStatus {
|
||||
DISCONNECTED,
|
||||
CONNECTING,
|
||||
CONNECTED,
|
||||
FAIL,
|
||||
};
|
||||
@@ -19,6 +19,7 @@ enum class PauseTarget {
|
||||
Idle,
|
||||
Sequence,
|
||||
Audio,
|
||||
Video,
|
||||
};
|
||||
|
||||
struct PauseEntry : SceneSwitcherEntry {
|
||||
|
||||
@@ -50,15 +50,15 @@ class SequenceWidget : public SwitchWidget {
|
||||
|
||||
public:
|
||||
SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
|
||||
bool extendSequence = false,
|
||||
bool editExtendMode = false);
|
||||
bool extendSequence = false, bool editExtendMode = false,
|
||||
bool showExtendText = true);
|
||||
SceneSequenceSwitch *getSwitchData();
|
||||
void setSwitchData(SceneSequenceSwitch *s);
|
||||
|
||||
static void swapSwitchData(SequenceWidget *s1, SequenceWidget *s2);
|
||||
|
||||
void UpdateDelay();
|
||||
void UpdateExtendText();
|
||||
void UpdateWidgetStatus(bool showExtendText);
|
||||
void setExtendedSequenceStartScene();
|
||||
|
||||
private slots:
|
||||
|
||||
@@ -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);
|
||||
|
||||
98
src/headers/switch-video.hpp
Normal file
98
src/headers/switch-video.hpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
#include <QSpinBox>
|
||||
|
||||
#include "switch-generic.hpp"
|
||||
|
||||
constexpr auto video_func = 9;
|
||||
constexpr auto default_priority_9 = video_func;
|
||||
|
||||
enum class videoSwitchType {
|
||||
MATCH,
|
||||
DIFFER,
|
||||
HAS_NOT_CHANGED,
|
||||
};
|
||||
|
||||
class AdvSSScreenshotObj {
|
||||
public:
|
||||
AdvSSScreenshotObj(obs_source_t *source);
|
||||
~AdvSSScreenshotObj();
|
||||
|
||||
void Screenshot();
|
||||
void Download();
|
||||
void Copy();
|
||||
void MarkDone();
|
||||
|
||||
gs_texrender_t *texrender = nullptr;
|
||||
gs_stagesurf_t *stagesurf = nullptr;
|
||||
OBSWeakSource weakSource;
|
||||
std::string path;
|
||||
QImage image;
|
||||
uint32_t cx = 0;
|
||||
uint32_t cy = 0;
|
||||
|
||||
int stage = 0;
|
||||
|
||||
bool done = false;
|
||||
std::chrono::high_resolution_clock::time_point time;
|
||||
};
|
||||
|
||||
struct VideoSwitch : virtual SceneSwitcherEntry {
|
||||
static bool pause;
|
||||
|
||||
videoSwitchType condition = videoSwitchType::MATCH;
|
||||
OBSWeakSource videoSource = nullptr;
|
||||
std::string file = obs_module_text("AdvSceneSwitcher.enterPath");
|
||||
double duration = 0;
|
||||
bool ignoreInactiveSource = false;
|
||||
|
||||
std::unique_ptr<AdvSSScreenshotObj> screenshotData = nullptr;
|
||||
std::chrono::high_resolution_clock::time_point previousTime{};
|
||||
QImage matchImage;
|
||||
|
||||
std::chrono::milliseconds currentMatchDuration{};
|
||||
|
||||
const char *getType() { return "video"; }
|
||||
bool initialized();
|
||||
bool valid();
|
||||
void save(obs_data_t *obj);
|
||||
void load(obs_data_t *obj);
|
||||
void getScreenshot();
|
||||
bool loadImageFromFile();
|
||||
bool checkMatch();
|
||||
|
||||
VideoSwitch(){};
|
||||
friend void swap(VideoSwitch &first, VideoSwitch &second);
|
||||
};
|
||||
|
||||
class VideoSwitchWidget : public SwitchWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
VideoSwitchWidget(QWidget *parent, VideoSwitch *s);
|
||||
VideoSwitch *getSwitchData();
|
||||
void setSwitchData(VideoSwitch *s);
|
||||
|
||||
static void swapSwitchData(VideoSwitchWidget *as1,
|
||||
VideoSwitchWidget *as2);
|
||||
|
||||
void UpdatePreviewTooltip();
|
||||
void SetFilePath(const QString &text);
|
||||
|
||||
private slots:
|
||||
void SourceChanged(const QString &text);
|
||||
void ConditionChanged(int cond);
|
||||
void DurationChanged(double dur);
|
||||
void FilePathChanged();
|
||||
void BrowseButtonClicked();
|
||||
void IgnoreInactiveChanged(int state);
|
||||
|
||||
private:
|
||||
QComboBox *videoSources;
|
||||
QComboBox *condition;
|
||||
QDoubleSpinBox *duration;
|
||||
QLineEdit *filePath;
|
||||
QPushButton *browseButton;
|
||||
QCheckBox *ignoreInactiveSource;
|
||||
|
||||
VideoSwitch *switchData;
|
||||
};
|
||||
@@ -21,6 +21,8 @@
|
||||
#include "switch-transitions.hpp"
|
||||
#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";
|
||||
@@ -67,9 +69,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;
|
||||
@@ -113,12 +116,21 @@ 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;
|
||||
|
||||
std::vector<int> functionNamesByPriority = std::vector<int>{
|
||||
default_priority_0, default_priority_1, default_priority_2,
|
||||
default_priority_3, default_priority_4, default_priority_5,
|
||||
default_priority_6, default_priority_7, default_priority_8};
|
||||
default_priority_6, default_priority_7, default_priority_8,
|
||||
default_priority_9};
|
||||
|
||||
struct ThreadPrio {
|
||||
std::string name;
|
||||
@@ -154,6 +166,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();
|
||||
@@ -164,6 +180,9 @@ struct SwitcherData {
|
||||
|
||||
bool prioFuncsValid();
|
||||
|
||||
bool tabOrderValid();
|
||||
void resetTabOrder();
|
||||
|
||||
void writeSceneInfoToFile();
|
||||
void writeToStatusFile(QString status);
|
||||
|
||||
@@ -193,6 +212,8 @@ struct SwitcherData {
|
||||
OBSWeakSource &transition);
|
||||
void checkAudioSwitchFallback(OBSWeakSource &scene,
|
||||
OBSWeakSource &transition);
|
||||
void checkVideoSwitch(bool &match, OBSWeakSource &scene,
|
||||
OBSWeakSource &transition);
|
||||
void checkNoMatchSwitch(bool &match, OBSWeakSource &scene,
|
||||
OBSWeakSource &transition, int &sleep);
|
||||
void checkSwitchCooldown(bool &match);
|
||||
@@ -215,6 +236,8 @@ struct SwitcherData {
|
||||
void saveAudioSwitches(obs_data_t *obj);
|
||||
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);
|
||||
@@ -235,6 +258,8 @@ struct SwitcherData {
|
||||
void loadAudioSwitches(obs_data_t *obj);
|
||||
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);
|
||||
|
||||
|
||||
@@ -187,7 +187,7 @@ void frontEndActionThread(sceneTriggerAction action, double delay)
|
||||
break;
|
||||
default:
|
||||
blog(LOG_WARNING, "ignoring unexpected frontend action '%d'",
|
||||
action);
|
||||
static_cast<int>(action));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -255,7 +255,8 @@ void SceneTrigger::performAction()
|
||||
bool stop = triggerAction == sceneTriggerAction::STOP_SWITCHER;
|
||||
t = std::thread(statusThread, duration, stop);
|
||||
} else {
|
||||
blog(LOG_WARNING, "ignoring unkown action '%d'", triggerAction);
|
||||
blog(LOG_WARNING, "ignoring unkown action '%d'",
|
||||
static_cast<int>(triggerAction));
|
||||
}
|
||||
|
||||
t.detach();
|
||||
@@ -439,19 +440,12 @@ void AdvSceneSwitcher::setupTriggerTab()
|
||||
|
||||
void SceneTrigger::save(obs_data_t *obj)
|
||||
{
|
||||
obs_source_t *sceneSource = obs_weak_source_get_source(scene);
|
||||
const char *sceneName = obs_source_get_name(sceneSource);
|
||||
obs_data_set_string(obj, "scene", sceneName);
|
||||
obs_source_release(sceneSource);
|
||||
|
||||
obs_data_set_string(obj, "scene", GetWeakSourceName(scene).c_str());
|
||||
obs_data_set_int(obj, "triggerType", static_cast<int>(triggerType));
|
||||
obs_data_set_int(obj, "triggerAction", static_cast<int>(triggerAction));
|
||||
obs_data_set_double(obj, "duration", duration);
|
||||
|
||||
obs_source_t *source = obs_weak_source_get_source(audioSource);
|
||||
const char *audioSourceName = obs_source_get_name(source);
|
||||
obs_data_set_string(obj, "audioSource", audioSourceName);
|
||||
obs_source_release(source);
|
||||
obs_data_set_string(obj, "audioSource",
|
||||
GetWeakSourceName(audioSource).c_str());
|
||||
}
|
||||
|
||||
void SceneTrigger::load(obs_data_t *obj)
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -438,7 +447,7 @@ void swap(AudioSwitch &first, AudioSwitch &second)
|
||||
second.resetVolmeter();
|
||||
}
|
||||
|
||||
void populateConditionSelection(QComboBox *list)
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.audioTab.condition.above"));
|
||||
@@ -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)
|
||||
|
||||
644
src/switch-network.cpp
Normal file
644
src/switch-network.cpp
Normal file
@@ -0,0 +1,644 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
#if !(__APPLE__)
|
||||
|
||||
using websocketpp::lib::placeholders::_1;
|
||||
using websocketpp::lib::placeholders::_2;
|
||||
using websocketpp::lib::bind;
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
#if __APPLE__
|
||||
ui->clientSettings->setDisabled(true);
|
||||
ui->serverSettings->setDisabled(true);
|
||||
ui->networkTabWarningLabel->setText(
|
||||
obs_module_text("AdvSceneSwitcher.networkTab.DisabledWarning"));
|
||||
#endif
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,7 @@ void resetPause()
|
||||
IdleData::pause = false;
|
||||
SceneSequenceSwitch::pause = false;
|
||||
AudioSwitch::pause = false;
|
||||
VideoSwitch::pause = false;
|
||||
}
|
||||
|
||||
void setPauseTarget(PauseTarget &target, bool &verbose)
|
||||
@@ -139,6 +140,10 @@ void setPauseTarget(PauseTarget &target, bool &verbose)
|
||||
vblog(LOG_INFO, "pause audio switching");
|
||||
AudioSwitch::pause = true;
|
||||
break;
|
||||
case PauseTarget::Video:
|
||||
vblog(LOG_INFO, "pause video switching");
|
||||
VideoSwitch::pause = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,6 +345,7 @@ void populatePauseTargets(QComboBox *list)
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.sceneSequenceTab.title"));
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.audioTab.title"));
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.VideoTab.title"));
|
||||
}
|
||||
|
||||
PauseEntryWidget::PauseEntryWidget(QWidget *parent, PauseEntry *s)
|
||||
|
||||
@@ -55,15 +55,18 @@ void AdvSceneSwitcher::on_priorityDown_clicked()
|
||||
|
||||
bool SwitcherData::prioFuncsValid()
|
||||
{
|
||||
auto it = std::unique(functionNamesByPriority.begin(),
|
||||
functionNamesByPriority.end());
|
||||
bool wasUnique = (it == functionNamesByPriority.end());
|
||||
auto fNBPCopy = functionNamesByPriority;
|
||||
|
||||
std::sort(fNBPCopy.begin(), fNBPCopy.end());
|
||||
auto it = std::unique(fNBPCopy.begin(), fNBPCopy.end());
|
||||
bool wasUnique = (it == fNBPCopy.end());
|
||||
|
||||
if (!wasUnique) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int p : functionNamesByPriority) {
|
||||
if (p < 0 || p > 8) {
|
||||
if (p < 0 || p > 9) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -30,20 +30,10 @@ void AdvSceneSwitcher::on_sceneSequenceRemove_clicked()
|
||||
}
|
||||
|
||||
{
|
||||
// Might be in waiting state of sequence
|
||||
// causing invalid access after wakeup
|
||||
// thus we need to stop the main thread before delete
|
||||
bool wasRunning = !switcher->stop;
|
||||
switcher->Stop();
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
int idx = ui->sceneSequenceSwitches->currentRow();
|
||||
auto &switches = switcher->sceneSequenceSwitches;
|
||||
switches.erase(switches.begin() + idx);
|
||||
|
||||
if (wasRunning) {
|
||||
switcher->Start();
|
||||
}
|
||||
}
|
||||
|
||||
delete item;
|
||||
@@ -142,18 +132,8 @@ void AdvSceneSwitcher::on_sceneSequenceLoad_clicked()
|
||||
return;
|
||||
}
|
||||
|
||||
// Might be in waiting state of sequence
|
||||
// causing invalid access after wakeup
|
||||
// thus we need to stop the main thread before delete
|
||||
bool wasRunning = !switcher->stop;
|
||||
switcher->Stop();
|
||||
|
||||
switcher->loadSceneSequenceSwitches(obj);
|
||||
|
||||
if (wasRunning) {
|
||||
switcher->Start();
|
||||
}
|
||||
|
||||
obs_data_release(obj);
|
||||
|
||||
DisplayMessage(obs_module_text(
|
||||
@@ -164,7 +144,8 @@ void AdvSceneSwitcher::on_sceneSequenceLoad_clicked()
|
||||
void AdvSceneSwitcher::OpenSequenceExtendEdit(SequenceWidget *sw)
|
||||
{
|
||||
QDialog edit;
|
||||
SequenceWidget editWidget(this, sw->getSwitchData(), false, true);
|
||||
SequenceWidget editWidget(this, sw->getSwitchData(), false, true,
|
||||
false);
|
||||
QHBoxLayout layout;
|
||||
layout.setSizeConstraint(QLayout::SetFixedSize);
|
||||
layout.addWidget(&editWidget);
|
||||
@@ -173,7 +154,7 @@ void AdvSceneSwitcher::OpenSequenceExtendEdit(SequenceWidget *sw)
|
||||
"AdvSceneSwitcher.sceneSequenceTab.extendEdit"));
|
||||
edit.exec();
|
||||
|
||||
sw->UpdateExtendText();
|
||||
sw->UpdateWidgetStatus(true);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_sequenceEdit_clicked()
|
||||
@@ -573,11 +554,17 @@ void SceneSequenceSwitch::logSequenceCanceled()
|
||||
blog(LOG_INFO, "unexpected scene change - cancel sequence");
|
||||
}
|
||||
|
||||
void populateDelayUnits(QComboBox *list)
|
||||
QString delayMultiplierToString(int delayMultiplier)
|
||||
{
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.unit.secends"));
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.unit.minutes"));
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.unit.hours"));
|
||||
switch (delayMultiplier) {
|
||||
case 1:
|
||||
return obs_module_text("AdvSceneSwitcher.unit.secends");
|
||||
case 60:
|
||||
return obs_module_text("AdvSceneSwitcher.unit.minutes");
|
||||
case 60 * 60:
|
||||
return obs_module_text("AdvSceneSwitcher.unit.hours");
|
||||
}
|
||||
return "???";
|
||||
}
|
||||
|
||||
QString makeExtendText(SceneSequenceSwitch *s, int curLen = 0)
|
||||
@@ -589,21 +576,7 @@ QString makeExtendText(SceneSequenceSwitch *s, int curLen = 0)
|
||||
QString ext = "";
|
||||
|
||||
ext = QString::number(s->delay / s->delayMultiplier) + " ";
|
||||
|
||||
switch (s->delayMultiplier) {
|
||||
case 1:
|
||||
ext += obs_module_text("AdvSceneSwitcher.unit.secends");
|
||||
break;
|
||||
case 60:
|
||||
ext += obs_module_text("AdvSceneSwitcher.unit.minutes");
|
||||
break;
|
||||
case 60 * 60:
|
||||
ext += obs_module_text("AdvSceneSwitcher.unit.hours");
|
||||
break;
|
||||
default:
|
||||
ext += obs_module_text("?????");
|
||||
break;
|
||||
}
|
||||
ext += delayMultiplierToString(s->delayMultiplier);
|
||||
|
||||
QString sceneName = GetWeakSourceName(s->scene).c_str();
|
||||
if (s->targetType == SwitchTargetType::SceneGroup && s->group) {
|
||||
@@ -627,8 +600,16 @@ QString makeExtendText(SceneSequenceSwitch *s, int curLen = 0)
|
||||
}
|
||||
}
|
||||
|
||||
void populateDelayUnits(QComboBox *list)
|
||||
{
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.unit.secends"));
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.unit.minutes"));
|
||||
list->addItem(obs_module_text("AdvSceneSwitcher.unit.hours"));
|
||||
}
|
||||
|
||||
SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
|
||||
bool extendSequence, bool editExtendMode)
|
||||
bool extendSequence, bool editExtendMode,
|
||||
bool showExtendText)
|
||||
: SwitchWidget(parent, s, !extendSequence, true)
|
||||
{
|
||||
this->setParent(parent);
|
||||
@@ -678,25 +659,8 @@ SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
|
||||
interruptible->setToolTip(obs_module_text(
|
||||
"AdvSceneSwitcher.sceneSequenceTab.interruptibleHint"));
|
||||
|
||||
if (s) {
|
||||
switch (s->delayMultiplier) {
|
||||
case 1:
|
||||
delayUnits->setCurrentIndex(0);
|
||||
break;
|
||||
case 60:
|
||||
delayUnits->setCurrentIndex(1);
|
||||
break;
|
||||
case 3600:
|
||||
delayUnits->setCurrentIndex(2);
|
||||
break;
|
||||
default:
|
||||
delayUnits->setCurrentIndex(0);
|
||||
}
|
||||
startScenes->setCurrentText(
|
||||
GetWeakSourceName(s->startScene).c_str());
|
||||
interruptible->setChecked(s->interruptible);
|
||||
}
|
||||
|
||||
// The extended sequence widgets never exist on their own and are always
|
||||
// place inside a non-extend sequence widget
|
||||
if (extendSequence) {
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
@@ -722,7 +686,7 @@ SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
|
||||
"AdvSceneSwitcher.sceneSequenceTab.entry"),
|
||||
startSequence, widgetPlaceholders);
|
||||
|
||||
//exetend widgets placed here
|
||||
// The exetend widgets placed here
|
||||
extendSequenceLayout = new QVBoxLayout;
|
||||
if (s) {
|
||||
if (!editExtendMode) {
|
||||
@@ -755,10 +719,9 @@ SequenceWidget::SequenceWidget(QWidget *parent, SceneSequenceSwitch *s,
|
||||
}
|
||||
|
||||
switchData = s;
|
||||
UpdateWidgetStatus(showExtendText);
|
||||
|
||||
loading = false;
|
||||
|
||||
UpdateDelay();
|
||||
}
|
||||
|
||||
SceneSequenceSwitch *SequenceWidget::getSwitchData()
|
||||
@@ -874,9 +837,33 @@ void SequenceWidget::InterruptibleChanged(int state)
|
||||
}
|
||||
}
|
||||
|
||||
void SequenceWidget::UpdateExtendText()
|
||||
void SequenceWidget::UpdateWidgetStatus(bool showExtendText)
|
||||
{
|
||||
extendText->setText(makeExtendText(switchData->extendedSequence.get()));
|
||||
if (showExtendText) {
|
||||
extendText->setText(
|
||||
makeExtendText(switchData->extendedSequence.get()));
|
||||
}
|
||||
|
||||
switch (switchData->delayMultiplier) {
|
||||
case 1:
|
||||
delayUnits->setCurrentIndex(0);
|
||||
break;
|
||||
case 60:
|
||||
delayUnits->setCurrentIndex(1);
|
||||
break;
|
||||
case 3600:
|
||||
delayUnits->setCurrentIndex(2);
|
||||
break;
|
||||
default:
|
||||
delayUnits->setCurrentIndex(0);
|
||||
}
|
||||
UpdateDelay();
|
||||
|
||||
startScenes->setCurrentText(
|
||||
GetWeakSourceName(switchData->startScene).c_str());
|
||||
interruptible->setChecked(switchData->interruptible);
|
||||
|
||||
SwitchWidget::showSwitchData();
|
||||
}
|
||||
|
||||
void SequenceWidget::ExtendClicked()
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
@@ -188,6 +190,16 @@ void AdvSceneSwitcher::on_transitionOverridecheckBox_stateChanged(int 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,
|
||||
obs_weak_source_t *scene2)
|
||||
{
|
||||
@@ -322,6 +334,10 @@ void SwitcherData::saveSceneTransitions(obs_data_t *obj)
|
||||
|
||||
obs_data_set_bool(obj, "tansitionOverrideOverride",
|
||||
switcher->tansitionOverrideOverride);
|
||||
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 +390,8 @@ void SwitcherData::loadSceneTransitions(obs_data_t *obj)
|
||||
|
||||
switcher->tansitionOverrideOverride =
|
||||
obs_data_get_bool(obj, "tansitionOverrideOverride");
|
||||
DefaultSceneTransition::delay =
|
||||
obs_data_get_int(obj, "defTransitionDelay");
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::setupTransitionsTab()
|
||||
@@ -412,6 +430,24 @@ void AdvSceneSwitcher::setupTransitionsTab()
|
||||
|
||||
ui->transitionOverridecheckBox->setChecked(
|
||||
switcher->tansitionOverrideOverride);
|
||||
|
||||
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 +563,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 +571,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 +588,6 @@ void setTransitionDelayed(OBSWeakSource transition)
|
||||
void DefaultSceneTransition::setTransition()
|
||||
{
|
||||
std::thread t;
|
||||
t = std::thread(setTransitionDelayed, transition);
|
||||
t = std::thread(setTransitionDelayed, transition, delay);
|
||||
t.detach();
|
||||
}
|
||||
|
||||
698
src/switch-video.cpp
Normal file
698
src/switch-video.cpp
Normal file
@@ -0,0 +1,698 @@
|
||||
#include <QFileDialog>
|
||||
#include <QBuffer>
|
||||
#include <QToolTip>
|
||||
#include <thread>
|
||||
|
||||
#include "headers/advanced-scene-switcher.hpp"
|
||||
#include "headers/utility.hpp"
|
||||
|
||||
bool VideoSwitch::pause = false;
|
||||
static QMetaObject::Connection addPulse;
|
||||
|
||||
static void ScreenshotTick(void *param, float);
|
||||
|
||||
AdvSSScreenshotObj::AdvSSScreenshotObj(obs_source_t *source)
|
||||
: weakSource(OBSGetWeakRef(source))
|
||||
{
|
||||
obs_add_tick_callback(ScreenshotTick, this);
|
||||
}
|
||||
|
||||
AdvSSScreenshotObj::~AdvSSScreenshotObj()
|
||||
{
|
||||
obs_enter_graphics();
|
||||
gs_stagesurface_destroy(stagesurf);
|
||||
gs_texrender_destroy(texrender);
|
||||
obs_leave_graphics();
|
||||
|
||||
obs_remove_tick_callback(ScreenshotTick, this);
|
||||
}
|
||||
|
||||
void AdvSSScreenshotObj::Screenshot()
|
||||
{
|
||||
OBSSource source = OBSGetStrongRef(weakSource);
|
||||
|
||||
if (source) {
|
||||
cx = obs_source_get_base_width(source);
|
||||
cy = obs_source_get_base_height(source);
|
||||
} else {
|
||||
obs_video_info ovi;
|
||||
obs_get_video_info(&ovi);
|
||||
cx = ovi.base_width;
|
||||
cy = ovi.base_height;
|
||||
}
|
||||
|
||||
if (!cx || !cy) {
|
||||
blog(LOG_WARNING, "Cannot screenshot, invalid target size");
|
||||
obs_remove_tick_callback(ScreenshotTick, this);
|
||||
done = true;
|
||||
return;
|
||||
}
|
||||
|
||||
texrender = gs_texrender_create(GS_RGBA, GS_ZS_NONE);
|
||||
stagesurf = gs_stagesurface_create(cx, cy, GS_RGBA);
|
||||
|
||||
gs_texrender_reset(texrender);
|
||||
if (gs_texrender_begin(texrender, cx, cy)) {
|
||||
vec4 zero;
|
||||
vec4_zero(&zero);
|
||||
|
||||
gs_clear(GS_CLEAR_COLOR, &zero, 0.0f, 0);
|
||||
gs_ortho(0.0f, (float)cx, 0.0f, (float)cy, -100.0f, 100.0f);
|
||||
|
||||
gs_blend_state_push();
|
||||
gs_blend_function(GS_BLEND_ONE, GS_BLEND_ZERO);
|
||||
|
||||
if (source) {
|
||||
obs_source_inc_showing(source);
|
||||
obs_source_video_render(source);
|
||||
obs_source_dec_showing(source);
|
||||
} else {
|
||||
obs_render_main_texture();
|
||||
}
|
||||
|
||||
gs_blend_state_pop();
|
||||
gs_texrender_end(texrender);
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSSScreenshotObj::Download()
|
||||
{
|
||||
gs_stage_texture(stagesurf, gs_texrender_get_texture(texrender));
|
||||
}
|
||||
|
||||
void AdvSSScreenshotObj::Copy()
|
||||
{
|
||||
uint8_t *videoData = nullptr;
|
||||
uint32_t videoLinesize = 0;
|
||||
|
||||
image = QImage(cx, cy, QImage::Format::Format_RGBX8888);
|
||||
|
||||
if (gs_stagesurface_map(stagesurf, &videoData, &videoLinesize)) {
|
||||
int linesize = image.bytesPerLine();
|
||||
for (int y = 0; y < (int)cy; y++)
|
||||
memcpy(image.scanLine(y),
|
||||
videoData + (y * videoLinesize), linesize);
|
||||
|
||||
gs_stagesurface_unmap(stagesurf);
|
||||
}
|
||||
}
|
||||
|
||||
void AdvSSScreenshotObj::MarkDone()
|
||||
{
|
||||
time = std::chrono::high_resolution_clock::now();
|
||||
done = true;
|
||||
}
|
||||
|
||||
#define STAGE_SCREENSHOT 0
|
||||
#define STAGE_DOWNLOAD 1
|
||||
#define STAGE_COPY_AND_SAVE 2
|
||||
#define STAGE_FINISH 3
|
||||
|
||||
static void ScreenshotTick(void *param, float)
|
||||
{
|
||||
AdvSSScreenshotObj *data =
|
||||
reinterpret_cast<AdvSSScreenshotObj *>(param);
|
||||
|
||||
if (data->stage == STAGE_FINISH) {
|
||||
return;
|
||||
}
|
||||
|
||||
obs_enter_graphics();
|
||||
|
||||
switch (data->stage) {
|
||||
case STAGE_SCREENSHOT:
|
||||
data->Screenshot();
|
||||
break;
|
||||
case STAGE_DOWNLOAD:
|
||||
data->Download();
|
||||
break;
|
||||
case STAGE_COPY_AND_SAVE:
|
||||
data->Copy();
|
||||
data->MarkDone();
|
||||
|
||||
obs_remove_tick_callback(ScreenshotTick, data);
|
||||
break;
|
||||
}
|
||||
|
||||
obs_leave_graphics();
|
||||
|
||||
data->stage++;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_videoAdd_clicked()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switcher->videoSwitches.emplace_back();
|
||||
|
||||
VideoSwitchWidget *sw =
|
||||
new VideoSwitchWidget(this, &switcher->videoSwitches.back());
|
||||
|
||||
listAddClicked(ui->videoSwitches, sw, ui->videoAdd, &addPulse);
|
||||
|
||||
ui->videoHelp->setVisible(false);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_videoRemove_clicked()
|
||||
{
|
||||
QListWidgetItem *item = ui->videoSwitches->currentItem();
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
int idx = ui->videoSwitches->currentRow();
|
||||
auto &switches = switcher->videoSwitches;
|
||||
switches.erase(switches.begin() + idx);
|
||||
}
|
||||
|
||||
delete item;
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_videoUp_clicked()
|
||||
{
|
||||
int index = ui->videoSwitches->currentRow();
|
||||
if (!listMoveUp(ui->videoSwitches)) {
|
||||
return;
|
||||
}
|
||||
|
||||
VideoSwitchWidget *s1 =
|
||||
(VideoSwitchWidget *)ui->videoSwitches->itemWidget(
|
||||
ui->videoSwitches->item(index));
|
||||
VideoSwitchWidget *s2 =
|
||||
(VideoSwitchWidget *)ui->videoSwitches->itemWidget(
|
||||
ui->videoSwitches->item(index - 1));
|
||||
VideoSwitchWidget::swapSwitchData(s1, s2);
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
|
||||
std::swap(switcher->videoSwitches[index],
|
||||
switcher->videoSwitches[index - 1]);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_videoDown_clicked()
|
||||
{
|
||||
int index = ui->videoSwitches->currentRow();
|
||||
|
||||
if (!listMoveDown(ui->videoSwitches)) {
|
||||
return;
|
||||
}
|
||||
|
||||
VideoSwitchWidget *s1 =
|
||||
(VideoSwitchWidget *)ui->videoSwitches->itemWidget(
|
||||
ui->videoSwitches->item(index));
|
||||
VideoSwitchWidget *s2 =
|
||||
(VideoSwitchWidget *)ui->videoSwitches->itemWidget(
|
||||
ui->videoSwitches->item(index + 1));
|
||||
VideoSwitchWidget::swapSwitchData(s1, s2);
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
|
||||
std::swap(switcher->videoSwitches[index],
|
||||
switcher->videoSwitches[index + 1]);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::on_getScreenshot_clicked()
|
||||
{
|
||||
QListWidgetItem *item = ui->videoSwitches->currentItem();
|
||||
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
VideoSwitchWidget *sw =
|
||||
(VideoSwitchWidget *)ui->videoSwitches->itemWidget(item);
|
||||
auto s = sw->getSwitchData();
|
||||
if (!s || !s->videoSource) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto source = obs_weak_source_get_source(s->videoSource);
|
||||
auto screenshotData = std::make_unique<AdvSSScreenshotObj>(source);
|
||||
obs_source_release(source);
|
||||
|
||||
QString filePath = QFileDialog::getSaveFileName(this);
|
||||
if (filePath.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// During selection of the save path enough time should usually have
|
||||
// passed already
|
||||
// Add this just in case ...
|
||||
if (!screenshotData->done) {
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
|
||||
if (!screenshotData->done) {
|
||||
DisplayMessage("Failed to get screenshot of source!");
|
||||
return;
|
||||
}
|
||||
|
||||
screenshotData->image.save(file.fileName());
|
||||
sw->SetFilePath(file.fileName());
|
||||
}
|
||||
|
||||
void SwitcherData::checkVideoSwitch(bool &match, OBSWeakSource &scene,
|
||||
OBSWeakSource &transition)
|
||||
{
|
||||
if (VideoSwitch::pause) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto &s : videoSwitches) {
|
||||
bool matched = s.checkMatch();
|
||||
if (!match && matched) {
|
||||
match = true;
|
||||
scene = s.getScene();
|
||||
transition = s.transition;
|
||||
if (verbose) {
|
||||
s.logMatch();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SwitcherData::saveVideoSwitches(obs_data_t *obj)
|
||||
{
|
||||
obs_data_array_t *videoArray = obs_data_array_create();
|
||||
for (VideoSwitch &s : videoSwitches) {
|
||||
obs_data_t *array_obj = obs_data_create();
|
||||
|
||||
s.save(array_obj);
|
||||
obs_data_array_push_back(videoArray, array_obj);
|
||||
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_set_array(obj, "videoSwitches", videoArray);
|
||||
obs_data_array_release(videoArray);
|
||||
}
|
||||
|
||||
void SwitcherData::loadVideoSwitches(obs_data_t *obj)
|
||||
{
|
||||
switcher->videoSwitches.clear();
|
||||
|
||||
obs_data_array_t *videoArray = obs_data_get_array(obj, "videoSwitches");
|
||||
size_t count = obs_data_array_count(videoArray);
|
||||
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
obs_data_t *array_obj = obs_data_array_item(videoArray, i);
|
||||
|
||||
switcher->videoSwitches.emplace_back();
|
||||
videoSwitches.back().load(array_obj);
|
||||
|
||||
obs_data_release(array_obj);
|
||||
}
|
||||
obs_data_array_release(videoArray);
|
||||
}
|
||||
|
||||
void AdvSceneSwitcher::setupVideoTab()
|
||||
{
|
||||
for (auto &s : switcher->videoSwitches) {
|
||||
QListWidgetItem *item;
|
||||
item = new QListWidgetItem(ui->videoSwitches);
|
||||
ui->videoSwitches->addItem(item);
|
||||
VideoSwitchWidget *sw = new VideoSwitchWidget(this, &s);
|
||||
item->setSizeHint(sw->minimumSizeHint());
|
||||
ui->videoSwitches->setItemWidget(item, sw);
|
||||
}
|
||||
|
||||
if (switcher->videoSwitches.size() == 0) {
|
||||
addPulse = PulseWidget(ui->videoAdd, QColor(Qt::green));
|
||||
ui->videoHelp->setVisible(true);
|
||||
} else {
|
||||
ui->videoHelp->setVisible(false);
|
||||
}
|
||||
|
||||
ui->getScreenshot->setToolTip(
|
||||
obs_module_text("AdvSceneSwitcher.videoTab.getScreenshotHelp"));
|
||||
}
|
||||
|
||||
bool VideoSwitch::initialized()
|
||||
{
|
||||
return SceneSwitcherEntry::initialized() && videoSource;
|
||||
}
|
||||
|
||||
bool VideoSwitch::valid()
|
||||
{
|
||||
return !initialized() ||
|
||||
(SceneSwitcherEntry::valid() && WeakSourceValid(videoSource));
|
||||
}
|
||||
|
||||
void VideoSwitch::save(obs_data_t *obj)
|
||||
{
|
||||
SceneSwitcherEntry::save(obj);
|
||||
|
||||
obs_data_set_string(obj, "videoSource",
|
||||
GetWeakSourceName(videoSource).c_str());
|
||||
obs_data_set_int(obj, "condition", static_cast<int>(condition));
|
||||
obs_data_set_double(obj, "duration", duration);
|
||||
obs_data_set_string(obj, "filePath", file.c_str());
|
||||
obs_data_set_bool(obj, "ignoreInactiveSource", ignoreInactiveSource);
|
||||
}
|
||||
|
||||
void VideoSwitch::load(obs_data_t *obj)
|
||||
{
|
||||
SceneSwitcherEntry::load(obj);
|
||||
|
||||
const char *videoSourceName = obs_data_get_string(obj, "videoSource");
|
||||
videoSource = GetWeakSourceByName(videoSourceName);
|
||||
condition = static_cast<videoSwitchType>(
|
||||
obs_data_get_int(obj, "condition"));
|
||||
duration = obs_data_get_double(obj, "duration");
|
||||
file = obs_data_get_string(obj, "filePath");
|
||||
ignoreInactiveSource = obs_data_get_bool(obj, "ignoreInactiveSource");
|
||||
|
||||
if (condition != videoSwitchType::HAS_NOT_CHANGED) {
|
||||
(void)loadImageFromFile();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoSwitch::getScreenshot()
|
||||
{
|
||||
auto source = obs_weak_source_get_source(videoSource);
|
||||
screenshotData = std::make_unique<AdvSSScreenshotObj>(source);
|
||||
obs_source_release(source);
|
||||
}
|
||||
|
||||
bool VideoSwitch::loadImageFromFile()
|
||||
{
|
||||
if (!matchImage.load(QString::fromStdString(file))) {
|
||||
blog(LOG_WARNING, "Cannot load image data from file '%s'",
|
||||
file.c_str());
|
||||
return false;
|
||||
}
|
||||
matchImage =
|
||||
matchImage.convertToFormat(QImage::Format::Format_RGBX8888);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VideoSwitch::checkMatch()
|
||||
{
|
||||
if (ignoreInactiveSource) {
|
||||
obs_source_t *vs = obs_weak_source_get_source(videoSource);
|
||||
bool videoActive = obs_source_active(vs);
|
||||
obs_source_release(vs);
|
||||
|
||||
if (!videoActive) {
|
||||
screenshotData.reset(nullptr);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool match = false;
|
||||
|
||||
if (screenshotData) {
|
||||
if (screenshotData->done) {
|
||||
bool conditionMatch = false;
|
||||
|
||||
switch (condition) {
|
||||
case videoSwitchType::MATCH:
|
||||
conditionMatch = screenshotData->image ==
|
||||
matchImage;
|
||||
break;
|
||||
case videoSwitchType::DIFFER:
|
||||
conditionMatch = screenshotData->image !=
|
||||
matchImage;
|
||||
break;
|
||||
case videoSwitchType::HAS_NOT_CHANGED:
|
||||
conditionMatch = screenshotData->image ==
|
||||
matchImage;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (conditionMatch) {
|
||||
currentMatchDuration +=
|
||||
std::chrono::duration_cast<
|
||||
std::chrono::milliseconds>(
|
||||
screenshotData->time -
|
||||
previousTime);
|
||||
} else {
|
||||
currentMatchDuration = {};
|
||||
}
|
||||
|
||||
bool durationMatch = currentMatchDuration.count() >=
|
||||
duration * 1000;
|
||||
|
||||
if (conditionMatch && durationMatch) {
|
||||
match = true;
|
||||
}
|
||||
|
||||
if (condition == videoSwitchType::HAS_NOT_CHANGED) {
|
||||
matchImage = std::move(screenshotData->image);
|
||||
}
|
||||
previousTime = std::move(screenshotData->time);
|
||||
|
||||
screenshotData.reset(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
getScreenshot();
|
||||
return match;
|
||||
}
|
||||
|
||||
void swap(VideoSwitch &first, VideoSwitch &second)
|
||||
{
|
||||
std::swap(first.targetType, second.targetType);
|
||||
std::swap(first.group, second.group);
|
||||
std::swap(first.scene, second.scene);
|
||||
std::swap(first.transition, second.transition);
|
||||
std::swap(first.usePreviousScene, second.usePreviousScene);
|
||||
std::swap(first.videoSource, second.videoSource);
|
||||
}
|
||||
|
||||
static inline void populateConditionSelection(QComboBox *list)
|
||||
{
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.videoTab.condition.match"));
|
||||
list->setItemData(
|
||||
0,
|
||||
obs_module_text(
|
||||
"AdvSceneSwitcher.videoTab.condition.match.tooltip"),
|
||||
Qt::ToolTipRole);
|
||||
list->addItem(
|
||||
obs_module_text("AdvSceneSwitcher.videoTab.condition.differ"));
|
||||
list->addItem(obs_module_text(
|
||||
"AdvSceneSwitcher.videoTab.condition.hasNotChanged"));
|
||||
}
|
||||
|
||||
VideoSwitchWidget::VideoSwitchWidget(QWidget *parent, VideoSwitch *s)
|
||||
: SwitchWidget(parent, s, true, true)
|
||||
{
|
||||
videoSources = new QComboBox();
|
||||
condition = new QComboBox();
|
||||
duration = new QDoubleSpinBox();
|
||||
filePath = new QLineEdit();
|
||||
browseButton =
|
||||
new QPushButton(obs_module_text("AdvSceneSwitcher.browse"));
|
||||
ignoreInactiveSource = new QCheckBox(obs_module_text(
|
||||
"AdvSceneSwitcher.videoTab.ignoreInactiveSource"));
|
||||
|
||||
filePath->setFixedWidth(100);
|
||||
|
||||
browseButton->setStyleSheet("border:1px solid gray;");
|
||||
|
||||
duration->setMinimum(0.0);
|
||||
duration->setMaximum(99.000000);
|
||||
duration->setSuffix("s");
|
||||
|
||||
QWidget::connect(videoSources,
|
||||
SIGNAL(currentTextChanged(const QString &)), this,
|
||||
SLOT(SourceChanged(const QString &)));
|
||||
QWidget::connect(condition, SIGNAL(currentIndexChanged(int)), this,
|
||||
SLOT(ConditionChanged(int)));
|
||||
QWidget::connect(duration, SIGNAL(valueChanged(double)), this,
|
||||
SLOT(DurationChanged(double)));
|
||||
QWidget::connect(filePath, SIGNAL(editingFinished()), this,
|
||||
SLOT(FilePathChanged()));
|
||||
QWidget::connect(browseButton, SIGNAL(clicked()), this,
|
||||
SLOT(BrowseButtonClicked()));
|
||||
QWidget::connect(ignoreInactiveSource, SIGNAL(stateChanged(int)), this,
|
||||
SLOT(IgnoreInactiveChanged(int)));
|
||||
|
||||
// TODO:
|
||||
// Figure out why scene do not work for "match exactly".
|
||||
// Until then do not allow selecting scenes
|
||||
AdvSceneSwitcher::populateVideoSelection(videoSources, false);
|
||||
populateConditionSelection(condition);
|
||||
|
||||
if (s) {
|
||||
videoSources->setCurrentText(
|
||||
GetWeakSourceName(s->videoSource).c_str());
|
||||
condition->setCurrentIndex(static_cast<int>(s->condition));
|
||||
duration->setValue(s->duration);
|
||||
filePath->setText(QString::fromStdString(s->file));
|
||||
ignoreInactiveSource->setChecked(s->ignoreInactiveSource);
|
||||
|
||||
if (s->condition == videoSwitchType::HAS_NOT_CHANGED) {
|
||||
filePath->hide();
|
||||
browseButton->hide();
|
||||
}
|
||||
}
|
||||
|
||||
QHBoxLayout *switchLayout = new QHBoxLayout;
|
||||
std::unordered_map<std::string, QWidget *> widgetPlaceholders = {
|
||||
{"{{videoSources}}", videoSources},
|
||||
{"{{condition}}", condition},
|
||||
{"{{duration}}", duration},
|
||||
{"{{filePath}}", filePath},
|
||||
{"{{browseButton}}", browseButton},
|
||||
{"{{ignoreInactiveSource}}", ignoreInactiveSource},
|
||||
{"{{scenes}}", scenes},
|
||||
{"{{transitions}}", transitions}};
|
||||
placeWidgets(obs_module_text("AdvSceneSwitcher.videoTab.entry"),
|
||||
switchLayout, widgetPlaceholders);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
|
||||
mainLayout->addLayout(switchLayout);
|
||||
setLayout(mainLayout);
|
||||
|
||||
switchData = s;
|
||||
UpdatePreviewTooltip();
|
||||
|
||||
loading = false;
|
||||
}
|
||||
|
||||
VideoSwitch *VideoSwitchWidget::getSwitchData()
|
||||
{
|
||||
return switchData;
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::setSwitchData(VideoSwitch *s)
|
||||
{
|
||||
switchData = s;
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::swapSwitchData(VideoSwitchWidget *s1,
|
||||
VideoSwitchWidget *s2)
|
||||
{
|
||||
SwitchWidget::swapSwitchData(s1, s2);
|
||||
|
||||
VideoSwitch *t = s1->getSwitchData();
|
||||
s1->setSwitchData(s2->getSwitchData());
|
||||
s2->setSwitchData(t);
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::UpdatePreviewTooltip()
|
||||
{
|
||||
if (!switchData ||
|
||||
switchData->condition == videoSwitchType::HAS_NOT_CHANGED) {
|
||||
return;
|
||||
}
|
||||
|
||||
QImage preview =
|
||||
switchData->matchImage.scaled({300, 300}, Qt::KeepAspectRatio);
|
||||
|
||||
QByteArray data;
|
||||
QBuffer buffer(&data);
|
||||
if (!preview.save(&buffer, "PNG")) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString html =
|
||||
QString("<html><img src='data:image/png;base64, %0'/></html>")
|
||||
.arg(QString(data.toBase64()));
|
||||
this->setToolTip(html);
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::SetFilePath(const QString &text)
|
||||
{
|
||||
filePath->setText(text);
|
||||
FilePathChanged();
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::SourceChanged(const QString &text)
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switchData->videoSource = GetWeakSourceByQString(text);
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::ConditionChanged(int cond)
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switchData->condition = static_cast<videoSwitchType>(cond);
|
||||
|
||||
if (switchData->condition == videoSwitchType::HAS_NOT_CHANGED) {
|
||||
filePath->hide();
|
||||
browseButton->hide();
|
||||
} else {
|
||||
filePath->show();
|
||||
browseButton->show();
|
||||
}
|
||||
|
||||
// Reload image data to avoid incorrect matches.
|
||||
//
|
||||
// Condition type HAS_NOT_CHANGED will use matchImage to store previous
|
||||
// frame of video source, which will differ from the image stored at
|
||||
// specified file location.
|
||||
if (switchData->loadImageFromFile()) {
|
||||
UpdatePreviewTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::DurationChanged(double dur)
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switchData->duration = dur;
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::FilePathChanged()
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switchData->file = filePath->text().toUtf8().constData();
|
||||
if (switchData->loadImageFromFile()) {
|
||||
UpdatePreviewTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::BrowseButtonClicked()
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString path = QFileDialog::getOpenFileName(
|
||||
this,
|
||||
tr(obs_module_text("AdvSceneSwitcher.fileTab.selectRead")),
|
||||
QDir::currentPath(),
|
||||
tr(obs_module_text("AdvSceneSwitcher.fileTab.anyFileType")));
|
||||
if (path.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
filePath->setText(path);
|
||||
FilePathChanged();
|
||||
}
|
||||
|
||||
void VideoSwitchWidget::IgnoreInactiveChanged(int state)
|
||||
{
|
||||
if (loading || !switchData) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(switcher->m);
|
||||
switchData->ignoreInactiveSource = state;
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <util/platform.h>
|
||||
#include "../headers/advanced-scene-switcher.hpp"
|
||||
|
||||
Reference in New Issue
Block a user